Skip to content

Latest commit

 

History

History
470 lines (407 loc) · 20.3 KB

File metadata and controls

470 lines (407 loc) · 20.3 KB

用 Subsync 调整字幕时间

Subsync 是一个命令行工具,用于调整字幕文件的时间轴,使语音和字幕同步。 它可以:

  • 按偏移时间,提前或推后显示字幕
  • 按比例缩放,提前或推后显示字幕
  • 在指定的时间范围内调整字幕时间
  • 支持 .srt, .ass.ssa 字幕格式
  • 用 C 语言写的过滤程序,简单高速
  • 只用了普通 C 运行库,可以移植到所有的操作系统
  • 命令行工具,很容易集成在脚本里面

为什么需要 Subsync

我有一些古老的电视剧和网上找来的 .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.asssource2.asssource3.ass

  • --overwrite-o 稍许不同,区别是前者产生一个备份文件。

    subsync +12000 --overwrite source1.ass source2.ass source3.ass
    

    修改后的内容覆盖 source1.asssource2.asssource3.ass, 同时产生备份文件 source1.ass.baksource2.ass.baksource3.ass.bak。如果操作失误,可以从备份文件中回退。

  • 偏移时间戳选项: -/+OFFSET 用于把字幕时间提前或延后。

    • + 增加时间戳,等于延后显示字幕。
    • - 减少时间戳,等于提前显示字幕。
    • OFFSET是针对字幕时间的偏移量,其格式可以是
      • 毫秒,如 19700
      • srt 时间戳格式,如 0:0:10,190
      • ass/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:M

    N: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,290subsync 将把前后两个时间戳转换为毫秒,并相减。其结果可以是正数,也可以是负数。

    • 如果结果为负数,则导致字幕时间提前
    • 如果结果为正数,则导致字幕时间延后
    • 时间戳格式可以是任何支持的格式,例如 01:44:31,660-12700
    • 便于引用不同语言的字幕时间数据,获取差值
  • 时间戳的比例表达式,用于缩放时间戳,例如 01:44:30,290/01:44:31,660subsync 将把前后两个时间戳转换为毫秒,并相除。其结果是一个浮点数。

    • 如果结果小于 1,则导致字幕间隔缩短
    • 如果结果大于 1,则导致字幕间隔拉长
    • 时间戳格式可以是任何支持的格式,例如 01:44:31,660/12700
    • 用最后一条字幕的时间除以对应语音的时间,可以快速获得缩放比例
  • 注意: 省略到秒分位时,秒和毫秒的区别。 例如 20 表示 20 毫秒,而不是 20 秒。20,020.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
    

HOWTO: 偏移时间戳

偏移时间戳是最常见操作,把字幕的时间戳加上或减去一个固定值,导致字幕被推迟或提前显示。 大多数错位字幕源于片头增加或去掉了一部分内容,偏移时间戳可以有效的纠正这个误差。

例如这个命令:

subsync +12000 < source.ass > target.ass

source.ass 里面所有的字幕时间戳增加了 12000 毫秒,导致每一条字幕推迟 12 秒显示。 这个参数 +12000 中的 + 指增加,对应的,- 指减少,因此

subsync -12000 < source.ass > target.ass

source.ass 里面所有的字幕时间戳减少了 12000 毫秒,导致每一条字幕提前 12 秒显示。

时间格式可以是整数,代表毫秒,也可以是常见的 HH:MM:SS 格式。 注意,毫秒位有两种不同格式, srtHH:MM:SS,mmmassHH: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 计算两个时间戳的差值,代替手工计算
  • 计算公式是: 期待时间 - 错位时间
  • 期待时间错位时间 可以是不同的时间格式。

HOWTO: 缩放时间戳

某些字幕无法通过偏移时间戳实现同步,这类视频的特点是,视频起始处的字幕是同步的, 随着播放时间增长,字幕时间开始飘移,有的是字幕逐渐延迟,有的是字幕逐渐提前。 这类视频问题源自帧率,例如视频来自 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 计算两个时间戳的比例,代替手工计算
  • 计算公式是: 期待时间 / 错位时间
  • 期待时间错位时间 可以是不同的时间格式。

HOWTO: 非线形编辑

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 的计算结果。

HOWTO: 批处理

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

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

工作细胞 Black

大概预览一下,觉得字幕提早了一秒,所以先用 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'

NaNa (2005)/娜娜/世上的另一个我

娜娜 的前 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

再造人卡辛 (Casshern Sins)

"再造人卡辛" 的特点是有十几秒不等长的片头,然后是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