T-CREATOR

FFmpeg フィルタグラフ設計術:複雑合成を filtergraph ファイルで可読・再利用化

FFmpeg フィルタグラフ設計術:複雑合成を filtergraph ファイルで可読・再利用化

FFmpeg で動画編集を行う際、複数の映像や音声を合成したり、エフェクトを重ねたりする処理が複雑になることはありませんか。 コマンドラインに長大な -filter_complex を直接記述すると、可読性が低下し、メンテナンスも困難になります。

そんな課題を解決するのが filtergraph ファイル です。 この記事では、フィルタグラフをファイル化することで、複雑な合成処理を可読的に設計し、再利用可能にする方法を詳しく解説します。

背景

FFmpeg におけるフィルタグラフとは

FFmpeg のフィルタグラフは、映像・音声データの処理フローを記述する仕組みです。 入力ストリームに対して、フィルタを連結・分岐・合成することで、多様な映像編集が実現できます。

以下の図は、基本的なフィルタグラフの構造を示しています。

mermaidflowchart LR
  input1["入力映像 1"] --> filter1["フィルタA<br/>(例: scale)"]
  input2["入力映像 2"] --> filter2["フィルタB<br/>(例: overlay)"]
  filter1 --> merge["合成<br/>(overlay)"]
  filter2 --> merge
  merge --> output["出力映像"]

図で理解できる要点:

  • 複数の入力ストリームが個別にフィルタ処理される
  • フィルタ結果が合成ポイントで統合される
  • 最終的に 1 つの出力ストリームとして書き出される

コマンドライン記述の限界

シンプルな処理であれば、コマンドラインに直接フィルタを記述しても問題ありません。 しかし、以下のような複雑なケースでは、可読性とメンテナンス性が大きく低下します。

#状況課題
1複数入力の合成フィルタチェーンが長くなり、全体像が把握しにくい
2エフェクトの多段適用各処理の境界が不明瞭になる
3チーム共有・再利用コマンド全体をコピー&ペーストする運用は非効率
4デバッグ作業エラー箇所の特定に時間がかかる

実際の例として、3 つの動画を重ね合わせてピクチャーインピクチャーを作る場合を考えてみましょう。

bashffmpeg -i main.mp4 -i sub1.mp4 -i sub2.mp4 \
  -filter_complex "[1:v]scale=320:180[v1];[2:v]scale=320:180[v2];[0:v][v1]overlay=10:10[tmp];[tmp][v2]overlay=10:200" \
  -c:v libx264 output.mp4

この 1 行だけでも、処理の流れを追うのは容易ではありません。 さらに、音声合成やフェード効果などを追加すると、コマンドが数百文字に膨れ上がることもあります。

課題

長大なコマンドラインがもたらす問題

複雑なフィルタグラフをコマンドラインに直接記述すると、以下のような問題が発生します。

mermaidflowchart TD
  longCmd["長大な<br/>-filter_complex"] --> read["可読性の低下"]
  longCmd --> maintain["メンテナンス困難"]
  longCmd --> reuse["再利用性の欠如"]
  read --> debug["デバッグに時間"]
  maintain --> bug["バグの混入"]
  reuse --> copy["コピペ運用"]

図で理解できる要点:

  • 長大なコマンドは可読性・メンテナンス性・再利用性のすべてに悪影響を及ぼす
  • 結果としてデバッグコストが増大し、バグが混入しやすくなる
  • コピー&ペースト運用では変更の追跡が困難になる

具体的な課題例

実際のプロジェクトで遭遇する課題を整理します。

#課題影響
1フィルタの順序が不明瞭意図しない処理結果が発生
2パラメータの調整が困難トライ&エラーのたびにコマンド全体を修正
3チーム内での共有が非効率Slack や Wiki に長大なコマンドを貼り付ける運用
4バージョン管理ができないGit で差分を追いにくい

たとえば、以下のようなコマンドを考えてみましょう。

bashffmpeg -i video1.mp4 -i video2.mp4 -i audio.mp3 -filter_complex \
"[0:v]scale=1920:1080,setsar=1[v0];[1:v]scale=640:360[v1];[v0][v1]overlay=x=1270:y=710:enable='between(t,5,15)'[vout];[0:a][2:a]amix=inputs=2:duration=first[aout]" \
-map "[vout]" -map "[aout]" -c:v libx264 -c:a aac output.mp4

このコマンドを見ただけで、以下の処理内容を即座に理解できるでしょうか。

  • メイン映像のスケーリングとアスペクト比調整
  • サブ映像のスケーリング
  • 5 秒から 15 秒の間だけサブ映像をオーバーレイ
  • 元の音声と追加音声をミックス

処理内容を理解するだけでも時間がかかり、パラメータを変更するとなるとさらに手間が増えます。

解決策

filtergraph ファイルによる外部化

FFmpeg では、-filter_complex の内容を外部ファイルに記述し、それを読み込むことができます。 この方法により、フィルタグラフの可読性が飛躍的に向上します。

bashffmpeg -i input1.mp4 -i input2.mp4 \
  -filter_complex_script filtergraph.txt \
  -c:v libx264 output.mp4

filtergraph.txt の例:

text# メイン映像のスケーリング
[0:v]scale=1920:1080,setsar=1[v0];

# サブ映像のスケーリング
[1:v]scale=640:360[v1];

# オーバーレイ合成(5-15秒の間のみ表示)
[v0][v1]overlay=x=1270:y=710:enable='between(t,5,15)'[vout]

このように、フィルタグラフをファイルに分離することで、以下のメリットが得られます。

#メリット具体的な効果
1可読性の向上コメントや改行を自由に追加できる
2メンテナンス性の向上各フィルタの役割が明確になり、修正が容易
3再利用性の向上同じフィルタグラフを複数のプロジェクトで使い回せる
4バージョン管理Git で変更履歴を追跡できる

filtergraph ファイルの基本構文

filtergraph ファイルは、テキストファイルとして作成します。 基本的な記述ルールを理解しましょう。

コメントの挿入

# で始まる行はコメントとして扱われます。 処理の意図を明記することで、後から見返したときの理解が容易になります。

text# これはコメントです
# 複数行にわたってコメントを記述できます
[0:v]scale=1280:720[scaled]

改行とセミコロン

フィルタの区切りには ;(セミコロン) を使用します。 改行は自由に入れられるため、処理単位で分割すると見やすくなります。

text# 処理1: スケーリング
[0:v]scale=1280:720[scaled];

# 処理2: テキストオーバーレイ
[scaled]drawtext=text='Hello':fontsize=48:x=10:y=10[texted];

# 処理3: フェード効果
[texted]fade=in:0:30[vout]

ストリーム指定子とラベル

入力ストリームは [番号:タイプ] の形式で指定します。

  • [0:v]: 1 番目の入力ファイルの映像ストリーム
  • [1:a]: 2 番目の入力ファイルの音声ストリーム

フィルタの出力には、任意のラベルを付けることができます。 このラベルを使って、後続のフィルタで参照します。

text[0:v]scale=1920:1080[v_main];
[1:v]scale=640:360[v_sub];
[v_main][v_sub]overlay=10:10[v_final]

上記の例では、v_mainv_sub というラベルを使って、2 つの映像を合成しています。

以下の図は、ストリームとラベルの関係を示しています。

mermaidflowchart LR
  in0["入力 0:v"] -->|scale| label1["[v_main]"]
  in1["入力 1:v"] -->|scale| label2["[v_sub]"]
  label1 --> overlay["overlay"]
  label2 --> overlay
  overlay --> final["[v_final]"]

図で理解できる要点:

  • 各入力ストリームは独立して処理される
  • フィルタの出力にラベルを付けることで、後続処理で参照可能になる
  • 最終的なラベルが出力ストリームとして使われる

実践的な設計パターン

複雑なフィルタグラフを設計する際は、以下のパターンを活用すると効果的です。

パターン 1: 処理単位でセクション分割

関連する処理をセクションとしてまとめ、コメントで区切ります。

text# ============================================
# セクション1: 入力映像の前処理
# ============================================

# メイン映像のスケーリングとクロップ
[0:v]scale=1920:1080,crop=1920:800:0:140[v_main];

# サブ映像のスケーリング
[1:v]scale=480:270[v_sub];
text# ============================================
# セクション2: 映像合成
# ============================================

# ピクチャーインピクチャー配置
[v_main][v_sub]overlay=x=1420:y=790[v_pip];
text# ============================================
# セクション3: エフェクト適用
# ============================================

# フェードイン・フェードアウト
[v_pip]fade=in:0:30,fade=out:st=570:d=30[vout]

このように、処理の段階ごとにセクションを分けることで、全体の構造が把握しやすくなります。

パターン 2: 変数的なラベル命名

ラベル名に処理内容や状態を反映させると、フロー追跡が容易になります。

text# スケーリング後のラベルに _scaled を付ける
[0:v]scale=1920:1080[v0_scaled];
[1:v]scale=640:360[v1_scaled];

# オーバーレイ後のラベルに _overlayed を付ける
[v0_scaled][v1_scaled]overlay=10:10[v_overlayed];

# フィルタ適用後のラベルに _filtered を付ける
[v_overlayed]eq=brightness=0.1[v_filtered]

以下の命名規則を参考にしてください。

#ラベル例用途
1v0_scaled映像 0 をスケーリングした結果
2v_mainメイン映像として扱うストリーム
3v_pipピクチャーインピクチャー合成後
4a_mixed音声ミックス後
5vout最終出力映像

パターン 3: 条件分岐の明示

時間帯や条件によって処理を変える場合は、コメントで明記します。

text# 0-5秒: フェードイン
# 5-60秒: 通常表示
# 60-65秒: フェードアウト
[0:v]fade=in:0:150,fade=out:st=1800:d=150[vout]
text# 10秒から20秒の間だけサブ映像を表示
[v_main][v_sub]overlay=x=10:y=10:enable='between(t,10,20)'[vout]

テンプレート化とパラメータ管理

同じ構造のフィルタグラフを繰り返し使う場合は、テンプレート化が有効です。 ただし、FFmpeg の filtergraph ファイルには変数機能がないため、シェルスクリプトや Python スクリプトで生成する方法を取ります。

シェルスクリプトによる生成

bash#!/bin/bash

# パラメータ定義
MAIN_WIDTH=1920
MAIN_HEIGHT=1080
SUB_WIDTH=640
SUB_HEIGHT=360
OVERLAY_X=1270
OVERLAY_Y=710

# filtergraph ファイル生成
cat > filtergraph.txt <<EOF
# メイン映像のスケーリング
[0:v]scale=${MAIN_WIDTH}:${MAIN_HEIGHT}[v_main];

# サブ映像のスケーリング
[1:v]scale=${SUB_WIDTH}:${SUB_HEIGHT}[v_sub];

# オーバーレイ合成
[v_main][v_sub]overlay=x=${OVERLAY_X}:y=${OVERLAY_Y}[vout]
EOF

このスクリプトを実行すると、パラメータに基づいた filtergraph.txt が生成されます。

bash# filtergraph 生成
bash generate_filtergraph.sh

# FFmpeg 実行
ffmpeg -i main.mp4 -i sub.mp4 \
  -filter_complex_script filtergraph.txt \
  -c:v libx264 output.mp4

Python による高度な生成

より複雑なロジックが必要な場合は、Python を使うと柔軟に対応できます。

python#!/usr/bin/env python3

# パラメータクラス定義
class FilterGraphConfig:
    def __init__(self, main_size, sub_size, overlay_pos):
        self.main_width, self.main_height = main_size
        self.sub_width, self.sub_height = sub_size
        self.overlay_x, self.overlay_y = overlay_pos
python# filtergraph 生成関数
def generate_filtergraph(config):
    return f"""
# メイン映像のスケーリング
[0:v]scale={config.main_width}:{config.main_height}[v_main];

# サブ映像のスケーリング
[1:v]scale={config.sub_width}:{config.sub_height}[v_sub];

# オーバーレイ合成
[v_main][v_sub]overlay=x={config.overlay_x}:y={config.overlay_y}[vout]
"""
python# 使用例
if __name__ == "__main__":
    config = FilterGraphConfig(
        main_size=(1920, 1080),
        sub_size=(640, 360),
        overlay_pos=(1270, 710)
    )

    filtergraph = generate_filtergraph(config)

    with open("filtergraph.txt", "w") as f:
        f.write(filtergraph)

    print("filtergraph.txt を生成しました")

この方法を使えば、設定ファイルから読み込んだパラメータで、動的に filtergraph を生成することも可能です。

具体例

ここでは、実際のユースケースに基づいた filtergraph 設計例を紹介します。

ケース 1: ピクチャーインピクチャー合成

メイン映像の右下に、サブ映像を小さく表示するピクチャーインピクチャーを作成します。

要件:

  • メイン映像: 1920x1080
  • サブ映像: 640x360(右下に配置)
  • 5 秒から 15 秒の間だけサブ映像を表示

以下の図は、処理フローを示しています。

mermaidflowchart TD
  main["メイン映像<br/>1920x1080"] --> scale1["スケーリング<br/>維持"]
  sub["サブ映像<br/>元サイズ"] --> scale2["スケーリング<br/>640x360"]
  scale1 --> overlay["overlay<br/>x=1270 y=710<br/>5-15秒のみ"]
  scale2 --> overlay
  overlay --> output["出力映像<br/>1920x1080"]

図で理解できる要点:

  • メイン映像はサイズを維持しつつ処理される
  • サブ映像は指定サイズにスケーリングされる
  • オーバーレイは条件付きで時間制御される

filtergraph_pip.txt:

text# ============================================
# ピクチャーインピクチャー合成
# ============================================

# メイン映像のスケーリング(アスペクト比補正)
[0:v]scale=1920:1080,setsar=1[v_main];
text# サブ映像のスケーリング
[1:v]scale=640:360[v_sub];
text# オーバーレイ合成(5-15秒の間だけ表示)
# x=1270: 右下配置(1920 - 640 - 10px マージン)
# y=710:  右下配置(1080 - 360 - 10px マージン)
[v_main][v_sub]overlay=x=1270:y=710:enable='between(t,5,15)'[vout]

実行コマンド:

bashffmpeg -i main.mp4 -i sub.mp4 \
  -filter_complex_script filtergraph_pip.txt \
  -map "[vout]" \
  -c:v libx264 -preset medium -crf 23 \
  output_pip.mp4

ケース 2: 3 分割画面レイアウト

3 つの映像を横並びに配置する分割画面を作成します。

要件:

  • 出力解像度: 1920x1080
  • 各映像: 640x1080(横幅を 3 分割)

以下の図は、レイアウト構造を示しています。

mermaidflowchart LR
  in1["映像 1"] --> pos1["左<br/>x=0"]
  in2["映像 2"] --> pos2["中央<br/>x=640"]
  in3["映像 3"] --> pos3["右<br/>x=1280"]
  pos1 --> canvas["黒背景<br/>1920x1080"]
  pos2 --> canvas
  pos3 --> canvas
  canvas --> out["出力"]

図で理解できる要点:

  • ベースとなる黒背景キャンバスを用意
  • 各映像をスケーリングして指定位置に配置
  • overlay を連続適用して合成

filtergraph_split3.txt:

text# ============================================
# 3分割画面レイアウト
# ============================================

# ベースとなる黒背景キャンバス作成
color=c=black:s=1920x1080:d=10[bg];
text# 各映像のスケーリング(横幅640に統一)
[0:v]scale=640:1080,setsar=1[v0];
[1:v]scale=640:1080,setsar=1[v1];
[2:v]scale=640:1080,setsar=1[v2];
text# 1つ目の映像を左端に配置
[bg][v0]overlay=x=0:y=0[tmp1];
text# 2つ目の映像を中央に配置
[tmp1][v1]overlay=x=640:y=0[tmp2];
text# 3つ目の映像を右端に配置
[tmp2][v2]overlay=x=1280:y=0[vout]

実行コマンド:

bashffmpeg -i video1.mp4 -i video2.mp4 -i video3.mp4 \
  -filter_complex_script filtergraph_split3.txt \
  -map "[vout]" \
  -c:v libx264 -preset medium -crf 23 \
  output_split3.mp4

ケース 3: 音声ミックスとフェード

複数の音声を合成し、映像にフェードエフェクトを適用します。

要件:

  • メイン音声と BGM をミックス(音量比 7:3)
  • 映像の最初と最後にフェード効果を適用
  • フェードイン: 0-1 秒
  • フェードアウト: 最後の 1 秒

以下の図は、音声処理のフローを示しています。

mermaidflowchart LR
  audio1["メイン音声<br/>音量 0.7"] --> mix["amix"]
  audio2["BGM<br/>音量 0.3"] --> mix
  mix --> aout["[aout]<br/>ミックス済み音声"]

filtergraph_fade_audio.txt:

text# ============================================
# 映像処理: フェードイン・フェードアウト
# ============================================

# フェードイン(0-1秒、30フレーム)
# フェードアウト(最後の1秒、30フレーム)
# ※ st=570 は 19分30秒の位置を想定(実際の長さに応じて調整)
[0:v]fade=in:0:30,fade=out:st=570:d=30[vout];
text# ============================================
# 音声処理: ミックス
# ============================================

# メイン音声の音量を70%に調整
[0:a]volume=0.7[a0];
text# BGMの音量を30%に調整
[1:a]volume=0.3[a1];
text# 2つの音声をミックス
# duration=first: メイン音声の長さに合わせる
[a0][a1]amix=inputs=2:duration=first[aout]

実行コマンド:

bashffmpeg -i main.mp4 -i bgm.mp3 \
  -filter_complex_script filtergraph_fade_audio.txt \
  -map "[vout]" -map "[aout]" \
  -c:v libx264 -preset medium -crf 23 \
  -c:a aac -b:a 192k \
  output_fade_audio.mp4

ケース 4: 動的テキストオーバーレイ

時間経過に応じて表示内容が変わるテキストを映像に重ねます。

要件:

  • 0-5 秒: "Introduction" を表示
  • 5-10 秒: "Main Content" を表示
  • 10 秒以降: "Conclusion" を表示

filtergraph_text_dynamic.txt:

text# ============================================
# 動的テキストオーバーレイ
# ============================================

# フォント設定の共通定義
# fontfile: フォントファイルのパス(環境に応じて変更)
# fontsize: 48px
# fontcolor: 白
# x, y: 画面中央
text# 0-5秒: "Introduction" を表示
[0:v]drawtext=fontfile=/System/Library/Fonts/Hiragino\ Sans\ W6.ttc:text='Introduction':fontsize=48:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:enable='between(t,0,5)'[txt1];
text# 5-10秒: "Main Content" を表示
[txt1]drawtext=fontfile=/System/Library/Fonts/Hiragino\ Sans\ W6.ttc:text='Main Content':fontsize=48:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:enable='between(t,5,10)'[txt2];
text# 10秒以降: "Conclusion" を表示
[txt2]drawtext=fontfile=/System/Library/Fonts/Hiragino\ Sans\ W6.ttc:text='Conclusion':fontsize=48:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2:enable='gte(t,10)'[vout]

実行コマンド:

bashffmpeg -i input.mp4 \
  -filter_complex_script filtergraph_text_dynamic.txt \
  -map "[vout]" \
  -c:v libx264 -preset medium -crf 23 \
  output_text_dynamic.mp4

ケース 5: 複数エフェクトの段階的適用

スケーリング、クロップ、色調補正、シャープネス適用を順次実行します。

要件:

  • 4K 映像を Full HD にスケーリング
  • 上下の黒帯をクロップ(2.35:1 シネスコ風)
  • 明度とコントラストを調整
  • 軽いシャープネスを適用

filtergraph_multi_effects.txt:

text# ============================================
# 複数エフェクトの段階的適用
# ============================================

# ステップ1: 4K → Full HD スケーリング
[0:v]scale=1920:1080[v_scaled];
text# ステップ2: シネスコサイズにクロップ
# 1920x817(アスペクト比 2.35:1)
# y=131.5: 上下均等に切り取り((1080-817)/2)
[v_scaled]crop=1920:817:0:131.5[v_cropped];
text# ステップ3: 色調補正
# brightness: 明度を10%アップ
# contrast: コントラストを5%アップ
# saturation: 彩度を5%アップ
[v_cropped]eq=brightness=0.1:contrast=1.05:saturation=1.05[v_color];
text# ステップ4: シャープネス適用
# luma_msize_x, luma_msize_y: マトリックスサイズ 5x5
# luma_amount: 強度 0.5(軽め)
[v_color]unsharp=luma_msize_x=5:luma_msize_y=5:luma_amount=0.5[vout]

実行コマンド:

bashffmpeg -i input_4k.mp4 \
  -filter_complex_script filtergraph_multi_effects.txt \
  -map "[vout]" \
  -c:v libx264 -preset slow -crf 18 \
  output_cinema.mp4

以下の図は、エフェクト適用の流れを示しています。

mermaidflowchart TD
  input["入力<br/>3840x2160"] --> step1["スケーリング<br/>1920x1080"]
  step1 --> step2["クロップ<br/>1920x817"]
  step2 --> step3["色調補正<br/>brightness/contrast"]
  step3 --> step4["シャープネス<br/>unsharp"]
  step4 --> output["出力<br/>1920x817"]

図で理解できる要点:

  • 各処理が独立したステップとして定義されている
  • 処理順序が明確で、デバッグや調整が容易
  • 不要なステップをコメントアウトすることで、部分的なテストが可能

まとめ

FFmpeg のフィルタグラフをファイル化することで、複雑な映像処理が格段に扱いやすくなります。 この記事では、以下のポイントを解説しました。

#ポイント効果
1filtergraph ファイルの基本構文コメントや改行で可読性が向上
2セクション分割とラベル命名処理の流れが追いやすくなる
3テンプレート化とパラメータ管理再利用性が高まり、チーム共有が容易に
4実践的な具体例実際のユースケースに即座に応用可能

長大なコマンドラインに悩んでいた方は、ぜひ filtergraph ファイルを活用してみてください。 可読性・メンテナンス性・再利用性のすべてが改善され、FFmpeg での映像編集がより快適になるはずです。

Git でバージョン管理すれば、変更履歴の追跡や複数人での共同編集もスムーズに行えます。 チーム開発においても、filtergraph ファイルは強力な武器となるでしょう。

関連リンク