ffmpeg で複数動画結合

あえての曖昧な言い回しの見出し。

ffmpeg で複数動画結合

動機 + 「ついで」

ここで hstack, vstack 使ったついでに Merge Conference Video and Audio call output using hstack ffmpeg 的な、かなり起こりうるニーズのことをやってみよう、つーことである。一つ前で予告した通り、『たとえばともだちと共同で同じ対象を撮影したとしてだな、未編集ならばそれらは時刻あわせさえちゃんと出来るなら、対象までの距離差さえなければ簡単に同期するはずだ、みたいなことね。』であるとか、あるいは「似ているが違う」ものを比較目的で同時再生したい、なんてのも良くあることだろうし。後者のニーズはサイエンスな分野でありがちな気がするわね。(というか正直そういう複数動画同時再生可能なプレイヤーがあればいいのにと思う。)

が、「動画結合」とあえてタイトルを外しておいて、ついでなので How to concatenate (join, merge) media files についても(改めて)メモっとこうかなと。結構何度もやるんだけど毎度忘れるもんで。

ついでといえばもうひとつ、一つ目が hstack, vstack のテリトリーで、二つ目が concat なわけだけれど、三つ目として overlay もメモっときたい。これの用途は無論広いが、「画像結合」という言葉で括る場合は「同じ対象を別アングルで撮影した複数画像を同時に見せる」ための hstack/vstack でない別解、俗に言うところの「ワイプ」だよね。ワタシ個人の用途ではそんなに頻繁に使う想像は出来ないけれど、一応やっておきたい。やりたくなってから調べるの億劫だからさ。

2018-07-04追記: この記事全体と jeorgen/align-videos-by-sound の関係

経緯としては最初にここで気付き、今あなたがご覧になっておらはるこのページを書いたあとの パディングの件のほうで「今あなたがご覧になっておらはるこのページ」での「やだぁ」の解決策を見出して…なんて流れのなかで、改造しまくってたら、「今あなたがご覧になっておらはるこのページ」のありとあらゆることに結構詳しくなって、なおかつ align-videos-by-sound がかなり実用になるものに仕上がってきた、て感じ。

align-videos-by-sound は「音声に基づいて複数動画の同期ポイントを知る」ために作られたプロジェクトなのだけれど、あたしがかなり書き換えたりした結果、align.py はワタシほどのバカでもわかるものになり(オリジナルと比較してみ)、「すぐに使えるバカチョンスクリプト」もいくつか追加した。今このページでやってるひとつは simple_stack_videos.py そのものだし、もうひとつ、さっき「ベースとなる音声に基づき、千切られた動画を連結する」なんてのも書いた。

何が言いたいのか。つまり、今目にしておられるこのページとともに、align-videos-by-sound もセットで読むとオイシイ人もいるかもしれないよ、ってお話。

入力動画たち

著作権、youtube の利用規約、個人情報保護等々ナイーブでシビアな問題が多いし、どのみち「処理結果の意味が伝えやすい作為的な動画が必要」なので自作動画を使うしかない、てことでWaveform な動画を自作した。再掲:

1つ目と3つ目は頭で「スラッシュスラッシュ」が入ってるかどうかだけが違う同じ音声、2つ目と4つ目も同じ関係、スラッシュの数が違う。これは「同期する時刻が動画ごとに違う」ことを模擬するためにそうしてる。

これらは便宜的に i1.mp4i2.mp4i3.mp4i4.mp4 という名前にしてあるとする。

試行錯誤のために先に知っておくべきこと

特に同期ポイントが動画開始に揃っていない動画について、どこで同期するかわからず試行錯誤するしかないとするならば、試行錯誤の一回あたりの時間も浪費しがちなので、「毎度動画全体を処理するのを待つ」ことは避けたいというわけだ。(当然そうなりにくいように、予め「およそこのくらい」というアタリをつけておくことも必要。)

ffmpeg の終了方法を知っとくのが先決。ffmpeg は処理中キー入力を受け付けるが、「q」で「処理終了」出来る。つまり、適当な時間処理して「q」で止めると、その時間までの動画が出来上がる。Ctrl-C で強制終了すると終了処理が行われないので、壊れた動画になり、どのプレイヤーも再生出来ない。

もう一つは -t で duration を指定できること、-ss で開始時間を指定できること。後者はフレームスキップが一瞬で終わるわけではないので時短用途には今ひとつだが、前者は特に何度も試行錯誤をする必要がある場合は重要。今 bash のヒストリをあさったらこんなのを使ってた:

bash の計算機能も駆使してる
1 me@host: ~$ for i in *.mp4 ; do \
2 > ffmpeg -y -i "$i" \
3 > -ss 00:00:05 \
4 > -t $((60*88 + 36 - 5)) \
5 > -vf \
6 >   "scale=iw/2:-1,pad=1280:720:(ow-iw)/2:(oh-ih)/2:color=black" \
7 > ../"`basename \"$i\"`" ; done

この例の場合、動画開始5秒から 5311 秒を処理している。ほかにもフレーム数で指定することも出来るので、詳しくは --helpドキュメント参照のこと。

2018-06-24追記: 音声に基づいて同期ポイントを知る方法についてはこれを知って、ここでその成果を使ってみている

2018-07-11追記: お試し(試行錯誤)モード別解

ずっと結構困っていたのだ。何せ q で終了してくれないケースが結構多いからだ。フィルタが複雑になると起こりやすいようだ。が、2つ見つけた。

一つ目は「チョン切れても再生できるコンテナを選ぶこと」。昔の mpeg1 とかって確かそうだったはずなんだよなぁ、という微かなおぼろげな記憶がありながらも、最近はずっと mpeg4 ばかり相手にしてたんで、これを試そうとしてなかった。うーん、早く試せば良かったよ。マトリョーシカ(拡張子としては mkv, mka、フォーマット指定なら -f matroska)。この場合、Ctrl-C でブチ切っても(少なくとも VLC Media Player は)再生出来る。

もう一つの解も本質は一緒だったりするのだけれど、ffmpeg が標準出力に吐き出して、ffplay が標準入力を食べるようにする。ここでもマトリョーシカ:

例によって MSYS bash コマンドライン。
1 [me@host: ~]$ ffmpeg -i input.mp4 -f matroska - | ffplay -

ffmpeg の基礎を少し知ってれば「tee」的なことはすぐ:

1 [me@host: ~]$ ffmpeg -i input.mp4 output.mp4 -f matroska - | ffplay -

出力ファイルを作りつつ ffplay でも再生する。ただこれ、「ffplay だけ終了」出来ない、と思う。のでやはり「お試し」でしか使えない技、ということになる。それでもまぁ「そもそも全部エンコード終わってみないことにはうまくいってるのかどうかがわからない」状態から較べれば微かに何億倍もマシ。

なお、Windows 版バイナリの最近の ffplay は音声ライブラリが壊れてるみたいで使えない。実は心当たりはあったりする、前に linux で SDL 関連でのビルドに失敗してるので。まさにそれ。static 版を選べば動くかもね、試してないけど。現状ワタシはこうしてる:

例によって MSYS bash コマンドライン。
1 [me@host: ~]$ ffplay="/c/Program Files/ffmpeg-3.3.2-win64-shared/bin/ffplay.exe"
2 [me@host: ~]$ "${ffplay}" aaa.mkv

本題ではない concat

本題ではないし、一度書いてるし、てことなんだけれど、youtube の利用規約違反に気付いてなかったときに書いたヤツなのでよろしくないので改めて、つーことだ。

といっても、基本は FFMpeg な wiki に書いてあるしね。さらっとだけ。

形式が統一されていない場合、最低でもサイズを統一する必要がある。たとえば リサイズはこんな感じ:

いつもの通りワタシは MSYS ユーザなので、MSYS bash のコマンドラインね
1 [me@host: HnK]$ mkdir cnv
2 [me@host: HnK]$ for i in *.mp4 ; do ffmpeg -i "$i" -vf scale=1920:1080 cnv/"$i" ; done

ほか色々変換が必要な可能性があるが、どこまで ffmpeg が空気読んでくれるかはやってみないとわからんところ。少なくともサイズに関しては全く ffmpeg の concat は我関せずでぶっ壊れた動画を作ってしまうが、ダメなら色々駆使してリエンコードする必要があるだろう(典型的には -r でフレームレート、-ar でオーディオサンプリングレート、-c:v でビデオコーデック、-c:a でオーディオコーデック、ここいらだろうとは思う)。

で、これ(ら)が済んで形式が整ったら今度はこれらを「結合」する。例えばこんな感じ:

カレントにはサイズが統一されたヤツらがいるとして
1 [me@host: HnK]$ ls *.mp4 | sed 's@^\(.*\)$@file '"'"'\1'"'"'@' > flist.txt
2 [me@host: HnK]$ ffmpeg -f concat -safe 0 -i flist.txt concat-result.mp4

リストを予め作るのが面倒くさいというやり方で、他のやり方もないではなさそうなんだけど、まぁ多分これが一番素直。(なお、上の例は「リエンコードを伴う結合」。リエンコードを避けたければ「-c copy」を付けなはれ。)

最初に挙げた入力動画を使った結果をアップロードすると結構デカい(50MB)し、これの結果が想像出来ない人はいないと思うので省略。(concatinate の名の通り「連結」動画になる、それだけ。)

結合部分をブラックアウトにするとかフェードアウト・フェードインにするとかについては、たとえばこんな検索で見つかると思う。


2018-06-28追記:
「典型的には -r でフレームレート、-ar でオーディオサンプリングレート、-c:v でビデオコーデック、-c:a でオーディオコーデック、ここいらだろうとは思う」と書いたとおりなのだが、「壊れた動画がすぐさま簡単に作れます!」という意味だと、どうやらサイズ(解像度)とともに「フレームレート」が一番の主犯のようだ。

サイズ違いの場合は「映像が壊れる」のだが、フレームレート違いの場合は、「同期が壊れる」。実際にやってみればわかるが、たとえば「絵の方だけ早送りみたいなヘンな動画」になる。てわけで、「解像度とフレームレートだけは最低限統一しておくべし」てことなのだろうなと思う。たぶんサンプリングレートもこれにあたるんじゃないかとは思うが、今のところワタシは偶然これでは問題起こしてない。


2018-06-29追記: ffmpeg で動画の頭とかお尻にパディングに simple_stack_videos_by_sound_track の件を追記しておいたのだが、これを作りながら気付いたことがいくつかあった。

出力のサンプリングレートを強制するには aresample を使えばいい。これは simple_stack_videos_by_sound_track の中でやってるので、みてもらったらいい。

あと SAR (setsar=1) しないと壊れるというパターンもあった。何が起こってるのかよくわかってないんだけれど、解像度がヘンなもの、たとえば 10×18 みたいな酔狂なものを扱おうとすると、setsar=1 しないと ffmpeg がエラーで死ぬ。これも simple_stack_videos_by_sound_track の中でやってる。

本題の hstack, vstack を使った結合

まずは音なしかつ同期に無頓着に単純に

1×2、2×1、4×42×2 と順に…、なんてやってるとダルいので、一気に 4×42×2 で:

(bash)シェルスクリプトの態だが、再利用可能性のためではなく単にコマンドラインで書き切るのが大変だから
 1 #! /bin/sh
 2 
 3 ffmpeg -y -i i1.mp4 -i i2.mp4 -i i3.mp4 -i i4.mp4 -filter_complex "
 4 [0:v]scale=iw/2:-1[0v];
 5 [1:v]scale=iw/2:-1[1v];
 6 [2:v]scale=iw/2:-1[2v];
 7 [3:v]scale=iw/2:-1[3v];
 8 [0v][1v]hstack[v1];
 9 [2v][3v]hstack[v2];
10 [v1][v2]vstack[v]" -map '[v]' merged0.mp4

filter_complex の書き方について熟知しているわけではなく、まだ雰囲気しかわかってないけど、これを読んでくれてるあなたもおそらく「雰囲気だけはすぐにわかる」と思う。0, 1, 2, 3 は当然4つの入力ファイルに対応してて、0:v は一個目の入力ファイルのビデオストリーム、なので一行目は「入力1のビデオストリームをリサイズしてこれを 0v とする」てこと。hstack, vstack 部分もこの要領で読めるでしょう(なのでscaleせずにそのまま結合するやり方も想像通りのものなはずよ)。本気であれこれやる必要が出たらもうドキュメントを熟読するしかない。まだワタシも全然出来てないけど。

これの処理結果例は、続く音声あり版から想像つくはずなので省略。

音ありだが同期にはまだ無頓着に

(bash)シェルスクリプトの態だが、再利用可能性のためではなく単にコマンドラインで書き切るのが大変だから
 1 #! /bin/sh
 2 
 3 ffmpeg -y -i i1.mp4 -i i2.mp4 -i i3.mp4 -i i4.mp4 -filter_complex "
 4 [0:v]scale=iw/2:-1[0v];
 5 [1:v]scale=iw/2:-1[1v];
 6 [2:v]scale=iw/2:-1[2v];
 7 [3:v]scale=iw/2:-1[3v];
 8 [0v][1v]hstack[v1];
 9 [2v][3v]hstack[v2];
10 [v1][v2]vstack[v];
11 [0:a][1:a][2:a][3:a]amix=inputs=4[a]" -map '[v]' -map '[a]' -ac 2 merged1.mp4

amix と amerge の違いはよくわかってないし、音の混ぜ方を懲りたければ、色々もっと複雑なことが出来るみたいで、当然「左動画の音は左チャンネルに、右動画の音は右チャンネルに」なんてことをしたいなんてことは考えられるけど、これはワタシは今のところ用事ないので、今回はやめとく。(こういうのって「悔しいからやりたい」気分はないではないので、気力があって気が向いたら追記の形で書くかもしれないし書かないかもしれない。) ⇒ 2018-06-24追記: 書いた

想像通りの結果になる:

「短い動画にお尻を揃えたい」なんてのはあると思うが、これはドキュメントを読めばわかると思う。

音ありで同期時刻合わせあり

たとえば StackOverflow のこの質問と回答を参考にすればこんな具合:

(bash)シェルスクリプトの態だが、再利用可能性のためではなく単にコマンドラインで書き切るのが大変だから
 1 #! /bin/sh
 2 
 3 ffmpeg -y -i i1.mp4 -i i2.mp4 -i i3.mp4 -i i4.mp4 -filter_complex "
 4 [0:v]scale=iw/2:-1,setpts=PTS-STARTPTS+1.9/TB[0v];
 5 [1:v]scale=iw/2:-1,setpts=PTS-STARTPTS+1.0/TB[1v];
 6 [2:v]scale=iw/2:-1,setpts=PTS-STARTPTS+1.0/TB[2v];
 7 [3:v]scale=iw/2:-1,setpts=PTS-STARTPTS+0.0/TB[3v];
 8 [0v][1v]hstack[v1];
 9 [2v][3v]hstack[v2];
10 [v1][v2]vstack[v];
11 [0:a]adelay=1900|1900[0a];
12 [1:a]adelay=1000|1000[1a];
13 [2:a]adelay=1000|1000[2a];
14 [3:a]adelay=0|0[3a];
15 [0a][1a][2a][3a]amix=inputs=4[a]" -map '[v]' -map '[a]' -ac 2 merged2.mp4

adelay の値としてパイプ記号で区切って二つの値を書いてるのは右チャンネル・左チャンネル別々に記述出来るから、かなと思う。(2018-06-16追記: 古いバージョンの ffmpeg (少なくとも 3.3.2)では adelay にゼロを与えるとエラーになるので注意。)

ただねぇ、気に喰わんのよ:

パーフェクトに同期出来ないのが気に喰わん! …つーことではなくて。「スラッシュスラッシュ」言ってる時間帯を観察してみてちょ。絵が動いておらんでしょう? 今の場合 i4.mp4 が基準で、i1.mp4 は i4.mp4 がスラッシュスラッシュ言い終わるまで待ってる、ということなわけね。だから「待ってるほう」が動かないのは無論意図通りなのだが、「待たせてるほう」は動かないとおかしいでしょう? どうも絵の方は「先に行って待ってる」らしい。そうじゃないだろ、と思うのだが…。(だって音の方は意図通りなんだもの、なんじゃそりゃ、て思うさそりゃ。)

なんとかしてみようといくつか試してみたけどダメだったので、これは諦めた。これがどうしても気になる場合はもう trim して開始を揃えてしまうのがいいと思う。(各々 -ss やらで切り取ったものを入力としてしまう、てこと。今の例の場合は「スラッシュ…」言ってる部分を切り取ってしまう。)

2018-06-19追記: setpts, adelay でない「気に喰う」解

頭にパディングするアプローチを書いた。上の「気に喰わない」がこれなら「気に喰う」結果になることは言わなくともわかるとは思うけれど、行うはキヨシ、結構ダルい。

音声は混ぜなくてええんや

一番音声が理想的なもの一つだけを取ればいいのだ、みたいなこともあると思う。実際の撮影動画で音割れしちゃってて使い物にならないようなものを「混ぜたい」わきゃぁねいのであって。

一つ上のやつより簡単だろう、と思ったがそうでもなかったの。

「正しい」アプローチがあるに違いない、と信じてはいるけれど、「volume 調整しちゃえばいいんじゃね?」という場当たり的な思いつきでも一応うまくいった:

(bash)シェルスクリプトの態だが、再利用可能性のためではなく単にコマンドラインで書き切るのが大変だから
 1 #! /bin/sh
 2 
 3 ffmpeg -y -i i1.mp4 -i i2.mp4 -i i3.mp4 -i i4.mp4 -filter_complex "
 4 [0:v]scale=iw/2:-1,setpts=PTS-STARTPTS+1.9/TB[0v];
 5 [1:v]scale=iw/2:-1,setpts=PTS-STARTPTS+1.0/TB[1v];
 6 [2:v]scale=iw/2:-1,setpts=PTS-STARTPTS+1.0/TB[2v];
 7 [3:v]scale=iw/2:-1,setpts=PTS-STARTPTS+0.0/TB[3v];
 8 [0v][1v]hstack[v1];
 9 [2v][3v]hstack[v2];
10 [v1][v2]vstack[v];
11 [0:a]adelay=1900|1900,volume=0[0a];
12 [1:a]adelay=1000|1000,volume=0[1a];
13 [2:a]adelay=1000|1000,volume=0[2a];
14 [3:a]adelay=0|0[3a];
15 [0a][1a][2a][3a]amix=inputs=4[a]" -map '[v]' -map '[a]' -ac 2 merged3.mp4

少なくとも「不正解」ではないしね、これでいいような気はする。

Overlay でワイプ的な

これの効果が今使ってる入力動画だけだと伝わりにくいので、予め

1 me@host: ~$ ffmpeg -y -i i2.mp4 -vf 'negate' i2n.mp4

なんてことをして、黒背景動画と白背景動画の overlay で遊ぶことにする。つまり入力はこの2つ:

あとはドキュメントを読めばわかる、と言いたいところだが、読み方がわからない状態でこれを読んでわかるわけはなく。たとえばこれなども参考にしつつたとえば:

(bash)シェルスクリプトの態だが、再利用可能性のためではなく単にコマンドラインで書き切るのが大変だから
1 #! /bin/sh
2 
3 ffmpeg -y -i i2n.mp4 -i i4.mp4 -filter_complex "
4 [0:v]setpts=PTS-STARTPTS+1.0/TB[0v];
5 [1:v]scale=iw/4:-1,setpts=PTS-STARTPTS+0.0/TB[1v];
6 [0v][1v]overlay=(W - w - 50):(H - h - 50)[v];
7 [0:a]adelay=1000|1000[0a];
8 [1:a]adelay=0|0[1a];
9 [0a][1a]amix[a]" -map '[v]' -map '[a]' -ac 2 merged4.mp4

開始時刻のズレ補正がいらないならもっと単純、なのはわかるよね。

結果:

これだけならなんてことはないね。

あとは透過させたり、一定時間だけオーバレイ出来たりするけど、特に難しくはないと思うので省略。

2018-06-21追記: 「マルチアングル」的な

DVD や BD でたまにみるやつね。ひっじょーに難しい…くはなくて、驚くほど簡単。

あえて複雑な例で紹介しとく。以下はこれのなかで作った「4つの動画を4つ同時再生、の、setpts、adelay でない解」の、現実世界の動画のために このスクリプトで生成したスクリプト:

 1 #! /bin/sh
 2 
 3 ffmpeg -y -i "1.mp4" -i "2.mp4" -i "3.mp4" -i "4.mp4" -filter_complex "
 4 color=c=black:s=960x540:d=6.136[prepadv0];
 5 sine=frequency=0:sample_rate=44100:d=6.136 [prepada_l0];
 6 sine=frequency=0:sample_rate=44100:d=6.136 [prepada_r0];
 7 [prepada_l0][prepada_r0]amerge=inputs=2[prepada0];
 8 [0:v]scale=960:540[v0];
 9 [prepadv0][v0] concat=n=2:v=1:a=0 [vc0];
10 [prepada0][0:a] concat=n=2:v=0:a=1 [ac0];
11 
12 [1:v]scale=960:540[v1];
13 color=c=black:s=960x540:d=0.496[postpadv1];
14 sine=frequency=0:sample_rate=44100:d=0.496 [postpada_l1];
15 sine=frequency=0:sample_rate=44100:d=0.496 [postpada_r1];
16 [postpada_l1][postpada_r1]amerge=inputs=2[postpada1];
17 [v1][postpadv1] concat=n=2:v=1:a=0 [vc1];
18 [1:a][postpada1] concat=n=2:v=0:a=1 [ac1];
19 
20 color=c=black:s=960x540:d=13.218[prepadv2];
21 sine=frequency=0:sample_rate=44100:d=13.218 [prepada_l2];
22 sine=frequency=0:sample_rate=44100:d=13.218 [prepada_r2];
23 [prepada_l2][prepada_r2]amerge=inputs=2[prepada2];
24 [2:v]scale=960:540[v2];
25 color=c=black:s=960x540:d=13.778[postpadv2];
26 sine=frequency=0:sample_rate=44100:d=13.778 [postpada_l2];
27 sine=frequency=0:sample_rate=44100:d=13.778 [postpada_r2];
28 [postpada_l2][postpada_r2]amerge=inputs=2[postpada2];
29 [prepadv2][v2][postpadv2] concat=n=3:v=1:a=0 [vc2];
30 [prepada2][2:a][postpada2] concat=n=3:v=0:a=1 [ac2];
31 
32 color=c=black:s=960x540:d=6.095[prepadv3];
33 sine=frequency=0:sample_rate=44100:d=6.095 [prepada_l3];
34 sine=frequency=0:sample_rate=44100:d=6.095 [prepada_r3];
35 [prepada_l3][prepada_r3]amerge=inputs=2[prepada3];
36 [3:v]scale=960:540[v3];
37 color=c=black:s=960x540:d=5.151[postpadv3];
38 sine=frequency=0:sample_rate=44100:d=5.151 [postpada_l3];
39 sine=frequency=0:sample_rate=44100:d=5.151 [postpada_r3];
40 [postpada_l3][postpada_r3]amerge=inputs=2[postpada3];
41 [prepadv3][v3][postpadv3] concat=n=3:v=1:a=0 [vc3];
42 [prepada3][3:a][postpada3] concat=n=3:v=0:a=1 [ac3];
43 
44 [vc0][vc1]hstack[1v];
45 [vc2][vc3]hstack[2v];
46 [1v][2v]vstack[v];
47 [ac0][ac1][ac2][ac3]amerge=inputs=4[a]
48 " -map '[v]' -map '[a]' -ac 2 \
49   "merged.mp4"

タイル状 4×42×2 に結合してるわけだけれど、音声の同期を取るのに悶着してるというわけだ。これをタイル状 4×42×2 ではなく「マルチアングル」として別ストリームとして記録したいてことだが、「別ストリームとして」と言ってることそのものがもう答えになってて、マッピングを変えるだけのことなのよ:

末尾の hstack, vstack, amerge を取っ払って -map しなおしてるだけさ
 1 #! /bin/sh
 2 
 3 ffmpeg -y -i "1.mp4" -i "2.mp4" -i "3.mp4" -i "4.mp4" -filter_complex "
 4 color=c=black:s=960x540:d=6.136[prepadv0];
 5 sine=frequency=0:sample_rate=44100:d=6.136 [prepada_l0];
 6 sine=frequency=0:sample_rate=44100:d=6.136 [prepada_r0];
 7 [prepada_l0][prepada_r0]amerge=inputs=2[prepada0];
 8 [0:v]scale=960:540[v0];
 9 [prepadv0][v0] concat=n=2:v=1:a=0 [vc0];
10 [prepada0][0:a] concat=n=2:v=0:a=1 [ac0];
11 
12 [1:v]scale=960:540[v1];
13 color=c=black:s=960x540:d=0.496[postpadv1];
14 sine=frequency=0:sample_rate=44100:d=0.496 [postpada_l1];
15 sine=frequency=0:sample_rate=44100:d=0.496 [postpada_r1];
16 [postpada_l1][postpada_r1]amerge=inputs=2[postpada1];
17 [v1][postpadv1] concat=n=2:v=1:a=0 [vc1];
18 [1:a][postpada1] concat=n=2:v=0:a=1 [ac1];
19 
20 color=c=black:s=960x540:d=13.218[prepadv2];
21 sine=frequency=0:sample_rate=44100:d=13.218 [prepada_l2];
22 sine=frequency=0:sample_rate=44100:d=13.218 [prepada_r2];
23 [prepada_l2][prepada_r2]amerge=inputs=2[prepada2];
24 [2:v]scale=960:540[v2];
25 color=c=black:s=960x540:d=13.778[postpadv2];
26 sine=frequency=0:sample_rate=44100:d=13.778 [postpada_l2];
27 sine=frequency=0:sample_rate=44100:d=13.778 [postpada_r2];
28 [postpada_l2][postpada_r2]amerge=inputs=2[postpada2];
29 [prepadv2][v2][postpadv2] concat=n=3:v=1:a=0 [vc2];
30 [prepada2][2:a][postpada2] concat=n=3:v=0:a=1 [ac2];
31 
32 color=c=black:s=960x540:d=6.095[prepadv3];
33 sine=frequency=0:sample_rate=44100:d=6.095 [prepada_l3];
34 sine=frequency=0:sample_rate=44100:d=6.095 [prepada_r3];
35 [prepada_l3][prepada_r3]amerge=inputs=2[prepada3];
36 [3:v]scale=960:540[v3];
37 color=c=black:s=960x540:d=5.151[postpadv3];
38 sine=frequency=0:sample_rate=44100:d=5.151 [postpada_l3];
39 sine=frequency=0:sample_rate=44100:d=5.151 [postpada_r3];
40 [postpada_l3][postpada_r3]amerge=inputs=2[postpada3];
41 [prepadv3][v3][postpadv3] concat=n=3:v=1:a=0 [vc3];
42 [prepada3][3:a][postpada3] concat=n=3:v=0:a=1 [ac3]
43 " \
44   -map '[vc0]' -map '[ac0]' \
45   -map '[vc1]' -map '[ac1]' \
46   -map '[vc2]' -map '[ac2]' \
47   -map '[vc3]' -map '[ac3]' \
48   -ac 2 \
49   "merged2.mp4"

プレイヤーからはこうみえる:


もっと単純な例はもっと単純なのでわかるとは思うけれど、たぶんとっかかりでつまづくとすれば、map の中にどう書けばいいかの部分で、上の例では filter_complex 内に書いていたラベルがマッピングの値になるけれど、特にフィルターすることがないようなシンプルなものでは例えばこんな具合なのよ:

1 #! /bin/sh
2 ffmpeg -y -i "1.mp4" -i "2.mp4" -i "3.mp4" -i "4.mp4"  \
3   -map '0:v' -map '0:a' \
4   -map '1:v' -map '1:a' \
5   -map '2:v' -map '2:a' \
6   -map '3:v' -map '3:a' \
7   -ac 2 \
8   "merged3.mp4"

ラベルでない場合は角括弧はつけない。

ただしこれは

いわゆる DVD や BD のマルチアングルだとか多重音声とは違うもの、らしい。

つーかさぁ、これでマルチアングルに出来るのかな、と調べようとするとさ、リッピングなダークサイドネタばかりがわんさか出るわ出るわ。あのなぁ…。実際 ffmpeg の情報収集がしにくいのも一番の原因はソレ。そしてドキュメントがえれーわかりにくいしな。

しかしまぁよくも一方的に「DVD や BD からリッピングした VOB をごにょごにょする」ネタばかりに偏るもんだ。そうではなくてさ、「マルチアングルな DVD を作るには?」みたいな初心者ネタがわんさか出ないと不健全だろうに。

なのでね、こういうマルチストリームな mp4 を「どうにか DVD 的なマルチアングルに仕立て上げる」術が全然見つけられず。諦める。

というかさぁ、「マルチアングル」ってそもそも誰が嬉しいの、とは思ったりする。そもそも「切り替え操作」という行為なくしては意味がないのだが、その「切り替え行為」がね、そんなに快適ではなくて、やはり「同時に観たい」のだよね、本来は。「複数台のディスプレイでマルチアングルの各々を同時再生」(テレビスタジオなんかでよくあるアレ)だけが本当の理想なのであって、ワタシが上でやった「4×42×2 分割同時再生」も DVD なマルチアングルも結局は「妥協案」に過ぎなくて、てことよね。

ということなのだけれど、「多重音声」なら話は別。こちらは例えば「日本語・英語同時再生」を常にやりたい「わけはなく」、もともとが「切り替え行為」との相性は良いわけよ。だってさぁ、「本編音声とオーディオコメンタリーを同列で同時に聞きたい」なんてこたぁねいでしょ、普通はどっちかに集中したいわけだ。

なので、「多重音声」ネタの方はやりたいと思っているのだけれどもね。こちらも同じ理由で全然欲しい情報にたどり付けてない。