长文本配音流水线:从分段到批量到压缩的一条完整流程

给绘本配了一百多本之后,我把流程固定下来了。这篇写的是现在实际在跑的那条线,从 txt 进、mp3 出,中间尽量少人工干预。

一、为什么必须先切句再合成

很多人图省事,把整章文本塞进去一次生成。会出问题:

  1. 单次生成太长,容易在中间某个地方开始发糊、漏字,而且没法只重录那一句。
  2. 断句和停顿完全不可控,长句一口气读完,听着累。
  3. 出错之后重来的成本太高。

切成小段之后,好处很直接:哪句读错了就重生成哪句,改一个字不用重录全文。

二、分段策略

我的规则:

  • 优先按句号、问号、感叹号、分号切。
  • 单段控制在 80 到 150 字,超过 200 字一定要切。
  • 逗号多的长句,如果读起来会喘不上气,就在逗号处再切一刀。
  • 每段首尾留干净,别带引号和空行。
  • 每段单独存一个文件,文件名带序号,方便后面重录和拼接。

切完之后做两遍文本清洗,这两步能省掉大量返工:

  1. 数字和英文转中文写法。“2025年"写成"二零二五年”,“3.5米"写成"三点五米”,“iPhone"写成"苹果手机”。各家 TTS 对数字的处理规则不一样,统一转写是最稳的。
  2. 多音字用词表兜底。建一个自己的替换表,比如"重(chóng)新"“银行(háng)",合成前先跑一遍字符串替换。

三、批量队列

用 API 模式跑,写个简单的 Python 脚本,逻辑就三层:

import os, time
from concurrent.futures import ThreadPoolExecutor

def tts(text, out_path, retry=2):
    for i in range(retry + 1):
        try:
            r = call_api(text)          # 换成你自己的引擎调用
            if r.ok:
                save(r.audio, out_path)
                return True
        except Exception as e:
            print(text[:20], e)
        time.sleep(1)
    return False                        # 失败的写进 failed.txt

files = sorted(os.listdir("chunks"))
with ThreadPoolExecutor(max_workers=2) as ex:   # 并发别开太高,显存会炸
    for f, ok in zip(files, ex.map(lambda f: tts(read(f), out(f)), files)):
        log(f, ok)

几个要点:

  1. 并发数控制在 2 到 4。显存有限,开太高不会更快,只会 OOM。
  2. 失败要落盘。把失败的文本单独写进 failed.txt,跑完统一重跑,别让脚本中途崩掉前功尽弃。
  3. 日志记录每一段的耗时和状态,跑几百条的时候,没有日志你根本不知道卡在哪。
  4. 先跑 5 条试水,确认音色、语速、参数都对,再全量跑。这条能救你几小时。

四、音频压缩:码率和体积

本地生成出来一般是 wav,体积大得离谱,一分钟大约 10MB 上下,必须压。

我用 ffmpeg,常用的两套参数:

# 语音旁白,64k 单声道够用
ffmpeg -i in.wav -codec:a libmp3lame -b:a 64k -ac 1 -ar 44100 out.mp3

# 有背景音乐,或者想要更干净一点,用 96k
ffmpeg -i in.wav -codec:a libmp3lame -b:a 96k -ac 1 -ar 44100 out.mp3

经验值:

  • 纯人声旁白 64k 单声道基本听不出损失,体积大概是原 wav 的十分之一左右。
  • 带配乐或者要保留一点空间感,用 96k 到 128k
  • 采样率 44100 和 24000 对语音来说区别不大,儿童故事用 24000 能再省一截。
  • 批量转换写成一行循环就行,几百个文件也就几分钟。

顺手做一下响度归一化,多段音频接在一起时音量不会忽大忽小:

ffmpeg -i in.mp3 -af loudnorm=I=-16:TP=-1.5:LRA=11 out_norm.mp3

五、成品整理

  1. 目录结构:一本书一个文件夹,章节做子目录,文件名用 第01章_001.mp3 这种零填充格式,排序不会乱。
  2. 拼接:按序号拼成整章或整本,ffmpeg 用 concat 文件列表最省事。
  3. 导出 m4b:要做有声书就导出 m4b,能带章节标记,播放器里可以直接跳章。
  4. manifest 表:写一个 CSV,记录每段的文件名、对应原文、生成时间、参数。哪天要改一句,直接查表定位,不用重新翻。

六、质检

这一步不能省,也不能全做。我的做法是抽 10%,重点听三类:

  • 错读漏读(多音字、数字最容易出问题)
  • 爆音和削顶(音量突然变大)
  • 句尾崩掉(尾音拉长或突然断掉)

再扫一遍文件时长,异常短的文件基本都是生成失败。

实际账:2000 字的文本,纯生成大概几分钟到十几分钟,加上清洗和质检,半小时能出成品。比人工录快一个数量级,而且声音永远稳定。

返回AI语音合成