起因当然就是, UP 主以前还没当过 UP 主呢, 这会儿想剪个 MAD 啦.
经过是, 写了个简单脚本用 avconv + mencoder 剪出没有任何特效, 只是纯粹拼接原始片段的视频.
教训是 Linux 从来不缺乏图形界面软件, 只是... 缺乏比对应的命令行软件更优秀的图形界面软件.
这次实践, 基本原理是用 avconv (不要吐槽名字啊, 其实这软件很健全的) 对源视频进行分割, 然后用 mencoder 串联起来, 再配上音乐.
Ubuntu 上安装这些东西以及对应的转码器
apt-get install libav-tools libavcodec-extra-53 mencoder
(Windows 上也有 avconv 和 mencoder 和, 理论上也能这么搞)
直接运用上述工具, 跟一般 Linux 命令行软件一样的问题就是, 参数略复杂, 比如用 mencoder 合并视频
mencoder -ovc copy -o OUTPUT_FILE.mp4 INPUT_FILE INPUT_FILE INPUT_FILE ...
而用 avconv 从一个视频源文件中提取一段内容, 转压成 640x360 分辨率的 MP4 文件, 去掉声音, 要这么干
avconv -ss 00:00:10 -i INPUT_VIDEO_FILE -t 00:00:30 -vf "scale=640:360" -f mp4 -vcodec libx264 -an OUTPUT.mp4
上面 -ss
参数后面是开始时间, -t
参数后是剪出的片段长度. (因为视频流压缩的问题, 这两个时间都可能不精确)
对于制 MAD 来说一次要剪出上百个片段, 这么一个个手打再多的爱最后也是死路一条. 所以得换个方式, 选取一些相对容易的工具; 当然如果没有, 就造一个.
这个轮子构想的出发点是尽量简化对视频剪裁参数的编写. 因为制作 MAD 往往是从多个视频中混合剪出片段, 而且顺序不确定, 所以填写文件名还是太麻烦, 可以这么考虑
将所有源视频放在一个目录下, 文件名前面编号 (如果是同一部动画, 那么就是集数了)
由一个文件给出剪取顺序, 信息包括视频顺序编号, 场景开始时间, 场景长度; 为了让这个文件更容易维护, 也允许文件中有注释
剩下的参数只需要指定输入的音频文件路径和输出的最终文件路径即可
中间弄出来的片段都放临时文件目录好了
基于以上指导思想, 实作如下的 Python 函数
import collections
def parse_seq(sequence):
Segment = collections.namedtuple('Segment', 'epnum,start,duration,subt')
result = []
total_dur = 0
for line in sequence:
line = line.strip()
if len(line) == 0 or line[0] == '#':
continue
parts = filter(None, line.split(' '))
if len(parts) == 3:
epnum, start, dur = parts
subt = None
else:
epnum, start, dur, subt = parts
start_parts = start.split(':')
if len(start_parts) == 2:
start_time = int(start_parts[0]) * 60 + float(start_parts[1])
else:
start_time = float(start)
dur = float(dur)
total_dur += dur
result.append(Segment(int(epnum), start_time, dur, subt))
return result, total_dur
if __name__ == '__main__':
with open('input_sequence.txt', 'r') as f:
segments, total_dur = parse_seq(f.readlines())
print segments
print total_dur
这个简单的解析函数会忽略空行和那些以 # 开头 (无论前面是否有空白字符) 的行; 接着尝试从一行内容里依次提取出视频编号, 开始时间, 时长和字幕 (字幕可以不存在, 实际试过之后发现 avconv 加字幕真是坑, 不过可以作调试用). 其中开始时间可以浮点数 (表示秒) 也可以是 "分钟:秒.毫秒" 的格式, 而视频编号和时长只能是数. 函数会统计一个总时长, 不过只作为参考.
另外, 目前视频编号从 1 开始而不是 0, 因为一般一部动画还是从第 1 集开始计的.
文件内容举例
# 井号开头: 这是一行注释
# 从第 2 个视频的第 10 分 30 秒处剪出 5 秒
2 10:30 5
# 从第 4 个视频的第 20 秒处剪出 2 秒, 配上字幕 ABC
4 00:20 2 ABC
分析完这个之后就来拼参数了. 虽然 Python 也有 libav 的封装, 不过最省事的方案在 UP 主看来还是利用 Subprocess
直接调命令行
import subprocess
if __name__ == '__main__':
with open('input_sequence.txt', 'r') as f:
segments, total_dur = parse_seq(f.readlines())
seg = segments[0]
p = subprocess.Popen([
'avconv',
'-ss', str(seg.start),
'-i', source_files[seg.epnum - 1],
'-t', str(seg.duration),
'-vf', 'scale=640:360',
'-f', 'mp4',
'-vcodec', 'libx264',
'-an',
'output_file.mp4'])
p.wait()
主干思想就是这样了. 剩下的琐事包括, 如何组织临时文件, 记录它们, 然后转交给 mencoder 进行合并. 这些就不详细说了, 代码完全体参见这个 gist.
使用方法: 下载代码随便摆在什么目录, 参数如
python mad.py 源视频目录 音频文件路径 输出文件路径 序文件路径
至于工程化的方法, 目前很粗糙但还蛮实用的: 用 audacity 或者其它什么软件把音频剪成片段, 对着音乐一段一段的剪辑场景; 每 30 秒左右合并一个大的看看效果.
最后的结果嘛, 在这里. 用于拼接 MAD 的序文件就是同一个 gist 里的 nichijou_x_airman.txt 了, 将日常 TVA (不含 OAD 的第 0 话) 放一起作为输入目录, 作为 BGM 的「エアーマンが倒せない」就自己找个吧.
比如这样的目录结构
% ls videos/nichijou
01.mkv 04.mkv 07.mkv 10.mkv 13.mkv 16.mkv 19.mkv 22.mkv 25.mkv
02.mkv 05.mkv 08.mkv 11.mkv 14.mkv 17.mkv 20.mkv 23.mkv 26.mkv
03.mkv 06.mkv 09.mkv 12.mkv 15.mkv 18.mkv 21.mkv 24.mkv
% ls music/airman_ga_taosenai.mp3
music/airman_ga_taosenai.mp3
% head documents/nichijou_x_airman.txt
20 18:55.50 0.8
20 18:20.00 2.7
20 18:56.00 0.4
20 20:00.00 1.8
20 19:05.80 1.2
20 19:15.00 1.4
20 19:20.80 1.7
20 19:26.20 1.5
% rm -f videos/nichijou_x_airman.mp4 # 确认删除的这个文件不恰好是重要的东西或节操
% python mad.py videos/nichijou music/airman_ga_taosenai.mp3 \
videos/nichijou_x_airman.mp4 \
documents/nichijou_x_airman.txt