从硬字幕提取到 Whisper 双语字幕:我的字幕工作流(全流程)
下载完一个外语视频,最烦的就是没字幕,或者只有"烧"在画面上的硬字幕(hardsub)去不掉。这两年我搭了一套从"识别硬字幕"到"生成可编辑双语字幕"的流程,省了不少手工活。这篇文章把完整链路讲一遍,包含我踩过的坑、工具对比、以及对齐字幕时间轴的小技巧。
一、先分清两种字幕
- 软字幕:独立
.srt/.vtt文件,可开关、可翻译、可编辑,最理想。下载时优先用--write-subs(yt-dlp)拿。 - 硬字幕:直接画在每一帧画面上的文字,文件里没有独立文本,只能"看图识字"(OCR)。
- 外挂字幕轨道:mkv/mp4 里封装的软字幕,用
ffmpeg -i video.mkv能看到,提取出来即可。
很多人下完发现没软字幕,才想起去截硬字幕。所以第一步永远是:先试探源站有没有软字幕轨道,没有再走 OCR。
二、硬字幕提取:OCR 这一关
思路:ffmpeg 把视频按固定间隔截帧 → OCR 引擎识别每帧底部文字 → 按时间合并去重。
# 每秒截一帧
ffmpeg -i video.mp4 -vf "fps=1" frames/%03d.png
# 对每帧跑 OCR,只取画面底部 20% 区域(字幕通常在那)
python ocr_subs.py frames/ --region bottom
工具对比:
- tesseract:开源老牌,中文识别率一般,对手写体、艺术字体、半透明描边经常翻车。
- PaddleOCR(百度):对中文硬字幕友好很多,竖排、艺术字、模糊都能扛一部分,我主力用这个。
- EasyOCR:英文场景强,中文也不错,但模型大、慢。
坑点清单:
1. 截帧太密浪费:1fps 足够,2fps 让后续去重压力大。动态字幕(一句显示 3 秒)用 1fps 刚好每句截到 1~2 帧。
2. 去重是关键:相邻帧字幕往往一样,OCR 完要按文本相似度合并,否则生成几千条重复字幕。我用一个"文本相同且时间相邻就合并"的逻辑,量能压到 1/10。
3. 位置裁剪:字幕只占底部一条,全图 OCR 会把画面里的文字(比如路牌、标题)也吃进来干扰。先 crop 再识别,准确率直线上升。
4. 预处理提效:识别前把帧做二值化/放大对比度,能明显改善 OCR 效果,尤其半透明描边字幕。
三、没有硬字幕,直接生成:Whisper
如果源视频连硬字幕都没有,上语音识别。OpenAI 的 whisper 目前最省事:
whisper video.mp4 --model medium --language zh --output_format srt
模型选哪个:
- tiny/base:快但准度低,只适合草稿;
- small/medium:中文够用,性价比高;
- large-v3:最准但慢 3 倍、吃显存(GPU 才划算,CPU 跑一部电影能跑一小时)。
显存不够就 CPU 跑 medium,或者上 faster-whisper(用 CTranslate2 加速,同精度快 4 倍)。
Whisper 的断句问题:它输出的 srt 经常把一句话切成三四段,或把两句并在一段。我跑完都用个小脚本按标点合并短句、按静音点切长句。这步省不掉,否则字幕读着累。
四、做双语字幕
两条路线:
1. 翻译已有的源语言字幕:把 srt 丢给翻译 API(或本地模型),再拼回去,保留原时间戳只替换文本。
2. Whisper 直接出双语:Whisper 有 translate 任务能把非英语翻成英文,但中文→中英双语得自己两步:先识别中文,再翻译,最后合并成 原文\n译文 格式。
我常用的中文视频双语流程:
whisper video.mp4 --model medium --language zh --output_format srt # 中文 srt
# 翻译模型出英文(本地用 argos/ctranslate2,或调用 API)
# 合并成 bilingual.srt,每句:中文\nEnglish
五、时间轴对齐:ffsubsync
翻译会引入轻微时间偏移(尤其机器翻译句子变长),直接用会"字幕慢半拍"。用 ffsubsync 以音频为基准重新对齐字幕:
ffsubsync video.mp4 -i zh.srt -o zh_aligned.srt
ffsubsync video.mp4 -i en.srt -o en_aligned.srt
原理是用音频指纹把字幕时间戳对齐到语音,效果立竿见影。双语两条都要对齐,且用同一个视频做基准,保证中英文同步。
六、烧录还是封装
- 想在任何播放器都显示:用 ffmpeg
subtitles滤镜烧进画面(变硬字幕,不可逆,且中文字体要指定否则乱码):
bash ffmpeg -i video.mp4 -vf "subtitles=zh.srt:force_style='FontSize=24,FontName=Noto Sans CJK SC'" out_hardsub.mp4 - 想保留开关:封装进 mkv/mp4 作软字幕轨道:
bash ffmpeg -i video.mp4 -i zh.srt -i en.srt -c copy -map 0 -map 1 -map 2 out.mkv
我一般留软字幕,除非对方设备太老不认。
七、批量流水线
几百个视频时,我把上面步骤串成脚本:截帧 OCR / Whisper → 去重合并 → 翻译 → ffsubsync → 封装。用上一段说的"线程下载 + 进程池后处理"架构,OCR/Whisper 这种 CPU 活丢进程池,不阻塞下载。
八、常见坑速查
| 现象 | 原因 | 解法 |
|---|---|---|
| OCR 大量错字 | 没 crop、没二值化 | 先裁底部再预处理 |
| 字幕几千条重复 | 没去重 | 按时间相邻合并 |
| Whisper 断句奇葩 | 默认策略 | 后处理按标点合并 |
| 双语不同步 | 翻译偏移 | ffsubsync 对齐 |
| 烧录中文乱码 | 没指定字体 | force_style 设 CJK 字体 |
| 识别慢到哭 | 用了 large+CPU | 换 faster-whisper 或 medium |
九、一句总结
能拿软字幕绝不 OCR;硬字幕走 PaddleOCR + 去重;纯无字幕上 Whisper + 后处理;双语靠翻译 + ffsubsync 对齐。这套下来,一个两小时外语片从"没字幕"到"可开关中英双语",跑两三个命令的功夫,剩下的手工校对机器暂时替代不了——但已经省了九成力气。