LoginSignup
0
1

【ffmpeg】複数の動画を並べてフレーム数を表示する

Posted at

概要

FFmpeg を使って、複数の動画を並べてフレーム数を表示する目的で使用する際の備忘録。
コマンド生成と FFmpeg 実行に python を利用する。(bat 化しても便利そう)

基本コマンド概要

最終的には以下のようなコマンドが実行される想定。肝心の複数の動画のレイアウトやフレーム数表示は後述。

ffmpeg.exe
# 上書き確認をしないフラグ
-y
# 入力 -r:入力フレームレート -stream_loop: ループ回数(0で1回)
-r 60 -stream_loop 1 -i input_01.mp4
-r 60 -stream_loop 0 -i input_02.mp4
# 出力動画のコーデック
-c:v h264
# 出力動画のフォーマット
-pix_fmt yuv420p
# 出力フレームレート
-frame_rate=60
# 無限ループ(gifで書き出す場合)
-loop 0
# filter
-filter_complex "後述"
# 出力ファイル
output.mp4

連番画像を入力する

入力に連番画像を並べる場合、例えば4桁のシーケンス番号を持つ input_0001.png のような連番画像は以下のように記載する。

-i input_%04d.png

filter_complex

複数の処理(フィルタ)を複雑に組み合わせる場合、 filter_complex コマンドを使う。
シンプルなフィルタを扱う場合も同じ知識が使えるため、ここでは filter_complex を前提として記載する。
フィルタごとは , フィルタの各パラメータは : で区切る。
ざっくり以下のような全体像になる。

# fps
fps=60,
# 出力サイズ
scale=640:480,
# レイアウト
[0:v][1:v]xstack=inputs=...,
# テキスト描画
drawtext=text=hoge:fontfile=Sans:...,
drawtext=text=fuga:fontfile=Sans:...

複数動画を並べる

xstack フィルタを利用して複数の入力を並べる。

xstack=
# 入力の数
inputs=2:
# 一番短い入力の終了時に強制終了するフラグ(0で無効)
shortest=0:
# レイアウト時の座標指示。'|'区切りで入力の分だけ記載する
layout=0_0|w0_0

レイアウト自動化

入力の数から自動でレイアウト指示を生成する。

layouts = []
# input の数の平方根から列の数を取得
sqrt = math.sqrt(input_length)
v_max = math.ceil(sqrt)
for i in range(input_length):
    # レイアウトを生成
    h, remainder = divmod(i, v_size)
    v = remainder if not remainder == v_max else 0
    w = '+'.join([f"w{x}" for x in range(0, v)]) if not v == 0 else "0"
    h = '+'.join([f"h{x}" for x in range(0, h)]) if not h == 0 else "0"
    layouts.append(f"{w}_{h}")

layout = '|'.join(layouts)

フレーム数を描画する

drawtext フィルタを利用する。

drawtext=
# テキストの表示位置(左上基準)
x=0:y=0:
# 使用するフォントのファイルパス、またはフォントファミリー
fontfile=Sans:
# 文字色。文字列指定または [0x|#]RRGGBB[AA] のフォーマットで記載
fontcolor=0xff0000ff:
# 文字の大きさ
fontsize=24:
# テキストに box (座布団) を敷くフラグ
box=1:
# box のカラー
boxcolor=0x00000080:
# box のボーダー(box の外周と文字間のパディング)
boxborderw=10:
# テキスト。 frame_num(n) で現在のフレーム数
text=frame_num

テキストの描画位置自動化

box ボーダーの幅を加味した左右上下揃えの座標を算出する

# 横
if is_left:
    # 左揃えの場合
    x = f"{box_border}"
else:
    # 右揃えの場合
    x = f"w-text_w-{box_border}"
# 縦
if is_top:
    # 上揃えの場合
    y = f"{box_border}"
else:
    # 下揃えの場合
    y = f"h-text_h-{box_border}"

フレーム数を補正する

expr_int_format (eif) を利用して、フレーム数に以下の補正を加える。

  • カウントをサイクルする
    • 動画をループ再生する際に1サイクル毎のフレーム数を表示する目的
  • 表示を開始するフレームを指定
    • 後述の「指定フレームのみ表示する」と併用を想定
  • カウントの初期値を指定
digits = 3
if is_cycle:
    frame = f"mod(n*{source_frame_rate / output_frame_rate},{one_cycle_frame_num})"
    digits = max(digit, len(str(one_cycle_frame_num)))
else:
    frame = "n"
text = f"%{{expr_int_format\:{frame}+{start_count - start_frame}\:d\:{digits}}}"
# sample
text=%{{expr_int_format\:n+0\:3}}
text=%{{expr_int_format\:mod(n*2,30)+10\:d\:2}}

指定のフレームのみ表示する

enable フィルタ + between() で範囲を指定する。
※ between() は文字列描画専用ではないので他の用途にも使える。

# sample
drawtext=
...:
# 範囲指定 (value, start, end)
enable=between(n,10,60)

Gif 動画を書き出す際の画質向上手段

こちらの記事に非常に丁寧にまとめられています。すばら :clap:
もろもろの処理の後にこちらのフィルターを追加すると画質向上や省サイズ化ができます。

-filter_complex "xstack=...,split [a][b];[a] palettegen [p];[b][p] paletteuse=dither=none"

Python から実行し、エラーを受け取る

python から FFmpeg を subprocess で叩き、実行結果を受け取りエラーハンドリングする。
※ ここはもっと良い手法がありそう。

result = subprocess.run(ffmpeg_command, shell=False, capture_output=True)

# ffmpeg のエラー文に特定の文字列が入っていたらエラーとみなす
if "Conversion failed!" in result.stderr.decode('utf-8'):
    # エラー処理
    is_error()
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1