短语音频切分器
输入一段「一条一条往下读」的音频 + 一份短语清单,输出「一条短语一个文件 + 编号 + zip 包」。
真正的难点不是切,而是判断哪两段语音是同一条:早读音频的条间停顿往往和句内停顿一样长(TTS 尤其如此,实测全部落在 0.45–0.51s),只靠停顿必然错位。所以本工具同时用三条线索:
- 停顿——把语音段切出来(自适应门限,不写死 -35dB);
- 遍数——估计每条被读了几遍(1/2/3 遍),多遍时只取第一遍;
- 时长结构——同一短语各遍时长几乎相同,用 DP 全局对齐到清单,允许一条被内部停顿拆成几段、也允许首尾有片头播报。
第 3 条是准确率的关键,不要为了省事退回「按停顿硬切」。
环境
PY="C:/Users/huawei/.workbuddy/binaries/python/envs/default/Scripts/python.exe"
"$PY" -m pip install imageio-ffmpeg python-docx openpyxl numpy # 首次或缺依赖时
ffmpeg 由 imageio-ffmpeg 自带,无需另装。
工作流
- 先预演,别直接切
bash "$PY" "<skill>/scripts/split_audio.py" --audio "课文.mp3" --doc "短语.docx" --dry-run
看三行数字是否讲得通:
-语音段 N → 归并后 U 个单元:U 应约等于条数 × 遍数;
-每条 r 遍:和实际朗读习惯一致(早读常见 2 遍);
- 每条的起止时间是否递增、无明显跳空。
条数与文档相差太远时,先怀疑文档解析(见「排错」),不要急着调音频参数。
-
正式切分
bash "$PY" "<skill>/scripts/split_audio.py" --audio "课文.mp3" --doc "短语.docx" \ -o outputs --name B1U2_Part2
产出:
outputs/B1U2_Part2_短语音频/ 49 个 mp3 + _index.csv outputs/B1U2_Part2_短语音频49条.zip 交付物(音频在根目录 + _index.csv)
文件名形如01_make a reservation.mp3,编号宽度随条数自动(≥2 位)。 -
读自检,不看会翻车
自检 静音文件 0 过短 0 内部停顿偏长 0 异常长 0
任何一项非 0 都要处理:
-静音/过短:切到空白处,通常是停顿门限或遍数估错;
-内部停顿偏长(>0.75s):可能把两条并成了一条;
-异常长(>2.6×中位数):几乎一定是并条,重新预演看--repeats/--merge。 -
交付:
present_files同时给出 zip 与索引 csv,并说明条数、时长范围、自检结果。
常用参数
| 需求 | 做法 |
|---|---|
| 每条读了几遍用户明确说了 | --repeats 2(不写则自动估计) |
| 自动估计不对 | 先 --dry-run 看单元数,再 --merge 0.3 手动给条间停顿阈值 |
| 只交付 zip 不要散文件 | 默认就是;散文件在 _短语音频/ 里 |
| 要 wav 不要 mp3 | --format wav |
| 取最后一遍而不是第一遍 | --take last |
| 前后留白多一点(跟读更顺) | --pad 0.15(默认 0.10) |
| 不需要 zip | --zip no |
| xlsx 指定的表/列 | --sheet "重点短语" --column "English" |
| 留一份机器可读结果 | --json report.json(含每条的起止秒数) |
排错
条数不对(解析出的条数和文档肉眼数的不一致)
- 早读材料常见「同一短语连写多行 + 空行」,脚本已做连续去重;
- 仍是两倍/三倍:说明文档里是不同的行(如英文行 + 中文行都保留了),用 --column 指定英文列;
- 少了几条:Unit/Part 标题行会被当标题丢掉;若某条短语本身以 Unit 开头,改用 txt 清单。
整体错位一条(前面对、从某条开始全偏)
- 多半是某条内部有长停顿(省略号、/ 分隔)被当成了两条,或某条被漏读;
- --dry-run 找到出错位置,确认音频里是否少读/多读了一条;DP 能容忍「一条被拆成几段」,但容不下「清单少一条」。
片头有播报 / 片尾有结束语
- 已自动丢弃(只允许丢首尾,不会动中间音频),预演会打印 丢弃首尾 N 段;N 明显偏大说明 --repeats 估计错了。
只读一遍但被估成两遍
- 症状:文件里是完整一条,但相邻两条内容对不上、内部停顿偏长 增多;
- 直接 --repeats 1。
判据(改代码前先看)
- 单元数 U、条数 N、遍数 r 三者关系:理想
U = r×N;相等时走整齐分组,不等时走 DP 对齐。 - DP 代价=「组内 r 段时长的一致性」为主,音节数先验只做次修正;不要反过来,音节估计对
sb/sth、缩写并不可靠。 - 首尾丢弃代价低(0.6)、中段不允许丢弃;这是「片头播报能被丢掉、而中间音频不会被吞」的原因。
- 切点用相邻单元的中点夹住,再加
--pad,保证不会串到隔壁。