TS packet 格闘日記

アニメを見ながら TS ときゃっきゃうふふするお仕事。 github: https://github.com/github-mec/mecenc twitter: https://twitter.com/mecenc

ffmpeg で動画から指定秒数毎に正確にサムネイルを抽出する

ffmpeg で指定秒数毎にサムネイルを抽出する方法が Web 上にいくつか転がっていたが(-r 使ったり -filter:v select 使ったり)、どうもまともに動くものが見当たらない。正確に言うと、先頭付近のフレームの扱いが微妙だったり、遅かったり、無駄にコマンドが複雑だったりする。

まともな方法を探して FFmpeg Filters Documentation を漁っていたら fps フィルタでどうにか出来た。サンプルは次の通り。

# 1 秒毎に 1 枚ずつ、0001.jpg, 0002.jpg, ... というファイル名でサムネイルを抽出
$ ffmpeg -i input.ts -filter:v fps=fps=1:round=down %04d.jpg
# 同じく 2 秒毎
$ ffmpeg -i input.ts -filter:v fps=fps=0.5:round=down %04d.jpg

round=down の部分でどのように n 秒毎を定義するかを指定している。down だと端数を切り捨て、up だと切り上げ、round だと近い値にまるめる、つまり四捨五入のような処理が行われる。個人的なおすすめは down である。何故なら up や round だと先頭フレームだけ他とは違う間隔でサムネイルが抽出されるからである。例えば 30fps の動画から round=up で 0.5 秒毎にサムネイルを抽出しようとすると、先頭 2 フレームのタイムスタンプ 0.000 秒と 0.033 秒となり、round=up により 0.033 秒は 0.5 秒に切り上げられ、結果として先頭の連続する 2 フレームが共に出力されてしまう。

もし放送波データを扱い、かつサムネイルを切り出す場所の正確性が問題となる場合、あらかじめ映像と音声を分離しておいたほうが無難である。何故なら映像と音声の開始時刻が違うために、切りだされる位置が想定よりずれるからである。これは Web 上に転がっている他の方法を使う場合にも同様に問題となる。これを回避するために、実際には次の手順を踏むことになる。

# 入力を映像と音声に分離
$ ffmpeg -i input.ts -vcodec copy -an input_video.ts -vn -acodec copy -input_audio.ts
# 映像と音声の開始位置のズレを確認。 start: x.xxxxx と書かれている場所がある。
# 音声と同期を取りたい場合に参照する。今回は無視しても構わない。
$ ffprobe in_video.ts
# 1 秒毎にサムネイルを抽出。
$ ffmpeg -i input.ts -filter:v fps=fps=1:round=down %04d.jpg

この方法だと比較的高速かつ簡単にサムネイルを抽出できる。mecenc では今のところこの方法と crop フィルタを組み合わせて局ロゴ検出に利用している。