[!TIP] 受众:想在自己机器上跑语音识别 + 翻译、不想把片源传云端的人。 核心目标:讲清"能跑"和"能交付"之间差了哪些东西。 问题-方案映射

  • 同一句字幕复读 983 次 → -mc 0 禁掉跨窗口上下文
  • 子进程变孤儿、黑窗口误导报错 → Windows Job Object + CREATE_NO_WINDOW
  • 逐行翻译太慢 → 批量 15 行 + 编号对齐

我给视频做中英双语字幕已经有一阵子了,要求是全程本地跑:视频不出机器,识别用 whisper.cpp,翻译再丢给本地大模型。工具本身不难写,难的是它真的被拿去做正事之后冒出来的那些坑。这篇挑四个记录一下,它们有一个共同点:不真跑,永远发现不了。

1. 复读机故障:983 次重复和丢失的 32 分钟

最值得说的一段。一个约 44 分钟的真实文件,同一句字幕原样重复了 983 次,后面的内容全部丢失。

第一次我以为是偶发,只加了个后处理安全网 collapse_repetitions:连续 ≥4 条完全相同的字幕就折叠成 1 条。结果重新处理同一文件,在完全相同的位置 00:08:06 再次触发——这是确定性故障,不是运气问题。安全网只是把垃圾行折叠好看了,丢失的 32 分钟并没有找回来。

根因在 whisper 的实现:默认把前一个 30 秒窗口的识别文本带进下一个窗口作上下文(condition on previous text)。一句话一旦进上下文,就会自我强化,越来越确信"接下来还是这句",一路卡死到文件结束。这也解释了为什么单独截出来的 4 分钟 / 15 分钟短片段怎么都复现不了——它们的上下文是干净的。

修复是加 -mc 0--max-context 0),禁止跨窗口携带文本上下文。验证在同一个故障音频上做:重复计数 983 → 0,fallbacks 5 → 0,44 分钟内容完整识别。代价只有全片末尾约 68 秒丢失大小写,规模远小于原故障,暂未处理。collapse_repetitions 安全网我留着做纵深防御。

2. VAD:方向和最初设计是反的

最初设计默认开 VAD(语音活动检测),想着能滤掉静音、防幻听。实测同一段真实纪录片音频打脸:开 --vad 反而导致输出全部丢失大小写和标点,比如 “Jeremy Wade, explorer” 变成 “jeremy wade explorer”;不开则完全正常,fallbacks=0。

VAD 的防幻听收益只在纯音调 / 长静音场景验证过(比如无中生有的 “(phone ringing)"),对持续对白类内容弊大于利。所以 v1 默认

3. 子进程治理:孤儿进程和黑窗口

主程序用 windows_subsystem = "windows" 关掉自己的控制台后,引出一个连带问题:子进程(ffmpeg / whisper-cli)会被 Windows 单独弹一个新控制台窗口。用户手滑关掉它,等于给子进程发终止信号,界面就报"音频提取失败”——看着像文件损坏,其实只是窗口被关了。修复是两个 Command 都加 CREATE_NO_WINDOW,stdout/stderr 照旧走管道,不影响进度解析。

更严重的是孤儿进程。真实发生过一次:主程序意外退出(大概率是渲染层"设备丢失直接 panic",它和识别引擎抢同一块物理 GPU),whisper-cli 当场变孤儿,自己又跑了半个多小时,GPU 占着、临时文件没人清。remove_file 那种"退出前清理"的代码天然靠不住——进程崩溃的那一刻恰恰是这些代码来不及跑的时候。修复是用带 KILL_ON_JOB_CLOSE 的 Windows Job Object,子进程一 spawn 就绑进去,这是内核层保证,不依赖应用层代码执行。有一条真实拉起子进程的测试锁住这个机制。

4. 翻译:不是逐行请求,也不是一个模型翻所有语言

一集字幕几百条,逐行发 HTTP 太慢。改成每批 15 行一起发,提示词里显式编号、要求原样保留,回复按编号解析对齐。行数对不上(漏译 / 多译 / 合并)就对半拆开重试缩小范围,最终仍对不齐的单行原样保留原文,不阻断整个任务。

模型选择也有个坑:SakuraLLM 只训练了日→中方向,拿它翻英文效果很差(不在训练分布内)。所以用"语言 → 模型"注册表:日语走 Sakura,其余走通用 Qwen3-4B,按识别出的源语言自动选。

5. 收尾:识别对了不等于能看

识别正确只完成了一半,字幕还得能读。折行按显示宽度折,CJK / 全角计 2 列,默认上限 42 列(中文 21 字),断点优先级空格 > 标点之后 > 硬断,不在英文词中间断。实测四集共 3476 块:折行后 0 行超过 42 列,38.5% 的块被插了换行——这个数和"英文行超过 42 字符的占 38.2%“吻合。

还有个二级治理:时长 > 6s 且总宽度 > 2 倍上限的块按时间比例拆成两块。实测 63 块被拆(1.81%),0 行超上限,时间轴单调不重叠。过程中的意外发现:英文行没有标点是主要瓶颈(69 个合格块里 34 个英文行无标点),所以英文无标点时退到最近的词边界(空格)断开,仍不拆词。

小结

性能上顺手一提:RTX 3060 12G 上 large-v3 约 8 倍实时,45 分钟一集大约 5–7 分钟出字幕。这条流水线跑过的坑,几乎都是"以为是 A、实测是 B”:VAD 的方向、窗口被关的报错、复读机的确定性,还有个小的是 Windows 记事本存 UTF-8 会写 BOM,导致 srt 首块编号解析失败、第一个字幕块被跳过。能交付的软件,就是把这些都撞过一遍。