Subsync 是一个命令行工具,用于调整字幕文件的时间轴,使语音和字幕同步。 它可以:
- 按偏移时间,提前或推后显示字幕
- 按比例缩放,提前或推后显示字幕
- 在指定的时间范围内调整字幕时间
- 支持
.srt,.ass和.ssa字幕格式 - 用 C 语言写的过滤程序,简单高速
- 只用了普通 C 运行库,可以移植到所有的操作系统
- 命令行工具,很容易集成在脚本里面
我有一些古老的电视剧和网上找来的 .srt 字幕文件,很多是不同步的。
.srt 的结构很简单,按理说只要过滤出时间戳,稍微调整一下,再写回去就好了。
出乎意料的是,只找到了很庞大的 gui 程序,而不是我希望的轻量级命令行工具。
更麻烦的是,没找到能够缩放时间轴的工具 -- 有些视频是 30 帧/秒的,
而对应的字幕来自 25 帧/秒,刚开始视频是同步的,越往后偏差越大,
不能用简单的提前或推后时间戳解决这个问题。所以写了这个小程序填补这些空白。
您可以在 github 上抓取源码编译:
git clone https://github.com/xuminic/subsync.git
cd subsync
make
如果一切正常,应该能够编译出 subsync。 您可以把它移动到路径上的任何地方。
编译 Windows 程序可以用 MinGW64 或 Cygwin 在 Windows 系统上直接编译, 也可以在 Linux 系统上用 MinGW 交叉编译,例如
apt install mingw-w64
然后在 subsync 目录下
make allwin
make 会联网下载 libiconv-1.18.tar.gz 源程序,然后分别编译产生 Win32 和 Win64
的 subsync 可执行文件。
- 可以预先把
libiconv-1.18.tar.gz存到subsync目录下,实现离线编译 - Windows 程序长这个样子: subsync_i686.exe 和 subsync_x86_64.exe
- 在 github 的 Release 区可以直接下载编译好的 Windows 程序
- 没有 Windows 上的安装程序,请直接拷贝使用
-
不指定文件名的话,
subsync读取stdin并且输出到stdout,例如:subsync +12000 < source.ass > target.ass -
只要不是命令行选项的参数,都当作输入文件名。 输出文件名用
-w选项指定。例如:subsync +12000 -w target.ass source.ass该命令等同于上一条命令。
-
一条命令行上可以指定多个文件名。例如
subsync +12000 source1.ass source2.ass source3.ass但如此一来,所有输出内容全部汇总到 stdout。 用
-w选项也是一样:subsync +12000 -w target.ass source1.ass source2.ass source3.ass所有输出内容全部汇总到 target.ass 。
-
覆盖选项是
-o或--overwrite,修改时间轴后直接覆盖原文件:subsync +12000 -o source1.ass source2.ass source3.ass因此输出文件还是
source1.ass,source2.ass和source3.ass。 -
--overwrite和-o稍许不同,区别是前者产生一个备份文件。subsync +12000 --overwrite source1.ass source2.ass source3.ass修改后的内容覆盖
source1.ass,source2.ass和source3.ass, 同时产生备份文件source1.ass.bak,source2.ass.bak和source3.ass.bak。如果操作失误,可以从备份文件中回退。 -
偏移时间戳选项:
-/+OFFSET用于把字幕时间提前或延后。+增加时间戳,等于延后显示字幕。-减少时间戳,等于提前显示字幕。OFFSET是针对字幕时间的偏移量,其格式可以是- 毫秒,如
19700 srt时间戳格式,如0:0:10,190ass/ssa时间戳格式,如0:0:10.19- 时间戳算术格式,如
01:44:31,660-01:44:36,290 - 详见后面的 HOWTO: 偏移时间戳 节。
- 毫秒,如
-
缩放时间戳选项:
-SCALE用于按比例缩放时间戳。可以指定为- 浮点数,如
1.1988 - 预定义常数
N-P,等于1.1988。 - 预定义常数
P-N,等于0.83417。 - 预定义常数
N-C,等于1.25。 - 预定义常数
C-N,等于0.8。 - 预定义常数
P-C,等于1.04271。 - 预定义常数
C-P,等于0.95904。 - 该命令行选项不会和
-OFFSET选项混淆,缩放必须是浮点数。 - 详见后面的 HOWTO: 缩放时间戳 节
- 浮点数,如
-
删除一定范围的字幕
-c N:M或--chop N:MN:M是字幕的序列号,用于srt文件。 -
重排
srt文件的序列号用-r [NUM]或--reorder [NUM]用于整理
.srt文件内的字幕序列号,尤其经过分拆或合并字幕的时候, 往往造成乱序,虽然不影响使用,但是很难看。-r选项可以从 1 开始、 重排序列号。NUM是可选参数,如果设置了NUM, 重排的序列号 将从NUM开始。 -
指定时间戳范围
-s TIME或--span TIME如果需要修改一定范围内的时间戳,而不是整个字幕文件,则通过这个选项指定时间范围。 时间范围看上去是这个样子
-s 00:00:52,570 0:11:00,140。 如果只指定第一部分,如-s 00:00:52,570,则默认结束时间是文件结尾处。 -
指定输出文件名
-w FILENAME或--write FILENAME如果不指定输出文件名,默认输出到标准输出
stdout。 除非用-o或--overwrite覆盖原文件。
subsync 支持下面的时间格式:
-
整数,单位是毫秒,如
19700 -
srt时间戳格式,如0:0:10,190。冒号之间分别是: 小时,分钟,秒钟。可以依次省略,例如
1:20表示 1 分 20 秒。 逗号后面是毫秒,190就是 190 毫秒。 -
ass/ssa时间戳格式,如0:0:10.19。冒号之间分别是: 小时,分钟,秒钟。可以依次省略,例如
1:20表示 1 分 20 秒。 句号后面是百分秒, 百分之19秒就是 190 毫秒。 -
时间戳的算术差,用于偏移时间戳,例如
01:44:31,660-01:44:36,290。subsync将把前后两个时间戳转换为毫秒,并相减。其结果可以是正数,也可以是负数。- 如果结果为负数,则导致字幕时间提前
- 如果结果为正数,则导致字幕时间延后
- 时间戳格式可以是任何支持的格式,例如
01:44:31,660-12700 - 便于引用不同语言的字幕时间数据,获取差值
-
时间戳的比例表达式,用于缩放时间戳,例如
01:44:30,290/01:44:31,660。subsync将把前后两个时间戳转换为毫秒,并相除。其结果是一个浮点数。- 如果结果小于 1,则导致字幕间隔缩短
- 如果结果大于 1,则导致字幕间隔拉长
- 时间戳格式可以是任何支持的格式,例如
01:44:31,660/12700 - 用最后一条字幕的时间除以对应语音的时间,可以快速获得缩放比例
-
注意: 省略到秒分位时,秒和毫秒的区别。 例如
20表示 20 毫秒,而不是 20 秒。20,0或20.0才是 20 秒。 -
注意: 上例的
20.0会和时间戳缩放比例混淆。 而时间戳缩放比例优先于偏移量。例如命令行上subsync -20.0 -w target.ass source.ass将扩大
source.ass的时间戳间隔 20 倍,而不是提前 20 秒显示。 如果要提前 20 秒显示,要用下面任何一种格式:subsync -20,0 -w target.ass source.ass subsync -20000 -w target.ass source.ass subsync -0:20 -w target.ass source.ass subsync -0:20.0 -w target.ass source.ass
偏移时间戳是最常见操作,把字幕的时间戳加上或减去一个固定值,导致字幕被推迟或提前显示。 大多数错位字幕源于片头增加或去掉了一部分内容,偏移时间戳可以有效的纠正这个误差。
例如这个命令:
subsync +12000 < source.ass > target.ass
把 source.ass 里面所有的字幕时间戳增加了 12000 毫秒,导致每一条字幕推迟 12 秒显示。
这个参数 +12000 中的 + 指增加,对应的,- 指减少,因此
subsync -12000 < source.ass > target.ass
把 source.ass 里面所有的字幕时间戳减少了 12000 毫秒,导致每一条字幕提前 12 秒显示。
时间格式可以是整数,代表毫秒,也可以是常见的 HH:MM:SS 格式。
注意,毫秒位有两种不同格式, srt 的 HH:MM:SS,mmm 和 ass 的 HH:MM:SS.nn。
详见前面 时间格式 节。
为了简化计算, subsync 支持时间戳的算术差作为偏移参数。
例如,你可以在字幕文件中,随便选取一个错位字幕的时间,然后去视频中,
找到对应台词的起始时间,用台词时间减去错位的时间:
subsync +00:00:52,570-0:11:00,140 source.ass > target.ass
这个偏移参数 +00:00:52,570-0:11:00,140 中,
+号被忽略,换成-号也是一样的00:00:52,570是视频里面,对应台词的起始时间0:11:00,140是字幕文件里面,对应字幕的起始时间subsync计算两个时间戳的差值,代替手工计算- 计算公式是:
期待时间 - 错位时间 期待时间和错位时间可以是不同的时间格式。
某些字幕无法通过偏移时间戳实现同步,这类视频的特点是,视频起始处的字幕是同步的, 随着播放时间增长,字幕时间开始飘移,有的是字幕逐渐延迟,有的是字幕逐渐提前。 这类视频问题源自帧率,例如视频来自 NTSC 版本,而字幕来自 PAL 版本,那么就会 出现 30 vs 25 的差异,有些电影直接用了 24 帧的影院版本,无论是 PAL 字幕 还是 NTSC 字幕,都会出现失步现象。
解决方法是 subsync 的缩放时间戳功能。缩放时间戳通过对字幕的每一个时间戳乘上
一个系数,来补偿时间轴的飘移。例如
subsync -1.000955 source.ass > target.ass
如果参数大于 1 ,表示字幕时间逐渐延迟,如果参数小于 1 ,表示字幕时间逐渐提前。
缩放时间戳的参数开关和正负无关,因此, -1.000955 和 +1.000955 完全等效。
因为该参数是浮点数,因此通常不会和偏移时间戳参数混淆。 出现混淆的情况下,缩放时间戳参数的优先级高于偏移时间戳参数。
缩放根据这个公式计算: 期待时间 / 错位时间。
例如,有一部电影,刚开始字幕是同步的,但是影片最后一条字幕时间是 1:35:26,690,
而对应的台词出现在 01:35:32,160,所以字幕实际上在逐渐提前。因此:
- 期待时间是
01:35:32,160,等于 5732160 毫秒 - 错位时间是
1:35:26,690,等于 5726690 毫秒 - 缩放系数是
5732160 / 5726690 ~= 1.000955
为了简化计算, subsync 支持时间戳的除法算式作为缩放参数。以上面情况为例:
subsync -01:35:32,160/1:35:26,690 source.ass > target.ass
这个缩放参数 -01:35:32,160/1:35:26,690 中,
-号被忽略,换成+号也是一样的01:35:32,160是视频里面,对应台词的起始时间1:35:26,690是字幕文件里面,对应字幕的起始时间subsync计算两个时间戳的比例,代替手工计算- 计算公式是:
期待时间 / 错位时间 期待时间和错位时间可以是不同的时间格式。
subsync 支持简单的非线形编辑,用来处理局部字幕偏移或飘移。
如果应用得当,尤其是处理电视连续剧,一旦找到第一集的处理公式,
通常可以把同样的参数用在其他集上,可以大幅度提高操作效率。
subsync 用 -s 开关选取处理范围,命令行参数是:
-s start-time-stamp [end-time-stamp]
这里的 start-time-stamp 是起始时间, end-time-stamp 是结束时间。
结束时间是可选项,如果忽略,则处理到文件结尾。例如
subsync -s 0:01:15.00 1:23:34.00 -00:01:38,880-0:03:02.50 source.ass > target.ass
该命令要求从 1 分 15 秒开始,到 1 小时 23 分 34 秒,
这段区间的字幕提前 83.62 秒显示。
提前 83.62 秒来自 -00:01:38,880-0:03:02.5 的计算结果。
subsync 很容易集成在 shell 脚本里面,例如
for i in *.srt; do subsync +12000 $i > $i.new; done
也可以单独使用,通过 -o 选项覆盖原文件
subsync -o +12000 *.srt
或者用 --overwrite 覆盖原文件的同时保留一个备份:
subsync --overwrite +12000 *.srt
这里也有 here.
EVA3.3 Theatrical Edition 的视频长度是 1:32:52,但是字幕显示非常混乱。
打开字幕文件 00002.v1.11_FINAL.ass,发现最后一条字幕是
Dialogue: 0,1:45:42.51,1:45:46.48,Comment,,0,0,0,,♫Peace in time we've never had it so good\N安享和平 生活从未如此美好
字幕比视频多出来 10 分钟,推测应该是来自巨神兵故事的场景。
不管怎样,首先找到第一句台词,发生在 52 秒处,好在英语字幕正确,可以拿到精确时间
00:00:52,570 Tracking team, report current Eva unit positions.
对应的中文字幕是
Dialogue: 0,0:11:00.14,0:11:02.98,Default,,0,0,0,,追踪班 报告两机体现在的位置
这次我们手工操作,用 subsync 的帮助工具计算时间差:
$ subsync --help-sub 00:00:52,570 0:11:00.14
Time difference is -00:10:07,570 (-607570 ms)
先用偏移时间戳补偿这 10 分钟场景:
$ subsync -00:10:07,570 00002.v1.11_FINAL.ass > 001.ass
试了试这个字幕,前面同步了,后面开始飘移,果然帧率也需要调整。
回到视频本身,拉到片尾,找到最后一句台词,发生在
01:35:32,160 The Wunder streaks through the sky
对应的中文字幕是
Dialogue: 0,1:35:26.69,1:35:28.07,Default,,0,0,0,,划破天际的Wunder
我们还是手工操作,用 subsync 的帮助工具计算时间差率:
$ subsync --help-div 01:35:32,160 1:35:26.69
Time scale factor is 1.000955
这个比例接近 24 / 23.976,估计就是飘移原因了,我们用这个系数再加工一下
刚才产生的 001.ass 字幕:
$ subsync -1.000955 001.ass > 002.ass
视频连上 002.ass 字幕,这次显示正常了。
顺便验证了一下自动计算命令:
subsync +00:00:52,570-0:11:00,140 -01:35:32,160/1:35:26,690 00002.v1.11_FINAL.ass > 002.ass
验证结果显示,和手工操作一样。
这个剧有 39 集,字幕全部失步。视频是 mkv 格式,自带英语字幕。
我们先用 ffmepg 把英语字幕抽出来看看:
ffmpeg -i "Nadia Ep 01.mkv" -map 0:s:0 subs.srt
可见一开始字幕是同步的,subs.srt 中:
1
00:00:03,200 --> 00:00:05,900
<font face="InfoDispBoldTf" size="46" color="#fffde1"><i>Are you adventurers,</i></font>
对应的中文字幕:
Dialogue: 0,0:00:03.50,0:00:05.50,*Default,,0000,0000,0000,,你是一位冒险家吗
但是从第 18 条字幕开始失步:
18
00:01:09,630 --> 00:01:12,460 as the threat of a world war loomed ever closer.
19
00:01:38,880 --> 00:01:41,040
<font face="InfoDispBoldTf" size="46" color="#fffde1">Paris... Paris...</font>
对应的中文字幕:
Dialogue: 0,0:01:08.00,0:01:11.80,*Default,,0000,0000,0000,,但是人们生活在 即将来临的世界阴影中
Dialogue: 0,0:03:02.50,0:03:04.90,*Default,,0000,0000,0000,,巴黎…巴黎…
视频间隔约 30 秒,字幕间隔却有 2 分钟,很可能来自不同的片头主题曲。
用 subsync 局部修理一下试试:
subsync -s 0:01:15.00 -00:01:38,880-0:03:02.50 "Nadia Ep 01.ass" > 01.ass
结果显示正常,不需要缩放时间轴。但问题是第一集和后面的剧集不一样,有开场白。
该开场白在后面的剧集里不再出现,而中文字幕保留了这段场景。
因此从第 2 集开始, 用文本编辑器手工删除前 18 条字幕,然后用 subsync 修正时间轴:
mv 01.ass 01.bak
subsync -00:00:01,710-00:01:25,510 -o *.ass
mv 01.bak 01.ass
大概预览一下,觉得字幕提早了一秒,所以先用 subsync 调整一下:
$ subsync +1000 -o *.ass
仔细查看视频,发现前 10 分钟左右是同步的,但后面字幕提早了 6 秒左右。 反正定位后发现,10 分 38 秒处有一段过场黑屏镜头,刚好是 6 秒,于是
$ subsync -s 10:38 +6000 --overwrite 'Hataraku Saibou Black_-_01.ass'
再检查视频,这次完全同步了。
比较麻烦的地方是,虽然黑屏镜头时长都一样,但每集的位置不一样,只能一集一集找过去。 步骤是:
- 观看视频,进度条直接拖到中间
- 如果字幕同步就向后搜索,如果字幕失步就向前搜索
- 找到黑屏镜头开始地点,记住时间
- 用
subsync -s 黑屏起始时间 +6000调整
所以命令行上看起来是这样的:
$ subsync -s 10:38 +6000 --overwrite 'Hataraku Saibou Black_-_01.ass'
$ subsync -s 10:44 +6000 -o 'Hataraku Saibou Black_-_02.ass'
$ subsync -s 11:20 +6000 -o 'Hataraku Saibou Black_-_03.ass'
$ subsync -s 11:32 +6000 -o 'Hataraku Saibou Black_-_04.ass'
娜娜 的前 10 分钟字幕正常,后 10 分钟基本都要延迟 3 到 4 秒。
比较麻烦的两个地方是
- 没有过场镜头,似乎被直接删除了
- 延迟时间也不一样,从 3 到 5 秒都有可能。
这种情况下只能手工寻找字幕延迟的起始地点,并来回调整延迟时间。
例如第三集,12 分 57 秒的时候字幕还同步,13 分 17 秒的时候已经滞后了 2 秒多。 来回查看,发现 13 分 9 秒有一个镜头切换,确认这里是删除点。先提前 3 秒试试:
$ subsync -s 13:10 -3000 -o 'S01E03-Nana and Shoji, Love'\''s Whereabouts [030F3BD0].ass'
结果显示字幕有一点点超前,因此在同样位置,延后 0.5 秒试试:
$ subsync -s 13:10 +500 -o 'S01E03-Nana and Shoji, Love'\''s Whereabouts [030F3BD0].ass'
这次字幕看上去同步了。一般来说,字幕显示误差在 0.5 秒内基本可以接受。
前三集处理起来比较费劲,要来回调整,一旦摸到规律,处理起来就比较快。 实际操作情况是这样的:
$ subsync -s 8:20 -3500 -o S01E04-*.ass
$ subsync -s 10:43 -3500 -o S01E05-*.ass
$ subsync -s 11:13 -3500 -o S01E06-*.ass
$ subsync -s 11:13 -500 -o S01E06-*.ass
$ subsync -s 10:36 -4000 -o S01E07-*.ass
$ subsync -s 10:36 +500 -o S01E07-*.ass
$ subsync -s 10:36 +500 -o S01E07-*.ass
$ subsync -s 11:20 -3500 -o S01E08-*.ass
$ subsync -s 11:00 -3000 -o S01E09-*.ass
$ subsync -s 11:00 +500 -o S01E09-*.ass
$ subsync -s 12:16 -2500 -o S01E10-*.ass
$ subsync -s 13:04 -3500 -o S01E11-*.ass
$ subsync -s 13:04 +500 -o S01E11-*.ass
$ subsync -s 10:12 -3500 -o S01E12-*.ass
$ subsync -s 10:12 500 -o S01E12-*.ass
"再造人卡辛" 的特点是有十几秒不等长的片头,然后是OP,然后是正片。显然 BluRay 版本的OP和字幕版本不一致,如果把OP后的时间轴延迟1分30秒,正片就同步了。
问题是片头不等长,所以必须每个字幕文件单独处理。一个简单办法是逐个打开原字幕文件,检查前面的部分:
Dialogue: 0,0:01:42.93,0:01:51.20,staff,NTP,0000,0000,0000,,{\a6\fad(100,200)}翻譯 十六夜剎那 後期 秋月 暮葉 OPED歌詞協力 灰羽
Dialogue: 0,0:00:02.94,0:00:03.81,*Default,NTP,0000,0000,0000,,你是?
Dialogue: 0,0:00:04.56,0:00:05.98,*Default,NTP,0000,0000,0000,,我是卡辛
Dialogue: 0,0:00:08.12,0:00:11.20,*Default,NTP,0000,0000,0000,,露娜 殺了妳
Dialogue: 0,0:00:00.00,0:00:00.00,*Default,NTP,0000,0000,0000,,//-------------------OP-------------------
Dialogue: 0,0:01:01.98,0:01:06.00,*Default,NTP,0000,0000,0000,,從那天開始的 毀滅
Dialogue: 0,0:01:07.39,0:01:09.92,*Default,NTP,0000,0000,0000,,從殺死露娜的那一瞬間開始
OP前的最后一个对话时间从 0:00:08.12 开始到 0:00:11.20 结束,下一个对话从 0:01:01.98 开始,所以从 0:0:12 秒到 0:1:1 都是可选范围:
subsync -o -s 0:0:12 +0:1:30 S01E01*.ass
完整的处理过程看上去是这样的:
subsync -o -s 0:0:12 +0:1:30 S01E02*.ass
subsync -o -s 0:0:12 +0:1:30 S01E03*.ass
subsync -o -s 0:0:12 +0:1:30 S01E04*.ass
subsync -o -s 0:0:24 +0:1:30 S01E05*.ass