一批文本要用 dots.tts 合成为音频时,真正的麻烦通常不在合成本身,而在三件事:文件名怎么定、失败的那几条怎么找回来、重跑时会不会把已经成功的文件覆盖掉。比较稳妥的做法是先把批量任务拆成四层:一行一条的输入清单、固定输出目录与命名规则、带状态回写的失败记录、只重跑失败项的重跑入口。下面按这个顺序把可落地的做法写清楚,接口调用部分留成占位符,需要按你本地 dots.tts 的实际入口替换。
批量合成的关键不是脚本多聪明,而是让每条文本都有稳定 id、稳定输出名和可回写的 status。先建一行一条的输入清单,再固定目录与命名规则,失败项写入 status 与 error,重跑只按 status=failed 过滤,这样既不会漏跑,也不容易覆盖已成功的音频。具体命令和接口以你实际环境为准。
准备一行一条的批量输入清单
批量任务的起点不是脚本,而是清单。清单的作用是让每条待合成文本可追踪、可去重、可回写状态。CSV 和 JSONL 都可以,前者方便用表格工具检查,后者方便脚本逐行读取、减少转义问题。字段名以实际项目为准,下面是一份通用示例。
CSV 示例(第一行是表头):
id,text,output_name,status
0001,今天天气不错,适合出门散步,,pending
0002,请把这段内容合成为音频,,pending
0003,第三条测试文本,,pending
JSONL 示例(每行一个对象):
{"id":"0001","text":"今天天气不错,适合出门散步","output_name":"","status":"pending"}
{"id":"0002","text":"请把这段内容合成为音频","output_name":"","status":"pending"}
{"id":"0003","text":"第三条测试文本","output_name":"","status":"pending"}
几个需要先确定的点:id 在整个批次里唯一,通常用递增序号或短哈希;text 如果包含逗号、换行或引号,CSV 要按字段转义,处理不确定时优先用 JSONL;output_name 可以先留空,由脚本根据命名规则生成,也可以人工指定。生成清单后先做一次检查:id 是否有重复、text 是否为空、行数是否和预期一致。这一步在写脚本之前做,能省掉后面很多排查。
固定输出目录和文件命名规则
命名混乱和覆盖大多来自两件事:直接用文本当文件名、所有文件堆在同一层目录。建议把输出目录按批次分层,例如 output/<batch_id>/,批次内再按状态或日期分目录也可以,但不要反复改,改一次所有历史路径都会失效。
命名规则用「id + 文本摘要」是比较稳的写法,例如 0001_今天天气不错.wav。id 保证唯一,摘要方便人工翻看,但摘要不能太长,通常截取前若干字符即可。摘要里可能出现斜杠、冒号、星号、问号、换行等在不同文件系统上有问题的字符,需要统一替换,例如全部换为下划线,或者直接剔除。中文路径在部分环境下也会有编码问题,如果确认过本地工具链对中文路径不友好,可以改用拼音或纯 id 命名,把可读性交给清单文件维护。
一个可用的目录结构示例:
output/
batch_20240101_01/
0001_今天天气不错.wav
0002_请把这段内容合成为音频.wav
0003_第三条测试文本.wav
batch_20240101_02/
...
命名和目录规则一旦定下来,就写进脚本里,不要让每次运行时靠人工决定。这样重跑时生成的文件名和首次一致,便于判断文件是否已经被覆盖。
写一个通用批量调用脚本骨架
脚本骨架只需要串起三步:读清单、调用合成、回写状态。调用 dots.tts 的位置是占位符,具体是 Python 调用、命令行还是本地服务接口,需要按你的实际环境替换。下面是伪代码骨架,重点在流程,不在某一行具体写法。
import csv
def build_output_name(row):
# 根据 id 和文本摘要生成文件名,非法字符替换为下划线
return "%s_%s.wav" % (row["id"], safe_summary(row["text"]))
def synthesize(text, out_path):
# 占位符:在这里调用本地 dots.tts
# 例如调用你的 TTS 入口,把 text 合成为音频并写到 out_path
# 需要确认:输入文本参数、输出格式参数、采样率等
pass
def main():
rows = read_csv("input.csv")
for row in rows:
if row["status"] == "done":
continue
out_path = build_output_name(row)
try:
synthesize(row["text"], out_path)
row["status"] = "done"
except Exception as e:
row["status"] = "failed"
row["error"] = str(e)
write_back(row)
几点需要在接入时确认:synthesize 的输入输出签名、是否支持直接指定输出路径、合成失败抛出的异常类型、输出音频的格式和采样率。这些不同环境不一样,不要照搬别处的参数。write_back 要每处理一条就写一次,而不是全部跑完再统一写,否则中途中断时状态会丢。如果并发合成,写回状态需要加锁或写入独立的临时文件,避免多个进程同时改同一行。
记录失败原因并支持只重跑失败项
失败记录的价值在于两点:知道哪条没跑成、知道为什么。清单里给每条加 status 和 error 两个字段就够了。status 建议只用几个固定值:pending、done、failed。error 写失败原因,可以是异常信息,也可以是简短的分类描述,不要写太长。
重跑时不要重新跑整个批次,而是按 status 过滤。例如读取清单后只处理 status=failed 的行:
for row in rows:
if row["status"] != "failed":
continue
# 重跑该条
重跑前需要确认旧文件是否已存在。如果上次失败但写出了半个文件,直接覆盖可能留下不完整音频,建议先删掉同名文件再重跑,或者先写到临时文件、成功后再改名。重跑成功后把 status 改回 done,error 清空,避免下次重跑又重复处理。
用抽样试听和文件数量做结果验证
批量任务跑完,先不要急着交付。验证可以从三个动作开始:数文件、看时长、抽听。
- 文件数量:清单里 status=done 的条数,应该和输出目录里的音频文件数对得上。数量不一致时,先用命令行统计文件数,再和清单核对。
- 文件时长:用系统自带的媒体信息工具查看时长,如果发现某个文件普遍偏短甚至接近 0,通常是合成没写完整或文本为空。不要只看文件大小,静音文件也可能有正常大小。
- 抽样试听:从结果里随机抽 3 条,对照清单里的原文听一遍,确认语音内容、语速和格式没有明显问题。抽听不需要覆盖全部,但至少覆盖不同长度的文本。
验证用命令行做数量统计时,例如在输出目录执行 ls *.wav | wc -l,和清单里 done 的数量对比即可。如果本地工具链对时长读取不稳定,以能正常播放为准。这套流程的目标不是证明每一条都完美,而是让「哪条没跑、哪条失败、哪条要重跑」都有据可查,避免在下一次批量时重复排查同一个问题。