上周帮人弄一部老剧的第二季,字幕是从别处下的外挂 srt。第一集看了十分钟,他说"这字幕有点意思",因为前几段对得挺准,到二十分钟开始差半句,四十分钟的时候字幕已经跑到画面前面十几秒了。他一直在用手里的播放器按"字幕延迟 +0.5 秒",按了七八次,越按越乱。
这个现象不是播放器的问题,也不是字幕文件坏了。它是两个数字对不上。
先分清是"整体偏"还是"越播越偏"
这两种毛病看起来都是"字幕不对",但成因完全不同,用错方法只会越调越糟。
整体偏:每一句都差差不多同样的时间。片头差两秒,片尾还是差两秒。这种是时间轴起点的问题,平移一下就好。
越播越偏:偏差随时间线性增长。片头刚好,十分钟差一点,四十分钟差一大截。这种是速率的问题,平移救不了,只能按比例把整条时间轴缩一遍。
判断方法很土但有用:在片头、片中、片尾各找一句台词,记下"字幕出现的时间"减"实际说话的瞬间"。如果三次分别是 0 秒、13 秒、26 秒,那就是速率;如果三次都是 2 秒左右,那就是起点。三处采样不到一分钟,比反复按 +0.5 秒靠谱得多。
差 1.042709 倍,是这件事的全部原因
电影常用 23.976 帧,电视和很多压制版是 25 帧。字幕是按其中一种速率做的,片源是另一种,于是每一秒都在累积误差。
比值就是 25 除以 23.976,等于 1.042709。换算成实际偏差:
- 1 分钟:2.6 秒
- 5 分钟:12.8 秒
- 10 分钟:25.6 秒
- 45 分钟:115.3 秒
也就是说一集 45 分钟的剧,字幕时间轴的 45:00 要挪到 46:55.315 才跟得上画面。差的那 1 分 55 秒,正好是很多人"看到一半突然发现字幕完全对不上、以为下错版本"的时刻。
反过来也成立,24 转 25 就除以这个数,片源比字幕快的时候偏差是往回收的。
整体偏移:-itsoffset 必须写在 -i 前面
平移最简单,ffmpeg 一条命令。我拿一份第一条字幕在 00:00:05,000 的文件试了 -itsoffset 2.4:
- 原时间轴:00:00:05,000 → 00:00:07,500
- 平移后:00:00:07,400 → 00:00:09,900
2.4 秒加得干净,没有四舍五入的误差。
但这个参数有个位置规矩。写成 -i 字幕.srt -itsoffset 2.4 会直接失败,报错原文是:
Option itsoffset (set the input ts offset) cannot be applied to output url shifted2.srt -- you are trying to apply an input option to an output file or vice versa. Move this option before the file it belongs to.
它把 -itsoffset 当成了输出参数。这句话其实已经把答案写清楚了:这个选项属于输入,要放在它作用的那个文件前面。
还有一个细节,往前平移(负数)的时候,第一条字幕如果本来就在第 1 秒,平移之后会变成负数,那一条会被丢掉。片头有台词的片子,宁可从片源里补一句,也别指望自动处理。
GBK 字幕读不出来,报错其实说得很明白
老字幕站下来的 srt 有不少是 GBK 编码。用 ffmpeg 读,会看到这样的输出:
- Invalid UTF-8 in decoded subtitles text; maybe missing -sub_charenc option
- Error decoding subtitles: Invalid data found when processing input
- Decode error rate 1 exceeds maximum 0.666667
关键在于 -sub_charenc 这个选项没给。加上 -sub_charenc GBK,同一份文件立刻正常解析,中文一个字不差。这里要放在 -i 前面,理由跟上面一样,它描述的是输入怎么解码。
顺带说一个反直觉的:时间轴里用点号写 00:00:05.000 而不是标准的逗号,ffmpeg 是能读的,输出时会自动规范成逗号。所以"格式看着怪"通常不是字幕对不上的主因,别在这上面浪费时间。
ASS 转 SRT,会留下一串花括号
这是把 ass 转成 srt 之后最常见的怪现象:字幕开头多出 {\an8} 这种字符,有的播放器直接把它当文字画在屏幕上。
我写了一份带顶部定位和斜体的 ass 去转,输出是这样:
<font face="微软雅黑" size="64">{\an8}顶上那行第二行带<i>斜体</i>结束</font>
\n 变成了真换行,\i1 变成了 <i>,这两条正常。但 {\an8} 原样留着——srt 没有覆写标签这个概念,它不认识花括号里的东西,只能当普通文本往下传。字体名和字号则被转成 <font> 标签塞进去,只有一部分播放器认。
想干净,转之前先把 {\...} 全部删掉,或者干脆别转:要保留排版就留 ass 挂外挂,srt 只用来放纯文本。
封进容器,mkv 保得住,mp4 保不住
同一份带标签的字幕,封进不同容器结果不一样。
封进 mkv 再取出来,标签基本还在:<font face="微软雅黑"><font size="64">{\an8}顶上那行,只是外面多套了一层 font。
封进 mp4 就不行了。mp4 的字幕轨只有 mov_text 一种选择,取出来是 <font size="64">顶上那行,{\an8} 被吃掉了,face 属性也没了。位置信息在转换里丢失,这就是为什么有人把 ass 塞进 mp4 之后,原本在顶部的注释字幕全跑到了屏幕中间。
结论很直接:要保住样式,用 mkv 装 ass;mp4 那条轨只适合纯文本。
还有一种不是漂移,是片长根本不一样
如果偏差不是均匀增长,而是"前半小时都对,中间某处开始突然错位,过一会儿又对上了",那就不是帧率问题,是片源版本不同——加长版、导演剪辑版、不同台的播出带,中间多或少了几分钟素材。
这种缩放和时间平移都救不了,因为差异是分段出现的。判断方式是看两个文件的时长差:差几十秒可能是片头片尾,差几分钟基本就是中间被动过。真碰上这种,只能去找跟片源同一版本的字幕,或者自己按段落重排。
我一般按这个顺序处理
- 三处采样,先判断是整体偏还是线性漂移
- 线性漂移,按 1.042709(或它的倒数)整体缩放时间轴
- 整体偏移,用
-itsoffset,注意写在-i前面、注意负偏移丢首条 - 分段跳变,别调了,去找对应版本
- 转码之前先确认编码,加
-sub_charenc,别等报错了再猜
这几步里最省事的其实是第一步。大部分人一发现对不上就开始按 +0.5 秒,按十次不如量三次——把三种偏移分清楚,后面全是算术。
你们遇到越播越偏的字幕,是直接换一份,还是自己拉时间轴重排?