ffmpeg で動画分割(とか)

今回も若干タイトルを曖昧にしてる。ちょうどffmpeg で複数動画結合の反対のニーズね。

ffmpeg で動画分割(とか)

動機

こんなん簡単なハズだ、と思ったりするわけだ。一度書いてたりもするし

けど「ほんのかすかにわずかに微複雑」なことをしようとするだけで、結構悶絶なのよねこれ。「ほんのかすかにわずかに微複雑」てね、「複数セグメントをカットしたい」てだけよ、そんだけでかなりエラいことになり、メモっとかないとまず思い出せないことになる。

ので。

作為的な入力動画

わけあって Waveform な動画 (ffmpeg で)2。再掲:

便宜上、この入力ファイル名は hnk_charas.mp4 とする。

頭をカットする

-ss は「set the start time offset」なのでこの目的に使えるてことになる。

-ss を「入力ファイル指定の前に記述する」のと「入力ファイル指定の後に記述する」のとで全然振る舞いが違い、前者が猛烈に速い(というか後者が悶絶するほど遅い)のだが、だからといって「いつでも前に書ける」わけではない。今の場合は可能で、たとえば:

1 [me@host: ~]$ #ffmpeg -y -i hnk_charas.mp4 -ss 00:05:52 master_kongo.mp4
2 [me@host: ~]$ ffmpeg -y -ss 00:05:52 -i hnk_charas.mp4 master_kongo.mp4

結果がどうなるか、これについては想像できない人はいないはずだけど一応:

これでいいならこれで十分だが、続くネタに関係してくるので、あえて trim, atrim を使った別解:

コマンドラインに書ききれないのでシェルスクリプトにしてるだけ
1 #! /bin/sh
2 # 00:05:52 -> 352[seconds]
3 ffmpeg -y -i hnk_charas.mp4 -filter_complex \
4 "[0:v]trim=start=352,setpts=PTS-STARTPTS[v];
5  [0:a]atrim=start=352,asetpts=PTS-STARTPTS[a]
6 " -map '[v]' -map '[a]' master_kongo2.mp4

まったく同じ結果を得られ、「面倒」なので何が言いたいんだ、てことになるかと思うが、読み進めていけばわかる。

頭とお尻をカットする

-ss-to を使うのが簡単なのだが、古い ffmpeg では -to が使えないので、この場合は -t を使う。-t-to は無論目的が違い、前者は duration を与えるものであり、-to は特定時刻を与える。

-ss-i の前に置けたように、-to も前置出来る:

1 [me@host: ~]$ ffmpeg -y -ss 00:00:43 -to 00:01:10 -i hnk_charas.mp4 cinnabar.mp4

-to を使えない場合は、代わりに -t で duration を与える:

1 [me@host: ~]$ ffmpeg -y -ss 00:00:43 -t 27 -i hnk_charas.mp4 cinnabar.mp4
2 [me@host: ~]$ #
3 [me@host: ~]$ #またはこれでもいい
4 [me@host: ~]$ ffmpeg -y -ss 00:00:43 -t 00:00:27 -i hnk_charas.mp4 cinnabar3.mp4

頭だけカットの際と同じく trim, atrim を使った別解:

コマンドラインに書ききれないのでシェルスクリプトにしてるだけ
1 #! /bin/sh
2 # 00:00:43 -> 43[seconds]
3 # 00:01:10 -> 70[seconds]
4 ffmpeg -y -i hnk_charas.mp4 -filter_complex \
5 "[0:v]trim=start=43:end=70,setpts=PTS-STARTPTS[v];
6  [0:a]atrim=start=43:end=70,asetpts=PTS-STARTPTS[a]
7 " -map '[v]' -map '[a]' cinnabar4.mp4

時間指定で分割

単純に 30分ごとに分ける、みたいなのではなくて、今回の入力の「Rutile」部分(00:02:01~00:02:14)、「Antarcticite」部分(00:04:37~00:04:51)を抜き出す、というものにしてみる。(「単純に 30分ごとに分ける」みたいなのはこれの単に単純なヤツ。)

すんげーむずかしいのかと思いきや実はこれそのものは全然そうではなくて、実に簡単だったりする:

コマンドラインに書ききれないのでシェルスクリプトにしてるだけ
1 #! /bin/sh
2 ffmpeg -y -i hnk_charas.mp4 \
3   -ss 00:02:01 -to 00:02:14 rutile.mp4 \
4   -ss 00:04:37 -to 00:04:51 antarcticite.mp4

なお、(今使ってる入力の「Master Kongo (Kongou-sensei)」のように)ある開始時刻から末尾まで、であれば -to (あるいは -t) は省略出来る。たとえば:

コマンドラインに書ききれないのでシェルスクリプトにしてるだけ
1 #! /bin/sh
2 ffmpeg -y -i hnk_charas.mp4 \
3   -ss 00:02:01 -to 00:02:14 rutile.mp4 \
4   -ss 00:04:37 -to 00:04:51 antarcticite.mp4 \
5   -ss 00:05:52              master_kongo.mp4

部分カット

直接的に「部分カット」する手段はなさげ。なので基本的に「特定部分抽出」→「結合」が解となる、はず。

「特定部分抽出」→「結合」を各々別のタスクとしてやればまさに以前やったヤツと同じ解。つまり一つ前の結果を入力として concat で結合する。

ただ、「特定部分抽出」→「結合」を各々別のタスクとしてやると、中間ファイルが大量に出来てしまうことになったり、あるいはその中間ファイルの管理が面倒だったりとか色々鬱陶しいわけで、なんとか一回の処理で済ませられませんかいな、てことなわけだ。答えはここにある(このコメントにも注意)が、これをわかりやすい入力を使って自分でやってみようつー話。

「頭をカット」での別解の意味はこれでわかるであろう:

コマンドラインに書ききれないのでシェルスクリプトにしてるだけ
 1 #! /bin/sh
 2 #  -ss 00:02:01 -to 00:02:14 rutile       -> from 121 to 134
 3 #  -ss 00:04:37 -to 00:04:51 antarcticite -> from 277 to 291 
 4 ffmpeg -y -i hnk_charas.mp4 -filter_complex "
 5 [0:v]trim=start=121:end=134,setpts=PTS-STARTPTS[v_1];
 6 [0:a]atrim=start=121:end=134,asetpts=PTS-STARTPTS[a_1];
 7 [0:v]trim=start=277:end=291,setpts=PTS-STARTPTS[v_2];
 8 [0:a]atrim=start=277:end=291,asetpts=PTS-STARTPTS[a_2];
 9 [v_1][a_1][v_2][a_2]concat=a=1[v][a]" \
10   -map '[v]' -map '[a]' rutile_antarcticite.mp4

オマケ: チャプターマークをつける

部分カットだとか分割だとか結合だとかしたいってことは、チャプターマークをつけるなんてのもやりたくなることをやってるつーことだ。

ただチャプターマークを認識出来るプレイヤーを使わないと意味ないんで、チャプターマークを見たければ例えば Windows Media Player Classic だとか VLC Player をお使いなはれ。

ドキュメントはこれ。はっきりいってめちゃくちゃ説明下手、このドキュメントだけで理解出来る人がいたら天才。例えばこういうこと:

ファイル名は hnk_charas_meta.txt だとする
 1 ;FFMETADATA1
 2 
 3 [CHAPTER]
 4 TIMEBASE=1/1
 5 # -ss 00:00:00 -to 00:00:41 phosphophyllite -> from 0 to 41
 6 START=0
 7 END=41
 8 title=phosphophyllite
 9 
10 [CHAPTER]
11 TIMEBASE=1/1
12 # -ss 00:02:01 -to 00:02:14 rutile -> from 121 to 134
13 START=121
14 END=134
15 title=rutile
16 
17 [CHAPTER]
18 TIMEBASE=1/1
19 # -ss 00:04:37 -to 00:04:51 antarcticite -> from 277 to 291 
20 START=277
21 END=291
22 title=antarcticite

(TIMEBASEが理解しにくいと思うが、TIMEBASE=1/1000 と書けば START などの指定がミリ秒、例の TIMEBASE=1/1 なら秒指定。)

これを入力とすれば例えばこうする:

1 [me@host: ~]$ ffmpeg -y \
2 >  -i hnk_charas.mp4 -i hnk_charas_meta.txt -map_metadata 1 \
3 >  -codec copy \
4 >  hnk_charas-with-meta.mp4

hnk_charas.mp4 が「入力0」、hnk_charas_meta.txt が「入力1」なので「-map_metadata 1」てことね。

VLCメディアプレイヤーで見ればこんな感じ:

(すべてのチャプターを切れ目なく入力してないのでヘンに見えるけれど、あるチャプターの末尾が次のチャプターの先頭になるように全部書けば「ヘンな気がする」ことはないので、安心しようとしてみるといい。)