有声书 TTS API:成本、分段与制作流程

一本 300 页的书约 450,000 个字符:用 speech-2.8-hd 按刊例价合成需要 $45.00,按当前 5% 的分组折扣是 $42.75,成品音频约 8 小时 20 分钟。本页逐步算清这笔账,说明让长篇书稿可以稳定复现的分段与文件命名规则,并给出“先出草稿、再出母带”的两遍制作流程。

试听旁白示例

以下为英文示例:同一段文字分别由两个模型生成,经本站中转,未做任何后期处理。多数制作者用 turbo 出草稿、用 hd 出母带,下面两段之间的差别,就是你要做的取舍。

speech-2.8-hd
speech-2.8-turbo

音色:English_expressive_narrator。

按一本 300 页的书算一笔账

以一本普通的平装书为例:300 页,每页约 250 个单词,共 75,000 个单词。英文算上词后的空格,平均每个单词约 6 个字符,所以实际提交的书稿约为 450,000 个字符。这两个数字是规划用的估算值,不是实测值。真正计费的只有你发送的字符串长度,在拿到 API Key 之前就可以用 len(text) 算出来。

YiduoChan 上的 MiniMax 语音模型按输入字符计费,价格与 MiniMax 官方公布的按量付费价一致。speech-2.8-hd 为每百万字符 $100(每 1,000 字符 $0.10);speech-2.8-turbo 为每百万字符 $60(每 1,000 字符 $0.06)。目前所有账号都在倍率为 0.95 的分组里,实际扣费比刊例价低 5%。请把它当作当前的分组折扣,而不是永久承诺;向客户报价之前,先到语音价格页核对一下。

项目speech-2.8-turbospeech-2.8-hd
每百万输入字符刊例价$60.00$100.00
按当前 5% 分组折扣的实际单价$57.00$95.00
450,000 字符书稿,刊例价$27.00$45.00
450,000 字符书稿,5% 折扣后$25.65$42.75
单章 15,000 字符,刊例价$0.90$1.50
单章 15,000 字符,5% 折扣后$0.86$1.43
每小时成品音频(约 54,000 字符),刊例价$3.24$5.40
每小时成品音频,5% 折扣后$3.08$5.13

表中不足一美分的零头已四舍五入显示。精确值为:折后单章分别是 $0.855 和 $1.425,折后 turbo 每小时音频是 $3.078。

时长也用同样的方法估算:英文语音每分钟约 900 个字符,所以 450,000 个字符约为 500 分钟,即 8 小时 20 分钟的成品音频。换算成每小时约 54,000 个字符;出版方给你的是目标时长而不是字数时,用的就是这个换算。

为什么按字符计费让长篇内容的成本可预期

账单只取决于你发送的输入,而不取决于返回的音频。一部 450,000 字符的书稿在 speech-2.8-hd 上按刊例价是 $45.00,按当前 5% 的分组折扣是 $42.75;无论最终生成八小时还是九小时、请求 mp3 还是 wav、旁白读得快还是慢,都是这个价。正是这一点,让有声书预算变成一道表格计算题,而不必先做一个试点项目。把定稿的字符数统计一遍、乘上单价,第一个请求发出之前,你就能算出精确到美分的成本。

这也意味着花一个小时清理文本是值得的。计费会计入你提交的每一个字符,包括空格、标点、页眉、脚注标记,以及源文件里残留的 Markdown 标记。把 450,000 字符的书稿删掉 2%,就少了 9,000 个字符,在 speech-2.8-hd 上按刊例价折合 $0.90,按当前分组价折合 $0.855。更大的好处是:你不用再花钱让旁白把你的星号念出来。

长时间批量生成时,第二个关键特性是失败的请求从不计费。一整本书要发几百到几千个请求,持续数小时。第 22 章时连接断开、某个片段返回错误、重试循环连试三次才成功——这些都不消耗额度。只有你真正收到的响应才计费。所以可续跑的生成循环可以放心地激进重试:遇到任何错误就重试,某个片段反复失败就拆小再试,你的成本模型始终等于那些真正在磁盘上生成了音频的文本字符数。

书稿怎么分段

/v1/audio/speech 没有公布单次请求的字符上限。既然上限没有写进文档,就不要围绕一个猜出来的数字做设计。把书稿分段,让每个请求都保持在宽裕的小尺寸;某个过大的片段报错时,把它当作需要继续拆分的信号,而不是一笔开销。

先按章

首先按章节边界切分。章节是编辑讨论问题的单位,是听众跳转的单位,也是某个专有名词读错时你要重新生成的单位。在示例书里,30 章、平均每章 15,000 个字符,合计正好 450,000;按每分钟 900 字符估算,每章约 16 分 40 秒音频。按章发请求还自然形成一份进度日志:30 行输出,每行带一个字符数,可以和账单逐项核对。

再按段落

如果某一章长到让你不放心,就再按空行切开,把完整的段落装进不超过固定上限的分块里——8,000 字符是一个保守的起点。永远不要在句子中间切开。韵律是按请求生成的,在从句中间断开,接缝处能听出来;在段落之间断开,则正好落在本来就该有短暂停顿的地方。装箱逻辑要保持贪心且确定,这样对没有改动的章节重新运行切分程序,得到的分块逐字节相同。

让文件名按播放顺序排序

每个序号都用前导零补齐到足以容纳最大值的宽度:从 ch001.wav 到 ch030.wav,段落分块则用 ch014_p0007.wav 这样的名字。不补零的文件名在任何 shell 通配、任何 sorted() 调用和任何文件管理器里都会排成 ch1、ch10、ch2,而这个错误要到拼接那一步才会发现,那时钱已经花出去了。书前内容单独占一个位置 ch000_frontmatter,让它排在第一章之前。每一遍的输出放在各自的目录里(draft/、master/),不要把模型名写进文件名,这样同一份排序列表对两遍都适用。

只重新生成一章

按章的粒度让修改保持便宜。在 speech-2.8-hd 上重新生成一章 15,000 字符,按刊例价是 $1.50,按当前分组价是 $1.425;在 speech-2.8-turbo 上分别是 $0.90 和 $0.855。所以修正第 14 章里一个读错的姓氏只要 $1.50,而整本生成要 $45.00——正好是全书的三十分之一,与这一章所占的字符比例一致;如果请求中途失败,则一分钱都不花。要是把整本书放进一个请求,为了改 40 个字符,你就得重新生成 450,000 个字符。

保持音色与节奏一致

八小时音频的一致性是配置问题,不是调参问题。整本书固定用一个音色 ID,中途绝不更换:英文旁白用 English_expressive_narrator,或者用其他官方音色 ID,例如 male-qn-qingse、female-shaonv。你也可以通过 /v1/minimax/voice_clone 用一段样本克隆出旁白音色,再固定使用它返回的音色 ID。无论哪种方式,旁白的身份就是你固定的那个音色 ID,所以相隔几个月生成的同一本书,听起来仍是同一位朗读者。

MiniMax 专有参数也一样处理。构造一个包含 voice_setting.emotion 和 audio_setting.sample_rate 的 metadata 对象,在模块级只定义一次,整本书的每个请求都传入同一个对象。逐章改变情绪,会在章节交界处产生听得出的变化,除非你本来就想要这种效果;改变 sample_rate 则会迫使拼接时重新采样。把模型 ID、音色 ID、输出格式和 metadata 对象写进音频旁边的 manifest.json,这样六个月后重新生成时,能复现第 13 章和第 15 章当初所用的设置。另外,同一本成品书里不要混用 speech-2.8-turbo 和 speech-2.8-hd。

格式:母带用无损,发布用 mp3

response_format 支持 mp3、wav、flac 和 pcm,默认 mp3。由于按输入字符计费,选哪种格式都不影响价格——示例书的 wav 母带按刊例价同样是 $45.00,与它的 mp3 草稿一样。

母带用 wav 或 flac 生成。两者都是无损格式,做音量标准化、裁剪静音、拼接分块都不会因反复编码而损失音质;flac 保存的是同样的采样,文件比 wav 小。只有在把原始采样直接送进 DSP 处理链、并且已经知道采样率和位深时才用 pcm,因为它没有容器头来告诉你这些信息。mp3 只在最后生成一次,由完成的无损母带编码而来——而不是向 API 要 mp3、剪辑后再重新编码。一次性的草稿那一遍可以直接用 mp3,那些文件听一遍就会删掉。这个接口没有文档说明支持流式音频,所以把生成程序写成批处理任务,批量生成本来也适合这样写。

按章生成音频的脚本

下面的循环先给整个任务报价,再把各章文本生成为编号文件,已经在磁盘上的文件会跳过,所以中断后重跑可以接着来,不会重复付费。它使用 OpenAI 兼容的 Base URL https://yiduochan.com/v1,由 SDK 带上 Authorization: Bearer <your API key>;Node 的等价写法见 SDK 配置说明。

import glob, json, os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["YIDUOCHAN_API_KEY"],
    base_url="https://yiduochan.com/v1",
)

LIST_USD_PER_1M = {"speech-2.8-turbo": 60.0, "speech-2.8-hd": 100.0}
GROUP_RATIO = 0.95  # current 5% group discount

VOICE = "English_expressive_narrator"
METADATA = {
    "voice_setting": {"emotion": "neutral"},
    "audio_setting": {"sample_rate": 44100},
}


def quote(chapters, model):
    chars = sum(len(c) for c in chapters)
    listed = chars / 1_000_000 * LIST_USD_PER_1M[model]
    return chars, round(listed, 4), round(listed * GROUP_RATIO, 4)


def split_paragraphs(text, max_chars=8000):
    """Pack whole paragraphs into parts; never split a sentence."""
    parts, buf = [], ""
    for para in text.split("\n\n"):
        if buf and len(buf) + len(para) + 2 > max_chars:
            parts.append(buf)
            buf = para
        else:
            buf = f"{buf}\n\n{para}" if buf else para
    if buf:
        parts.append(buf)
    return parts


def render(chapters, model, fmt, outdir):
    os.makedirs(outdir, exist_ok=True)
    for i, chapter in enumerate(chapters, start=1):
        for j, part in enumerate(split_paragraphs(chapter), start=1):
            path = os.path.join(outdir, f"ch{i:03d}_p{j:04d}.{fmt}")
            if os.path.exists(path):      # resume; failed runs cost nothing
                continue
            audio = client.audio.speech.create(
                model=model,
                input=part,
                voice=VOICE,
                response_format=fmt,
                extra_body={"metadata": METADATA},
            )
            with open(path, "wb") as f:
                f.write(audio.content)
            print(path, len(part), "chars")
    with open(os.path.join(outdir, "manifest.json"), "w") as f:
        json.dump({"model": model, "voice": VOICE,
                   "response_format": fmt, "metadata": METADATA}, f, indent=2)


chapters = [open(p, encoding="utf-8").read()
            for p in sorted(glob.glob("manuscript/ch*.txt"))]

print(quote(chapters, "speech-2.8-hd"))       # (450000, 45.0, 42.75)
render(chapters, "speech-2.8-turbo", "mp3", "draft")   # pass 1
render(chapters, "speech-2.8-hd", "wav", "master")      # pass 2

两遍制作:turbo 出草稿,hd 出母带

一遍成书的典型翻车方式是:听到第六个小时才发现,旁白把一个反复出现的姓氏读错了;书稿里本来指页码的数字被读成了“eighteen forty-two”;或者把一个残留的 Markdown 标题念了出来。这些全都是书稿的问题,你希望用便宜的价格把它们找出来。

第一遍用 speech-2.8-turbo 把整本书生成为 mp3,放进 draft/,唯一的用途是倍速试听。改好书稿文本后,第二遍用 speech-2.8-hd 把修正后的文本生成为 wav,放进 master/。

遍次模型格式字符数刊例价5% 折扣后
1 — 草稿与质检speech-2.8-turbomp3450,000$27.00$25.65
2 — 母带speech-2.8-hdwav450,000$45.00$42.75
合计——900,000$72.00$68.40

总成本是单遍 hd 的 1.6 倍:按刊例价多花 $27.00,按当前分组价多花 $25.65。盈亏平衡点很好算。按刊例价,hd 重新生成一章要 $1.50,所以草稿这一遍只要避免了 18 次按章重做就回本了($27.00 ÷ $1.50 = 18)。按折扣价算,比例完全相同:$25.65 ÷ $1.425 = 18。对一本 30 章的书来说,这相当于 60% 的章节。

开通账号,核算你自己的数字

使用方式是预付美元额度,按量付费,无订阅;新账号注册即送 $0.10 试用额度,约合一分钟的 hd 旁白,正式使用需要充值。最低充值 $5,预设档位有 $5、$10、$20、$50、$100、$200 和 $500,也可以自定义金额,额度有效期 12 个月。$100 档足够覆盖 $68.40 的两遍示例,还留有重新生成的余量。在 /register 注册,在 /pricing 查看价格;规划一批书目之前,先把你自己的字符数放进费用计算器算一算。

更短的内容也用同一个接口、同一套计费方式:短内容的做法见视频配音 TTS,参数细节见 speech-2.8 模型页,对话模型见 MiniMax 模型总览。调用 GET /v1/models 可以确认你的 Key 能访问的确切模型 ID;如果估算和账单对不上,请发邮件至 support@yiduochan.com。

常见问题

把一本 300 页的书做成有声书要多少钱?

一本 300 页的书约 450,000 个字符,按刊例价在 speech-2.8-hd 上是 $45.00,在 speech-2.8-turbo 上是 $27.00。按当前 5% 的分组折扣,分别为 $42.75 和 $25.65。

单次请求有字符数上限吗?

官方没有公布单次请求的字符上限,所以先按章、再按段落切分,让每个请求保持较小。如果某个过大的片段返回错误,把它拆开重试即可——失败的调用不计费。

失败或重试的请求要付费吗?

不用。失败的请求从不计费,所以出错就重试的可续跑生成循环,唯一的代价只是多花些时间。

母带应该用什么输出格式?

母带用 wav 或 flac 生成,最后再从这份无损母带编码一次 mp3。格式不会影响价格,因为计费按输入字符计算。

可以克隆旁白的音色吗?

可以。/v1/minimax/voice_clone 接收一段样本,返回一个可以固定用作旁白的音色。整本书固定使用一个官方音色 ID,例如 English_expressive_narrator,并在每个请求里复用同一个 metadata 对象。

有免费额度可以测试吗?

有,但不多:新账号注册即送 $0.10 试用额度,约合一分钟的 hd 旁白。此后需要使用预付美元额度,按量付费,最低充值 $5,额度有效期 12 个月。

免费注册获取 API Key 查看完整模型定价