本文首发于我的Hexo博客:https://likianta.coding.me/2018/0213-224002/
注意:以下内容基于帧率为24FPS的视频讲解。
如图所示是Aegisub的工作界面:
其中“开始/结束时间”是0:00:00.00
的形式,小数点后有两位,代表的是多少毫秒,比如0:00:06.81
就是6秒+810毫秒。
而本文中的另一个主角是“实际时间”,其形式为0:00:00.000
,小数点后有三位,与前者的区别可不光是更加精确了,更重要的是同一时间下它们显示的时间值是不一样的,其换算关系为:
开始时间 = 实际时间 - 半帧的时间 再应用“去尾法”得到
举几个例子:
(注:24fps的视频,即 24帧 = 1s,换算得 1帧 = 1000/24ms,半帧 = 500/24ms ≈ 21ms)
实际时间 | 对应的“开始时间” | 备注 |
---|---|---|
0:00:06.840 |
0:00:06.81 |
840 - 21 = 819ms ≈ 0.81s |
0:00:06.881 |
0:00:06.86 |
881 - 21 = 860ms ≈ 0.86s |
0:00:09.009 |
0:00:08.98 |
1009 - 21 = 988ms ≈ 0.98s |
0:00:00.000 |
0:00:00.00 |
零秒处是特例 |
Aegisub的实际时间有一个特点,在24fps(每秒有0-23帧)的视频中,每秒的第0帧的毫秒数会递增“1ms”,如下表所示:
第几秒(总帧数) | 当前秒的第0帧的实际时间 | 对应的“开始时间” | 备注 |
---|---|---|---|
0 (0) | 0:00:00.000 |
0:00:00.00 |
零秒处是特例 |
1 (24) | 0:00:01.001 |
0:00:00.98 |
1001 - 21 = 980ms ≈ 0.98s |
2 (48) | 0:00:02.002 |
0:00:01.98 |
1002 - 21 = 981ms ≈ 0.98s |
3 (72) | 0:00:03.003 |
0:00:02.98 |
1003 - 21 = 982ms ≈ 0.98s |
4 (96) | 0:00:04.004 |
0:00:03.98 |
1004 - 21 = 983ms ≈ 0.98s |
5 (120) | 0:00:05.005 |
0:00:04.98 |
1005 - 21 = 984ms ≈ 0.98s |
6 (144) | 0:00:06.006 |
0:00:05.98 |
1006 - 21 = 985ms ≈ 0.98s |
… | … | … | |
30 (720) | 0:00:30.030 |
0:00:30.00 |
30 - 21 = 9ms ≈ 0.00s |
60 (1440) | 0:01:00.060 |
0:01:00.03 |
60 - 21 = 39ms ≈ 0.03s |
… | … | … |
从上表可以发现一个规律,每增加一秒,其实际时间的第0帧的时间值会累加1ms,我们把这种现象叫做“溢出”,那么可以推算当溢出量累积到1000ms时(也就是在16分40秒的时候),那么会出现“加一秒”的情况(由于手头没有这么长的视频,所以暂时还没有实际验证过对不对)。
那么了解了这么一个规律,究竟有什么用呢?下面的实战是一个很好的例子:
Aegisub的时间码格式为时:分:秒.毫米
;
Adobe After Effects(后面简称“AE”)的时间码格式为时:分:秒:帧
。
现在我们手上有一个从Aegisub保存下来的.ass
文件,用记事本打开里面的内容为:
[Script Info]
; Script generated by Aegisub 3.2.2
; http://www.aegisub.org/
Title:
Original Script:
Original Translation:
Original Editing:
Original Timing:
Original Script Checking:
ScriptType: v4.00+
Timer: 100.0000
WrapStyle: 0
ScaledBorderAndShadow: Yes
[Aegisub Project Garbage]
Audio File: 开花火腿芝士面包 合成视频.mp4
Video File: 开花火腿芝士面包 合成视频.mp4
Video AR Mode: 4
Video AR Value: 1.777778
Video Zoom Percent: 0.500000
Active Line: 2
Video Position: 1440
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Arial,20,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,2,2,2,10,10,10,1
Style: 封面白粗,Heiti SC,30,&H00FFFFFF,&H00FFFFFF,&H00FFFFFF,&H00FFFFFF,0,0,0,0,100,100,0,0,1,0,0,2,10,10,10,1
Style: 封面黑粗,Heiti SC,30,&H00000000,&H00000000,&H00000000,&H00000000,0,0,0,0,100,100,0,0,1,0.6,0,2,10,10,10,1
Style: 白色字体,Heiti SC,32,&H00000001,&H00FFFFFF,&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,0.4,0.5,2,10,10,6,1
Style: 水印,Heiti SC,16,&H00FFFFFF,&H00FFFFFF,&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,0.1,0.1,7,10,10,10,1
Style: 注释,Heiti SC,13,&H00FFFFFF,&H00FFFFFF,&H00FFFFFF,&H00000000,0,0,0,0,100,100,0,0,1,0.1,0.1,8,10,10,10,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:04.69,0:00:06.23,白色字体,,0,0,0,,酵母
Dialogue: 0,0:00:06.23,0:00:08.36,白色字体,,0,0,0,,橄榄油
Comment: 0,0:01:00.03,0:01:00.08,白色字体,,0,0,0,,橄榄油
Dialogue: 0,0:00:08.36,0:00:10.07,白色字体,,0,0,0,,水
Dialogue: 0,0:00:10.07,0:00:12.32,白色字体,,0,0,0,,牛奶
Dialogue: 0,0:00:12.32,0:00:14.45,白色字体,,0,0,0,,糖
Dialogue: 0,0:00:17.45,0:00:19.20,白色字体,,0,0,0,,面粉
Dialogue: 0,0:00:19.20,0:00:20.91,白色字体,,0,0,0,,盐
Dialogue: 0,0:00:28.00,0:00:31.63,白色字体,,0,0,0,,发酵30分钟
Dialogue: 0,0:00:31.63,0:00:34.18,白色字体,,0,0,0,,巴西奶油奶酪
Dialogue: 0,0:00:34.18,0:00:37.39,白色字体,,0,0,0,,马苏里拉奶酪
Dialogue: 0,0:00:37.39,0:00:40.68,白色字体,,0,0,0,,火腿
Dialogue: 0,0:01:17.30,0:01:20.05,白色字体,,0,0,0,,蛋黄液
Dialogue: 0,0:01:25.60,0:01:27.90,白色字体,,0,0,0,,烘焙25分钟/180℃
假设我们通过某种方法删去了里面不重要的内容,并提取出来了每条字幕的“开始时间”、“结束时间”以及“文本内容”(依次如图中的红、黄、栏框注部分):
下面我们尝试着把开始时间转换成AE能够识别的时间码格式:
以第一条字幕为例,其开始时间为0:00:04.69
,那么根据之前我们发现的规律,可求得其实际时间在0:00:04.711
到0:00:04.720
之间。
我们取0:00:04.711
作为计算结果(与实际时间的最大误差在9ms以内,是可以接受的),先将它换算为总秒数:
0:00:04.711
-> 4711(s)
然后利用AE表达式(以下为JavaScript代码):
var t = 4711;
var FPS = 24;
var timecode = timeToCurrentFormat(t, FPS); // 将总秒数根据当前视频帧率转换为AE时间码格式
return timecode; // 得到`0:00:04:17`,即表示第四秒第17帧处
这样子就转换完成了……其实并没有。
通过在测试中观察实际画面发现,Aegisub的帧数和AE的帧数总是相差一帧,因此我们还要再减去一帧才是AE真正的时间码:
var timecode_aegisub = `0:00:04.69`;
var frame_duration = 0.042;
var half_frame_duration = 0.021;
var t = 4690 + half_frame_duration - frame_duration;
var FPS = 24;
var timecode_ae = timeToCurrentFormat(t, FPS); // 将总秒数根据当前视频帧率转换为AE时间码格式
return timecode_ae; // 得到`0:00:04:16`,即表示第四秒第16帧处