dots.tts 读数字串音,先别急着换模型或改音素输入,而要用三类数字文本把问题定位到文本预处理层还是音素/发音输入层。通常文本规范化负责“切分与读法选择”,音素层负责“发音单元”:前者出问题的症状是整串连读成一个大数、分组错位、单位词和小数点丢失;后者出问题的症状是切分对了但单字发音怪、声调或重音不对。先做复现,再决定改哪一层。
判断顺序:先用金额、年份、电话号码三类文本各做一次复现,确认串音是集中在特定数字结构还是所有长数字;再只改文本层(写成汉字读法、按 3-4-4 分组)重合成同一句。若输出变正常,问题在文本规范化;若分组正确但逐字发音仍不对,才考虑音素或发音词典入口。若项目没有暴露音素接口,不要硬改,只记录文本层覆盖不到的边界,并在文本侧用回退写法绕过。
用金额、年份、电话号码各做一条复现文本
三类数字的结构不同,暴露的问题也不同,所以不要混在一段里合成,逐条单独跑,并记录送入 TTS 的字符串。
| 类型 | 输入文本 | 预期读法 | 记录点 |
|---|---|---|---|
| 金额 | 订单金额 12345.67 元 | 一万二千三百四十五点六七元 | 是否连读成大数、小数点是否读出、单位词是否保留 |
| 年份 | 2024年3月发布 | 二零二四年三月发布 | 是否读成“两千零二十四年”这类基数词读法 |
| 电话 | 联系电话 13800138000 | 一三八 零零一三 八零零零 或逐位 | 是否被当成一个整数连读、是否按 3-4-4 断 |
执行时把每条文本单独合成,不要连续放在一段里,因为上下文可能影响数字判定。下面的命令只是示意,请按你项目实际的调用入口替换:
# 逐条合成,观察串音是否只集中在某类数字结构上
for t in "订单金额12345.67元" "2024年3月发布" "联系电话13800138000"; do
echo "=== INPUT: $t"
dots-tts `--text` "$t" `--out` "/tmp/out_$(echo "$t" | md5sum | cut -c1-8).wav"
done如果项目有 debug、verbose 之类的开关,打开后把“规范化之后的文本”一起打出来。看不到这一步,就只能靠听回放判断,定位会慢很多。三类文本里只要有一类稳定复现、另一类正常,就说明问题集中在特定数字结构,而不是整套数字处理都坏了。
只改文本规范化再合成同一句
把 2024年 显式写成 二零二四年,把 13800138000 按 3-4-4 分组为 138 0013 8000,其他参数(音色、语速、采样率、模型版本)全部保持不变,再合成同一句。这一步只动文本,不动模型,能直接判断问题是否出在文本层没切分好。
import re
def normalize_for_tts(text: str) -> str:
# 年份:显式写成汉字,避免被当作基数词
text = re.sub(
r"(\d{4})年",
lambda m: "".join("零一二三四五六七八九"[int(c)] for c in m.group(1)) + "年",
text,
)
# 手机号:按 3-4-4 分组,插入空格作为停顿
text = re.sub(r"\b(\d{3})(\d{4})(\d{4})\b", r"\1 \2 \3", text)
# 金额:小数点改读作“点”,整数部分保留分组
text = re.sub(r"(\d+)\.(\d+)", r"\1点\2", text)
return text最小修改验证步骤可以固定成下面这几步,便于换句子重复:
- 取一条串音最明显的原句,先原样合成一次,留作对照。
- 只改数字写法(汉字读法或 3-4-4 分组),其他参数不动,再合成一次。
- 逐字听回放,记录哪一类数字变正常、哪一类没变。
- 对没变的那一类,换第二种文本写法(例如把空格换成逗号或逐位汉字)再试一次。
- 用一两句不含数字的文本做回归,确认改动没有影响普通句子的读法。
边界要留意:文本规范化改变的是送入模型的内容,同形数字可能被连带改掉,例如版本号 3.10.2、带区号的长串,改完要专门回归。如果三类都变正常,问题基本在文本层;如果年份正常而电话仍连读,说明分隔符没被识别,继续在文本层内换分组写法。
检查音素或发音词典入口是否存在
先在配置、README、启动参数或代码里找 phoneme、pinyin、g2p、lexicon、pronunciation dictionary 这类入口,确认项目是否允许绕过文本、直接给发音单元。
| 症状 | 更可能的层 | 动作 | 验证方式 | 边界 |
|---|---|---|---|---|
| 整串数字连读成一个大数 | 文本规范化 | 改写成汉字读法或分组写法 | 同一句重合成,听断点是否出现 | 同形数字可能被连带改写 |
| 分组正确但某几个字发音怪 | 音素/发音词典 | 用正确的拼音或音素序列做对照 | 音素输入正常而文本输入异常 | 需要项目确实暴露该入口 |
| 小数点、单位词丢失 | 文本规范化 | 把“.”改写成“点”,补回单位词 | 听单位词是否读出 | 不同语种读法需分别确认 |
| 只暴露文本入口 | 停留在文本层 | 穷举分组与汉字写法,记录无效项 | 列出仍失败的数字结构清单 | 不要虚构不存在的音素接口 |
如果项目确实暴露音素或拼音输入,就对同一句话做一次文本入口与音素入口的对照:音素序列按正确读法标注,比较两次输出。若音素入口正确、文本入口错误,重点应放回文本规范化或 G2P 词典,而不是继续换模型。如果只暴露文本入口,就不要硬编接口,只记录文本层试过哪些写法、哪些数字结构仍然不行,把这些当作边界交给维护者。另外,部分 TTS 支持在文本里插入逗号、空格或竖线来强制断句,这仍属于文本层手段,可以先试。
建立数字读法的优先级规则
为了后续内容生产有固定改法,建议按数字类型分别约定首选与回退写法,同一份内容里对同类数字保持同一种写法,避免同一篇文章里金额时而汉字时而阿拉伯数字,导致读法不一致。
| 数字类型 | 首选写法 | 回退写法 | 判断依据 |
|---|---|---|---|
| 金额 | 汉字读法加“点”,如一万二千三百四十五点六七元 | 阿拉伯数字加千分位与单位,如 12,345.67 元 | 小数点与单位词是否稳定读出 |
| 年份 | 逐位汉字,如二零二四年 | 四位数字加“年” | 是否被读成基数词 |
| 电话/手机号 | 逐位汉字,或 3-4-4 分组 | 逐位数字加空格或停顿符 | 是否连读成一个大整数 |
| 编号/版号/型号 | 逐位读,字母按字母读 | 分组加停顿符 | 是否保留字母读法 |
执行顺序保持“先文本层、后音素层”:文本层优先用显式汉字,因为它不依赖模型对分隔符的支持;分组写法作为次选,遇到模型不认空格时再退回汉字。只有确认项目暴露音素或发音词典入口时,才把个别的顽固读法下沉到那一层处理,其余情况都在文本侧解决,并把无效写法记录下来,作为下一轮调整的依据。