Luna-TTS 长文本合成到中间断句奇怪 / 是分段方式还是标点问题?

文章导读
先把问题定在“分段方式”还是“标点写法”上,最省事的做法是做一次单变量对照:同一段几百字文本,分别按整段、按句号、按固定字数切分后各合成一遍,把每条音频里听着别扭的停顿记成时间点,再对回原文。异常停顿如果出现在切分边界附近,多半是分段方式的问题;如果落在句子中间、改一处标点后就消失,通常是标点写法的问题。
📋 目录
  1. 壹 先在音频里标出所有异常停顿的位置
  2. 贰 把同一段文字按句号、按自然段、按固定字数切三份
  3. 叁 用标点最小改动复测一次
  4. 肆 按对比结果定下采用的切分方式
  5. 伍 把切分规则写成合成前的固定检查项
A A

先把问题定在“分段方式”还是“标点写法”上,最省事的做法是做一次单变量对照:同一段几百字文本,分别按整段、按句号、按固定字数切分后各合成一遍,把每条音频里听着别扭的停顿记成时间点,再对回原文。异常停顿如果出现在切分边界附近,多半是分段方式的问题;如果落在句子中间、改一处标点后就消失,通常是标点写法的问题。

异常停顿落在切分边界,通常说明是分段粒度的问题;落在句子中间且改一处标点后消失,通常是标点写法的问题。先按句、按段、按固定字数各合成一遍对照,再对可疑句只改一处标点复测,据此确定每段几句一断并写成合成前的检查项。若两种切分都出现同样的停顿,需要回头确认文本编码、空白字符和引擎版本等环境因素,不要直接归因于标点。

先在音频里标出所有异常停顿的位置

主观的“听着奇怪”没法用来对比,先把它变成时间点。用播放器时间轴或用静音检测把停顿位置打印出来,如果 Luna-TTS 的日志里能看到每次请求的文本片段和对应输出文件,也一并对照。ffmpeg 的静音检测可以先给出候选点,阈值和时长需要结合自己的音频音量确认:

ffmpeg -i out.wav -af silencedetect=noise=-35dB:d=0.4 -f null -

把检测结果和听感合并成三列表格,第四列用来判断是否落在切分边界,便于后面归因:

时间点前后文字停顿长短是否落在切分边界
00:12.4“请确认。” / “如果……”明显停顿,接近一秒是,正好是按句切分的位置
00:27.8“价格是 3” / “,500 元”短促但可听出断开否,落在数字中间
00:41.0“先这样” / “然后……”偏长,前后都无标点是,按固定字数硬切的位置

记录时把正常句末停顿和异常停顿分开标注:正常句末停顿一般在句号、问号之后,位置稳定;异常停顿往往出现在没有句末标点的地方,或者前后文字读起来并不该断。

把同一段文字按句号、按自然段、按固定字数切三份

这一步只改切分粒度,文本一个字都不改。可以先写个小脚本,一次生成三个待合成文件:

import re
text = open('long.txt', encoding='utf-8').read()
by_sentence = re.split(r'(?<=[。!?;])', text)
by_para = [p.strip() for p in text.split('\n') if p.strip()]
by_len = [text[i:i+80] for i in range(0, len(text), 80)]
for name, segs in (('sentence', by_sentence), ('para', by_para), ('len80', by_len)):
    with open(name + '.txt', 'w', encoding='utf-8') as f:
        f.write('\n'.join(s for s in segs if s.strip()))

80 字只是测试用的一个取值,可以按实际听感换成 60 或 120。合成顺序建议固定为:按句、按自然段、按固定字数,每次只替换切分结果,其他配置不动,产出三个音频后逐一对照第一张表。

  • 按句切:如果异常停顿消失或明显变少,问题偏向切分粒度过大。
  • 按自然段切:如果异常停顿跟着段落边界走,说明段落本身过长。
  • 按固定字数切:如果出现了新的人工断点,说明硬切位置落到词或数字中间。

听感差异要写下来,不能只在脑子里比较:谁在哪个时间点断了、断在什么字上、是变好还是换了个位置。

Luna-TTS 长文本合成到中间断句奇怪 / 是分段方式还是标点问题?

用标点最小改动复测一次

如果异常停顿落在句子中间,取那一句单独合成,每次只改一处标点,改前改后各听一遍。重点覆盖三类写法:

  • 行尾标点:句末漏写句号,或用了半角句点。比如“……先到这里”改成“……先到这里。”,看停顿是否回到句尾。
  • 引号内标点:他说:“先这样”。 与 他说:“先这样。” 对比,看停顿是落在引号内还是引号外。
  • 连续逗号:不是这样,,是那样 改成 不是这样,是那样,看引擎是否把重复标点读成额外停顿。

复测时把改动前后的文本和音频文件成对命名保存,例如 quote_before.wav 和 quote_after.wav,避免之后分不清哪条是改过的。若同一句只改标点后停顿消失,结论是标点问题;若改标点后停顿仍在同一位置,则更可能是文本长度或切分导致的,需要回到上一步按更细的粒度再切。

按对比结果定下采用的切分方式

结论要落到能执行的规则,而不是“感觉好一点”。通常可以这样定:

  • 断句阈值:以按句切分为基线,单句超过一定长度(例如 50 到 80 字,取自己复测中表现稳定的区间)时,再在该句的逗号处二次切分,不直接按字数硬切。
  • 段首段尾约定:每段段首不留空白字符,段尾补齐句末标点;不把上一段的尾巴和下一段的开头合并成一段;段与段之间不共享引号或括号。
  • 不适合切分的位置:引号内的整句话、括号内的补充说明、数字和单位之间(如 3,500 元)、小数点、英文缩写和版本号。这些位置一旦被切开,容易出现读错或异常停顿。

如果按段落切分与按句切分表现接近,优先选按句切分:片段短、定位问题容易,代价是请求次数变多,需要结合自己的调用成本和超时限制确认是否可接受。

把切分规则写成合成前的固定检查项

把上面定下来的规则固化成脚本或手动清单,下次不用重试一遍:

  1. 文本是否已统一为全角句末标点,句末有无漏标点。
  2. 是否存在连续逗号、连续空格、行尾多余空白。
  3. 是否已排除数字、单位、缩写被切断的情况。
  4. 每段长度是否在阈值内,超长句是否已在逗号处二次切分。
  5. 每段的起止字符位置是否记录在清单里,便于出问题时回查对应的原始文本。

合成后抽听不用整段逐字检查,建议每段固定抽三处:首句、末句、以及含引号或数字的那一句,重点确认这四处边界衔接是否自然;全部段落合成完再整篇连听一遍,看段落衔接处有没有新的异常停顿。把抽听结果补回第一张记录表,规则就能持续修正而不是每次重新试。