cBlog

Tips for you.

ffmpegで動画を再エンコードなしで正確に切り出す

スポンサーリンク

FFmpeg ロゴ

やったー!やりましたー!無劣化な動画をキーフレーム単位で正確に切り出すことに成功しました!

妊娠したよー\(^o^)/

冗談はさておき以前の記事。

yaritakunai.hatenablog.com

-ssオプションで開始位置にシークし、-tオプションで切り出し長さを指定するのだが、それらは正確なものではない。実際には開始位置は-ssオプションで指定した位置から1つ前のキーフレームになるし、-tオプションで指定する長さは時間指定のため、きれいに両端をキーフレームで切り出ししたくともその指定は容易ではない。

-ssオプションの挙動はまだ読める。理屈では開始位置にしたいキーフレームの少しあとの時間を指定してやればいいし、Avidemuxのようなキーフレームを確認できるソフトを使えば目で結果の確認もできる。

問題は-tオプションだが、我々がやりたいのはフレームベースの編集なのだから-vframesオプションを使ってフレーム数で指定してやればいいことに気づく。これなら狙ったフレームで動画の出力を止めることが可能である。

さて、そのためには動画のフレーム番号が求められる。そこで、ffprobeで解析し、出力をパースすることでキーフレームの時間リストを作成した。

 

ffprobeでキーフレームのインデックスを取得する

ffprobe -read_intervals %+10:00 -show_frames -select_streams 0 -show_entries frame=key_frame,pkt_pts_time:side_data= -of csv=p=0 in.m2ts | grep , | awk -F , 'NR==1{t0=$2} $1==1{print NR, $2-t0}' > out.txt
  • -read_intervals %+10:00 -- 動画の開始から10分のみ処理する。それなりに時間が掛かるので必要に応じて
  • -show_frames -- フレーム情報を出力
  • -select_streams 0 -- ビデオのストリーム番号を指定すること。この場合、入力の0番目のストリーム
  • -show_entries frame=key_frame,pkt_pts_time:side_data= -- 必要なkey_frame(キーフレームで1が立つ)とpkt_pts_time(PTSタイムスタンプ)のみ出力させる。side_data=は不要なSIDE_DATAセクションを空にしている
  • -of csv=p=0 -- CSV形式で出力し、セクション名を出力しない
  • grep , -- 空行を削除(カンマのある行を抽出)
  • awk -F , 'NR==1{t0=$2} $1==1{print NR, $2-t0}' -- 区切り文字にカンマを指定し、タイムスタンプのオフセットを0にしている。1行目の2フィールド目(pkt_pts_time)をt0に代入。1フィールド目(key_frame)が1の行について行番号(つまりフレーム番号)とオフセットを除去したタイムスタンプを出力

切り出したい開始位置と終了位置のフレーム番号の差+1をffmpegで-vframesに指定してやれば、エンコードせずともGOP単位での正確な切り出し(キーフレームから別のキーフレームまで)が可能となる。(Avidemuxのタイムコードは00:00:00.000始まりではないようなので注意!)

ffmpeg -ss 05:11.811 -i in.m2ts -ss 0 -vframes 6586 -map 0 -c copy out.m2ts
  • -ss 0 -- 付けないとAACが変になる
  • -map 0 -- 入力0のプログラム、ストリームすべてを出力するため

はずなのだが、実践してみると終端(Iフレーム)直前のBフレーム2つが省略されてしまい、開始位置と終了位置のフレーム番号の差+3とする必要があった。実用上は結果を

ffprobe -show_frames -select_streams 0 in.m2ts | grep pict_type > out.txt

などすることで確認・調整してやる必要があるようだ。

 

あとがき

結局のところよくわからん。

願わくばスタートポジションもフレームインデックスで指定させてほしい。

出力プログラム番号やストリーム番号(#0:0[0x240]とか)も保持させる方法ないですか?

 

関連記事

yaritakunai.hatenablog.com