同一段稿子,切成短句合成听着正常,整段一次性合成却出现拖长、吞字或停顿错位,通常不是 Luna-TTS 的合成质量问题,而是断句粒度和稿件结构没对上。判断该按短句喂还是整段喂,靠猜没有用:拿同一段对照文本分别按短句、整段各合成一遍,两遍都从头听到尾,再决定切分规则。
短句合成正常、整段合成发垮,多数情况下差异来自切分位置,而不是模型本身。做法是:用同一段同时含短句和长句的对照文本,先按句切分合成一遍,再整段不切分合成一遍,两遍都完整听一遍,逐句记录吞字、拖长和停顿位置;把差异归到切分、标点、文本本身三类,只为确认跟着切法走的那一类写断句规则,并用一段新文本复测。换文本后问题位置变了,规则就要放宽。
准备一份同时含短句和长句的对照文本
两遍合成必须面对同样的文字,差异才有可比性。对照文本不必长,150 到 300 字即可,但结构要覆盖:连续短句、带逗号的长句、冒号引出的列举、引号内的英文标识、数字与时间、句末标点齐全的收尾句。下面用 [停] 标出必须停顿的位置,也就是短句版要切开的地方。
今天下午三点,产品组把方案发到了群里。[停]
大家看完以后,主要有三个问题:[停]
第一,首页那句文案太长,读起来容易断气。[停]
第二,接口返回里的「status: pending」要不要念出来?[停]
第三,明早 9:30 的评审会改到线上还是线下?[停]
老王说先别动,等测试跑完再说。[停]
必须停顿的位置建议固定为这几处:句末的句号、问号、感叹号之后;冒号引出列举之后;列举项之间;引号包裹的英文标识符前后。逗号和顿号处不设硬停顿,留给合成时的自然处理,这样第二次整段合成才有观察价值。
把这段文字存成 paragraph.txt,两遍合成读同一个文件,中间不要改字、不要补标点。
按短句切分合成第一遍并逐句记录
先只打印切分结果,确认切点落在你标的位置,再开始合成。下面这段骨架里的 synthesize 一行换成你自己的 SDK 调用,参数名以实际 SDK 为准;音色、语速、采样率在整个测试里保持不变,否则后面无法归因。
text = open('paragraph.txt', encoding='utf-8').read()
segs = [s.strip() for s in text.split('[停]') if s.strip()]
for i, seg in enumerate(segs):
print(i, len(seg), seg)
# audio = tts.synthesize(text=seg, voice=VOICE, speed=SPEED)
# open(f'short_{i:02d}.wav', 'wb').write(audio)
逐句听完后按下表记录。不要只写「正常」或「不正常」,要写清听到的现象。判断标准可以先定三条:句首第一个字有没有被吃掉;句尾尾音是否被拖长到不自然;句与句之间的停顿长度是否接近正常说话的换气习惯。
| 序号 | 文本特征 | 听感问题 | 句间停顿是否自然 | 吞字或拖长 |
|---|---|---|---|---|
| 0 | 含时间数字的短句 | (按实际听感填) | (自然 / 偏长 / 偏短) | (有无) |
| 1 | 冒号引出列举 | (按实际听感填) | (自然 / 偏长 / 偏短) | (有无) |
| 2 | 引号内英文标识 | (按实际听感填) | (自然 / 偏长 / 偏短) | (有无) |
这一遍的目的只是拿到短句条件下的听感基线,不用在这一步调整任何参数。
按整段不切分合成第二遍并记录
把 paragraph.txt 整段一次性喂进去,其他设置与第一遍保持一致。这一步最常见的失误是顺手换了音色或语速,那样两遍差异就没有意义了。
text = open('paragraph.txt', encoding='utf-8').read()
# audio = tts.synthesize(text=text, voice=VOICE, speed=SPEED)
# open('whole.wav', 'wb').write(audio)
听完后逐项和短句版对照,按下面的清单列差异,并记下问题出现在第几段、第几句附近。
| 对照维度 | 短句版 | 整段版 | 问题出现的段落位置 |
|---|---|---|---|
| 句首字头是否被吃 | (填听感) | (填听感) | (第几段第几句) |
| 句尾尾音是否拖长 | (填听感) | (填听感) | (第几段第几句) |
| 句中停顿位置 | (填听感) | (填听感) | (第几段第几句) |
| 语速是否忽快忽慢 | (填听感) | (填听感) | (第几段第几句) |
| 数字与时间读法 | (填听感) | (填听感) | (第几段第几句) |
| 英文标识读法 | (填听感) | (填听感) | (第几段第几句) |
位置要写得具体,例如「第一条列举之后没有停顿,直接连读第二条」「引号里的英文标识连读成一串」。只写「后半段听起来差」没法用于下一步归因。
把两遍差异归到切分、标点、文本本身三类
归因靠一次只改一个变量:换切法不换文本,换标点不换切法,换写法不换切法。三类问题的判断依据和例子如下。
- 切分:同一段文字短句版正常、整段版出问题,且问题位置正好落在切点附近(句末、冒号后、列举项之间),改写标点后问题依旧。例:整段版在「第一,」之后直接连读下一句,短句版在同样位置的停顿听起来接近正常换气,这类差异值得写进断句规则。
- 标点:两遍都在同一个标点附近出问题,改动标点后听感变化。例:文本里写「status: pending」,半角冒号加空格在整段合成里可能被忽略或被读成奇怪的停顿,改成中文描述「status 为 pending」再合成,读法变清楚,就说明是标点写法的问题。
- 文本本身:两遍都不对,且和切点无关,改写写法后才正常。例:「9:30」在短句版和整段版读法都不符合预期,写成「九点三十分」后正常,这类属于文本规范化,不该写成断句规则。
三类都记完再定规则,不要把两遍里出现的所有问题都归给 Luna-TTS 的合成质量。
写出自己的断句规则并复测一次
把确认属于切分和标点的问题转成能机械执行的规则条目。可以先按下面几条起步,再按自己稿件的听感调整:
- 句末的句号、问号、感叹号一律作为硬切点。
- 冒号引出列举时,冒号后是硬切点,每个列举项单独成段。
- 逗号处不硬切;单句超过你设定的字数上限时(可以先按 25 到 35 字试,再按听感收紧),在语义完整的逗号处切一次。
- 引号内的英文标识符、字段名前后各留一个停顿,或提前改写成中文描述。
- 时间、金额、单位、多音字提前写成中文读法。
复测要用一段新文本,结构相似但措辞不同,同样包含短句、长句、列举、数字和英文标识,例如把上面对照文本改写成一段会议纪要或产品说明,保持同样的句式类型。把新文本按规则切一遍合成,再整段不切分合成一遍,两遍对照听,重点看原来的问题位置在新文本上是否还出现。
如果问题跟着规则消失,规则可以保留;如果问题换了位置出现,说明阈值或切点类型要改,例如列举项本身太长,就要在列举项内部再加一次切分;如果问题在两种切法下都在,回到第四步按文本本身处理。规则建议写在项目里的一个配置文件或注释块中,和稿件放在一起,下次换文本直接复用。