ffmpeg で動画加工を頑張った話
はじめに
Supership 株式会社のKotaro です。
本エントリーは、動画加工機能を実装する上での、ffmpeg コマンドの組み立て方とその工夫点をまとめています。
また、エントリーの内容は、社内勉強会で発表した内容を外部向けに再編したものです。
動画加工機能について
今回述べている「動画加工機能」ですが、具体的には動画の一部分をカット/静止する機能のことを指します。また、UI設計上以下の特徴を持ちます。
・カット/静止は別々のUIが存在し、単独で操作される
・カット/静止位置は複数指定できる
また、以下の理由から、動画加工に用いるffmpeg の実行回数は極力少なくするように実装しています。
・動画加工にかかる処理時間を短くするため
・動画の劣化を防ぐため
コマンド組み立てにおける問題点
ffmpeg コマンドはオプションも多数存在し、様々な動画加工に対応できます。しかし、今回の要件のように、動画の複数箇所をカット/静止させるには、一般的なオプションを使用すると、その箇所分だけコマンドを発行する必要がありました。
例えば、上の画像のように動画のカットを行う場合、以下のようなコマンドを生成する必要があります。
1. 元の動画から① の部分(動画A)を残すコマンドを生成
2. 元の動画から② の部分(動画B)を残すコマンドを生成
3. 元の動画から③ の部分(動画C)を残すコマンドを生成
4. 動画A~C を結合するコマンドを生成
この方法だと、動画加工に時間がかかり劣化も回避できません。
そのため、上の1~4 のコマンドをまとめられないか調査を進めました。
解決方法
-filter_complex オプションを使用しその引数への記述の工夫することで、動画加工をコマンド1つに完結させることができました。
-filter_complex オプションは、その引数に動画の加工内容を文字列として渡すことで処理を実行させます。文字列で渡す部分の内容が肝要で、ここの記述次第で様々な表現ができます。
文字列の記述内容は、動画のカットを例にすると、大きく2つの内容を記述する必要があります。
1. 元の動画の① ~ ③ の部分を指定する
2. ① ~ ③ を結合する
「1.」を記述すると以下のようなコマンドになります。
細かく説明していきます。
まず1行目の [0:v]trim=0:5,setpts=PTS-STARTPTS[v0]; を要素分解すると、以下の要素に分けられます。
[0:v] -> ストリーム(元になる動画)の指定
・「0」が-iオプションで指定したファイルの番号(= sample.mp4)
・「v」が映像ファイル(「a」だと音声ファイル)
trim=0:5,setpts=PTS-STARTPTS -> 処理内容
・0s-5s の範囲を指定
・ 秒数はコマンドを具体的に表すために任意の数値を入れています。
[v0] -> ラベルの指定
・「v0」というラベル名を付ける(ラベル名は任意)
つまり、文章にすると以下の通りです。
-iオプションで0番目に指定されたファイル(sample.mp4)の映像ファイルの、0s-5s の範囲をv0と名付ける。
他の行も同一で、2, 3行目までは、他の部分の映像ファイルにラベル名を付けています。4~6行目は、① ~ ③ の部分の音声ファイルにラベル名を付けています。
続いて「2.」の記述です。追加すると以下のようなコマンドになります。
追加された、[v0][a0][v1][a1][v2][v2]concat=n=3:v=1:a=1 を要素分解して説明します。
[v0][a0][v1][a1][v2][v2] -> ラベル
・前処理でつけたラベルを連結順に並べる([映像][音声]...)
concat=n=3:v=1:a=1 -> 連結処理
・n: 連結するファイルの組み合わせの数(映像+音声で1カウント)
・v: 出力する映像ストリーム数
・a: 出力する音声ストリーム数
つまり、文章にすると
ラベルの順で、映像/音声ファイルを連結し、1つの映像/音声ストリーム(=sample_2.mp4)として出力する。
という風な意味になります。
以上のコマンドで、動画加工をコマンド1つに完結することができました 🎉
具体的なコマンド例
機能:カット
・元となる動画:sample.mp4/20s
・元動画から5-15s の部分を切り取り、残りを結合する
λ ffmpeg -y -i sample.mp4 -filter_complex \
"[0:v]trim=0:5,setpts=PTS-STARTPTS[v0];
[0:v]trim=15,setpts=PTS-STARTPTS[v1];
[0:a]atrim=0:5,asetpts=PTS-STARTPTS[a0];
[0:a]atrim=15,asetpts=PTS-STARTPTS[a1];
[v0][a0][v1][a1]concat=n=2:v=1:a=1" \
sample2.mp4
機能:静止
・元となる動画:sample.mp4/20s
・元動画の10s 地点を5s 間静止させる
### 前処理として、10s 地点で5s 間静止した映像ファイルと、5s 間無音の音声ファイルを作成
λ ffmpeg -y -ss 10 -t 10 -i sample.mp4 -frames:v 1 static_image.jpg
λ ffmpeg -y -loop 1 -i static_image.jpg -t 5 -r 30 static_movie.mp4
λ ffmpeg -y -ar 48000 -t 5 -f s16le -ac 2 -i /dev/zero -aq 4 silent.mp3
### 上で作ったファイル等を元に静止動画を作成
λ ffmpeg -y -i sample.mp4 -i static_movie.mp4 -i silent.mp3 -filter_complex \
"[0:v]trim=0:10,setpts=PTS-STARTPTS[v0];
[0:v]trim=10,setpts=PTS-STARTPTS[v1];
[1:v]setpts=PTS-STARTPTS[v2]; <- 5s 間静止した動画
[0:a]atrim=0:10,asetpts=PTS-STARTPTS[a0];
[0:a]atrim=10,asetpts=PTS-STARTPTS[a1];
[2:a]asetpts=PTS-STARTPTS[a2]; <- 5s 間無音の音声
[v0][a0][v2][a2][v1][a1]concat=n=3:v=1:a=1" \
sample2.mp4
まとめ
-filter_complex に渡す文字列の表現はもっと多彩で、さらに応用が効くと思います。ただ、日本語のドキュメントが少ない/見つけてもバージョンの違い?等で動かない場合があるので、その辺は大変でした。