ストリーム・フィルターチェーン・フィルターグラフ・リンクラベルという概念に注目して調べてみた。
ファイル情報を見る
- ファイルに含まれるビデオとオーディオの形式を確認してみる。
$ ffmpeg -i sample.flvffmpeg version 2.5.4 Copyright (c) 2000-2015 the FFmpeg developers ...中略... Input #0, flv, from 'sample.flv: Metadata: starttime : 0 totalduration : 1155 totaldatarate : 346 bytelength : 50005082 canseekontime : true sourcedata : B0AFCF105HH1424147114695748 purl : pmsg : httphostheader : r18---sn-oguesnl7.googlevideo.com Duration: 00:19:14.65, start: 0.000000, bitrate: 346 kb/s Stream #0:0: Video: flv1, yuv420p, 426x240, 280 kb/s, 29.97 fps, 29.97 tbr, 1k tbn, 1k tbcStream #0:1: Audio: mp3, 22050 Hz, stereo, s16p, 65 kb/s ...中略... At least one output file must be specified
ストリームという概念
ストリームという概念は重要である!
- ファイルは-iオプションで指定した順に0、1、2...と番号管理されている。
- 同様に、ファイル中のストリームも順に0、1、2...と番号管理されている。
- つまり、Stream #0:0とは、ファイル0番のストリーム0番という意味。
- 一般的に、ストリーム0番にはビデオが記録される。
- ストリーム1番以降に、一つあるいは複数のオーディオが記録される。
- ffmpegでは、処理対象をストリーム単位で指定できる。
- Stream #0:0は、コマンドオプション中で[0:0]のように表記できる。
変換する
- .flvファイルを.mp4ファイルに変換する。
- ファイルフォーマットは、出力ファイルの拡張子によって判別される。
$ ffmpeg -i sample.flv sample.mp4
変換しないで取り出す
- .flvファイルからmp3オーディオだけ取り出す。
- -acodec copyを指定することで、.flvファイル中のmp3をそのままコピーするのだ。(再変換なし・劣化なし)
- -acodec copyを指定しないと、再変換する手間と時間がかかり、多少劣化すると思う。(再変換あり・劣化あり)
- codec = コーデック = データをエンコード(符号化・圧縮化)する時の処理方法のこと。
$ ffmpeg -i sample.flv -acodec copy sample.mp3
- ちなみに、.flvファイル中にmp3オーディオが含まれているからコピーできるのだ。
- 含まれているのがaacオーディオだったりすると、エラーが発生してコピーできない。
- もしaacオーディオが含まれている場合は、以下のように指定すればコピーできる。
$ ffmpeg -i sample.flv -acodec copy sample.aac
- さらに、h264ビデオとaacオーディオが含まれているなら、劣化なしでmp4ファイルに変換できる。
- h264とaacがmp4の要件に合っているからコピーできる。
- 以下の3通りの書き方は、すべて同じ処理を指定している。
# ビデオストリームとオーディオストリームをコピーする $ ffmpeg -i sample.flv -vcodec copy -acodec copy sample.mp4 # -codecオプション=-vcodecと-acodecをまとめて指定する $ ffmpeg -i sample.flv -codec copy sample.mp4 # -cオプション=-codecの短縮形 $ ffmpeg -i sample.flv -c copy sample.mp4
指定した範囲を切り出す
- ビデオやオーディオの好みの部分を指定して、切り出せる。
- 再生時刻1分10秒から2分30秒まで(スタート時刻とエンド時刻を指定)
$ ffmpeg -i sample.mp3 -ss1:10-to2:30 sample_110_to_230.mp3
- 再生時刻1分10秒から、1分20秒間を切り出す。(スタート時刻と期間を指定)
$ ffmpeg -i sample.mp3 -ss1:10-t1:20 sample_110_t_120.mp3
- 上記はどちらも同じ部分を切り出し、1分20秒間のmp3オーディオとなる。
複数のファイルを連結する
- sample_1.mp4とsample_2.mp4を連結して、sample_1_2.mp4に出力する。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"concat=n=2:v=1:a=1" sample_1_2.mp4
- -filter_complex オプションで連結の指定
- concat=
- n=連結するファイル数(デフォルト: 2)
- v=ビデオ ストリーム数(デフォルト: 1)
- a=オーディオ ストリーム数(デフォルト: 0)
- concat=
- concatの引数を何も指定しないと、デフォルト設定によって以下のように解釈される。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"concat" sample_1_2.mp4 # 以下のように解釈される $ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"concat=n=2:v=1:a=0" sample_1_2.mp4
- デフォルトはa=0なので、二つ目のファイルのオーディオは連結されないのだ。
- 連結とは、一つ目のファイルにconcatの処理をした二つ目のファイルを接続する処理なので、
- 一つ目のファイルのオーディオはそのまま残るが、二つ目のファイルのオーディオは削除される。
- また、-iオプションのファイル数より、nの値が小さくてもコマンドは正常に終了するが、nで指定したファイル数しか連結されない。
- 以下の例では、sample_1_2_3.mp4には、sample_1.mp4とsample_2.mp4しか連結されない。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -i sample_3.mp4 -filter_complex"concat=n=2:v=1:a=1" sample_1_2_3.mp4
- すべて連結するには、n=3を指定するのだ。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -i sample_3.mp4 -filter_complex"concat=n=3:v=1:a=1" sample_1_2_3.mp4
よって、正しく連結できたかどうかは、動画の最後まで確認する必要がある!
フェードイン・フェードアウト
ビデオ
- ビデオの先頭から、30フレーム使ってフェードインさせる。
$ ffmpeg -i sample.mp4 -vf"fade=in:0:30" sample_fade-in.mp4
- ビデオの300フレームから、30フレーム使ってフェードアウトさせる。
$ ffmpeg -i sample.mp4 -vf"fade=out:300:30" sample_fade-out.mp4
- ビデオの先頭から、1秒使ってフェードインさせる。
$ ffmpeg -i sample.mp4 -vf"fade=t=in:st=0:d=1" sample_fade-in.mp4
- ビデオの時刻10秒から、1秒使ってフェードアウトさせる。
$ ffmpeg -i sample.mp4 -vf"fade=t=out:st=10:d=1" sample_fade-out.mp4
オーディオ
- オーディオの先頭から、1秒使ってフェードインさせる。
$ ffmpeg -i sample.m4a -af"afade=t=in:ss=0:d=1" sample_fade-in.m4a # あるいは $ ffmpeg -i sample.m4a -af"afade=t=in:st=0:d=1" sample_fade-in.m4a
- afadeのみ、ss=Nを指定できる。
- ssの場合、Nに何を指定しても、常に先頭の指定となる。
- オーディオの60秒から、1秒使ってフェードアウトさせる。
$ ffmpeg -i sample.m4a -af"afade=t=out:st=60:d=1" sample_fade-out.m4a
- 仮にss=60と指定しても、いきなりフェードアウトが始まってしまう...。
フィルターチェーン・フィルターグラフ・リンクラベルによる連続技
フィルターチェーン
- フィルターチェーンを使って、二つのフィルター(フェードイン・フェードアウト)を適用する。
- フィルターチェーンとは、複数のフィルターをカンマ,で区切って指定する方法。
- 一つ目のフィルター処理、その結果に対して二つ目のフィルター処理...のように適用される。
- 以下の処理で、フェードインで始まり、フェードアウトで終わるビデオになる。
# -vf(video filters)で指定した場合
$ ffmpeg -i sample.mp4 -vf "fade=in:0:30,fade=out:300:30" sample_fade-in_fade-out.mp4
# -filter_complexで指定した場合
$ ffmpeg -i sample.mp4 -filter_complex "fade=in:0:30,fade=out:300:30" sample_fade-in_fade-out.mp4
フィルターグラフとリンクラベル
- フィルターグラフとリンクラベルを使っても、フィルターチェーンと同様の処理を行える。
- フィルターグラフとは、フィルターやフィルターチェーンをセミコロン;で区切って指定する方法。
- フィルターグラフとフィルターチェーンは似ているので、よく混乱するのだけど、決定的な違いがある。
- フィルターチェーンは、リンクラベルがなくても処理結果を次のフィルターに引き継ぐが...
$ ffmpeg -i sample.mp4 -vf "fade=in:0:30,fade=out:300:30" sample_fade-in_fade-out.mp4
- フィルターグラフは、リンクラベルなしでは処理結果を次のフィルターに引き継がない。
$ ffmpeg -i sample.mp4 -vf "fade=in:0:30;fade=out:300:30" sample_fade-in_fade-out.mp4
...エラー発生...
Simple filtergraph 'fade=in:0:30;fade=out:300:30' does not have exactly one input and output.
Error opening filters!
- フィルターグラフでは、必ずリンクラベルを設定しておく必要がある。
$ ffmpeg -i sample_1.mp4 -vf "fade=in:0:30[a];[a]fade=out:300:30" sample_fade-in_fade-out.mp4
- [a]という表記がリンクラベル。(英数文字とアンダースコア_を使って任意の名前を命名する)
- fade=in:0:30のフィルターの結果に[a]というリンクラベルを設定して、
- [a]が指し示す結果に対してfade=out:300:30フィルターを適用している。
- たった二つのフィルターの連続では、フィルターグラフの利用価値をあまり見出せないが...
- フィルターグラフには、フィルターチェーンの区切りとなって、グループ化する役割がある。
- 例えば、sample_1.mp4とsample_2.mp4を連結してから、フェードイン・フェードアウトする場合...
- ビデオだけなら、シンプルなフィルターチェーンのみで処理できる。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"concat=n=2:v=1:a=0, fade=in:0:30, fade=out:800:30" sample_1_2.mp4
- ビデオとオーディオを扱うと、concatの出力は二つのストリームになるので、必ず二つのリンクラベルが必要になる。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"concat=n=2:v=1:a=1[v][a], [v]fade=in:0:30, fade=out:800:30"-map[a] sample_1_2.mp4
- -map [a]は何をしている?
- フィルター出力がリンクラベルの指定で終了している場合、 -mapにそのリンクラベルを指定して、出力ファイルに書き込む必要がある。
- 最後のフィルター出力でリンクラベルを指定しなければ、そのフィルター出力は自動的に出力ファイルに書き込まれる。
- -map [a]は何をしている?
- フィルターグラフを使えば、もう少しシンプルに表現できる。
- フィルターグラフの場合、ビデオは[v]にリンクされ、リンクなしのオーディオは出力ファイルに書き込まれる。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"concat=n=2:v=1:a=1[v]; [v]fade=in:0:30, fade=out:800:30" sample_1_2.mp4
- 一方、フィルターチェーンを使うと、リンクなしのオーディオは次のフィルターにリンクしてしまう。
- だから二つのリンクを受け取ってfadeフィルターがエラーになってしまうのだ!
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"concat=n=2:v=1:a=1[v], [v]fade=in:0:30, fade=out:800:30" sample_1_2.mp4 ...エラー発生...
リンクラベルを使うなら、フィルターグラフで区切った方が幸せになれそう。
- フィルターの順序を逆にして、sample_1.mp4をフェードイン・sample_2.mp4をフェードアウトしてから、連結することもできる。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"[0:0]fade=in:0:30[a];[1:0]fade=out:300:30[b];[a][0:1][b][1:1]concat=n=2:v=1:a=1" sample_1_2.mp4
- [0:0]、[1:0]などの表記については、前項のストリームという概念を参照。
オプション指定 | 意味 |
---|---|
[0:0]fade=in:0:30[a]; | sample_1.mp4のストリーム0番をフェードインして、リンクラベル[a]を設定する。 |
[1:0]fade=out:300:30[b]; | sample_2.mp4のストリーム0番をフェードアウトして、リンクラベル[b]を設定する。 |
[a][0:1][b][1:1]concat=n=2:v=1:a=1 | [a]・sample_1.mp4のストリーム1番・[b]・sample_2.mp4のストリーム1番を連結する |
難解なffmpegのオプション指定が少しずつ見えてきた!
- フィルターチェーン・フィルターグラフ・リンクラベルが分かってくると、フィルター処理の流れが見える。
- そしたら後は、個々のフィルターの動作を調べれるだけで、ffmpegが何をしているのか理解できるのだ。
- フィルターの動作をすべて覚える必要なんてない。
- 必要になったらマニュアルを見て調べればいいのだ。
合成
- ffmpegのフィルター指定は、まるでプログラミングコードである。
- フィルターチェーン・フィルターグラフ・リンクラベルを理解すると、そのコードの流れが見えてくる。
- コードの流れが見えてくると、自分が何をわからず、何を調べればいいのか、理解できるようになる。
こうなると、俄然面白くなってくる!
- 以下をサンプル映像とさせていただき、いろいろなオプション指定を試してみた。
- サンプル映像
- sample_1.mp4 = https://www.youtube.com/watch?v=uXR1Ni5WHxI
- sample_2.mp4 = https://www.youtube.com/watch?v=759NraOrv8k
左右に並べる
ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"[0:0]pad=2*iw[a];[a][1:0]overlay=w" overlay.mp4
- padは、ビデオの表示領域を設定する。
- iwは、入力側のビデオの横幅(input width)
- 2*iwを設定すると、横幅が2倍のビデオになる。
- 拡大された領域は、背景が黒くなる。
- overlayは、二つのビデオを重ね合わせる。
- [a][b]overlayとした場合、ビデオ[a]にビデオ[b]を重ねる。(ビデオ[b]が上になる)
- [a][b]overlay=x:yで、ビデオ[a]の(x,y)座標を起点にビデオ[b]を配置する。
- w・hは、ビデオ[b]の幅・高さ解釈される。
- W・Hは、ビデオ[a]の幅・高さと解釈される。
- 指定なしは、0と解釈される。
- つまり、sample_1.mp4の横幅を2倍にして、(640,0)の座標を起点に、sample_2.mp4を重ね合わせるのだ。
- sample_1.mp4とsample_2.mp4のビデオサイズは、640x360。
上下に並べる
ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"[0:0]pad=iw:2*ih[a];[a][1:0]overlay=0:h" overlay.mp4
- padとoverlayの指定を上下に配置する設定に変更してみた。
ピクチャー in ピクチャー
ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"[1:0]scale=iw/2:ih/2[red];[0:0][red]overlay"-map1:1 overlay.mp4
- scaleは、入力されたビデオサイズを拡大・縮小する。
- scale=iw/2:ih/2は、ビデオの幅と高さを半分に縮小するのだ。
- map 1:1は、sample_2.mp4のオーディオも合成している。
- つまり、sample_1.mp4に、幅と高さを半分に縮小したsample_2.mp4を重ねているのだ。
- overlayの座標指定なしなので、(0,0)を起点として、左上の1/4領域に配置される。
透過合成
ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"[0:0]split[black1][black2];[black1][1:0]overlay[black1red];[black1red][black2]blend=c0_mode=average"-map1:1 overlay.mp4
- 重ねたビデオ同士を透過する。
- しかし、blendフィルターをちゃんと理解できていない...。
- c0_mode、c1_mode、c2_mode、c3_mode、all_modeの違いは?
- averageは、何をしている?
ピクチャー in ピクチャー + 透過合成
ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"[1:0]scale=iw/2:ih/2[red];[0:0]split[black1][black2];[black1][red]overlay[black1red];[black1red][black2]blend=c0_mode=average"-map1:1 overlay.mp4
- 上に重なるビデオサイズを調整してから透過合成すれば、ピクチャーinピクチャーの透過合成となる。
確実にconcat
- 実は、上記サンプル映像二つは、単純にはconcatできない...。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex "concat=n=2:v=1:a=1" sample_1_2.mp4 ...エラー発生... [Parsed_concat_0 @ 0x7fabbbc00360] Input link in1:v0 parameters (size 640x360, SAR 1281:1280) do not match the corresponding output link in0:v0 parameters (640x360, SAR 1:1) [Parsed_concat_0 @ 0x7fabbbc00360] Failed to configure output pad on Parsed_concat_0
- 調べてみると、アスペクト比が異なっていると気付いた。
Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 640x360 [SAR 1:1 DAR 16:9], 498 kb/s, 29.97 fps, 29.97 tbr, 30k tbn, 59.94 tbc (default) Stream #1:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 640x360 [SAR 1281:1280 DAR 427:240], 593 kb/s, 29.97 fps, 29.97 tbr, 30k tbn, 59.94 tbc (default)
- ならばsample_2.mp4のアスペクト比をsample_1.mp4に合わせてみる。
- その後にconcatを使えば、ちゃんと連結できた!
$ ffmpeg -i sample_2.mp4 -aspect16:9 sample_2_16.9.mp4 $ ffmpeg -i sample_1.mp4 -i sample_2_16.9.mp4 -filter_complex"concat=n=2:v=1:a=1" sample_1_2.mp4
- しかし、上記ではコマンドラインが2行に分かれて、余分な中間ファイルも作成してしまい、あまり嬉しくない。
- そこで、filter_complexを使って、setdarあるいはsetsarのフィルターを追加することで、一行で連結できた。
$ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"[0:v]setdar=16:9[0v]; [1:v]setdar=16:9[1v]; [0v][0:a][1v][1:a]concat=n=2:v=1:a=1" sample_1_2.mp4 # あるいは... $ ffmpeg -i sample_1.mp4 -i sample_2.mp4 -filter_complex"[0:v]setsar=1:1[0v]; [1:v]setsar=1:1[1v]; [0v][0:a][1v][1:a]concat=n=2:v=1:a=1" sample_1_2.mp4
- [0:v]・[0:a]は、ファイル0番のビデオストリーム・ファイル0番のオーディオストリームという意味のようだ。
- sample_1.mp4の場合、[0:v]は[0:0]と同じストリーム、[0:a]は[0:1]と同じストリームを指す。
参考ページ
以下のページがたいへん参考になりました。感謝です!
*1:At least one output file must be specified(少なくとも一つは出力ファイルを指定しなければならない)