Claude Media
Claude Codeでffmpegを使う — 権限設計とffprobeで変換結果を検証する

Claude Codeでffmpegを使う — 権限設計とffprobeで変換結果を検証する

Claude Codeにffmpegの変換コマンドを組ませるとき、上書きオプション-yを拒否して実行前に確認させる権限設定と、ffprobeで出力を検証するStop hookを説明します。

ffmpegの変換コマンドをClaude Codeに組み立てさせるなら、決めておくことは2つです。実行前に人が確認できる権限設定と、できあがったファイルをffprobeで検査する完了条件。この2つがあれば、動画や音声の変換を安心して任せられます。

ffmpegは1行のコマンドで入力を壊さずに別形式へ変換できる一方、オプションの置き方ひとつで結果が変わります。同名ファイルを上書きするオプションもあります。Claudeが書いたコマンドを毎回目で追うのは大変なので、確認の関所と自動検証を設定側に置きます。

ffmpegで事故になりやすい3つの挙動

ffmpegのドキュメントから、Claudeに任せるときに効いてくる挙動を3つ拾います。

1つ目は上書きです。-yは出力ファイルが既にあっても確認なしで上書きします。逆に-nは、指定した出力ファイルが既にあると何もせず終了します。生成されたコマンドに-yが混ざると、元素材と同じ名前を出力に指定した場合に素材ごと失われます。

2つ目は標準入力です。ffmpegは標準入力が入力として使われていない限り、対話操作を受け付ける状態で動きます。これを止めるのが-nostdinです。バックグラウンドで動かすときなどに、入力待ちで止まるのを避けられます。

3つ目は出力量です。既定では著作権表示とビルド設定、ライブラリのバージョンが毎回表示されます。-hide_bannerで消せますし、-loglevelでログの詳しさも絞れます。変換のたびに長いログがセッションに流れ込むと、Claudeが読むコンテキストを無駄に使います。

ffmpeg -hide_banner -loglevel error -nostdin -n \
  -i input.mov -c:v libx264 -c:a aac output.mp4

出力ファイルは入力と違う名前にして-nを付けておけば、既存ファイルを壊す経路をコマンド側でも塞げます。この形をCLAUDE.mdに書いておくと、Claudeが毎回同じ型で組み立てます(後述)。

ffmpegを確認付き、ffprobeを自動許可にする

権限は、読むだけのffprobeを許可し、ファイルを書くffmpegは毎回確認に回します。さらに上書きオプションの-y付きは拒否します。

{
  "permissions": {
    "allow": ["Bash(ffprobe *)"],
    "ask": ["Bash(ffmpeg *)"],
    "deny": [
      "Bash(ffmpeg -y *)",
      "Bash(ffmpeg * -y *)",
      "Bash(ffmpeg * -y)"
    ]
  }
}

Claude Codeの権限は、deny、ask、allowの順で評価され、最初に当たったものが結果を決めます。ffmpegのコマンドがaskとdenyの両方に当たるときはdenyが勝ちます。-yを3通り書いているのは、ffmpegが-yをコマンドのどこに置いても受け付けるためです。先頭、途中、末尾の3つを別々のルールで押さえます。

Bashルールの*は空白を含む任意の文字列に当たり、ルール先頭から*の直前までは書かれたとおりに照合されます。Bash(ffmpeg *)はffmpegのコマンド全般、Bash(ffprobe *)はffprobeのコマンド全般に当たる、ということです。

ffprobeを明示的に許可しているのは、組み込みの読み取り専用コマンドの一覧がlsやcat、grep、diffなどで、ffprobeをその例には挙げていないためです。許可ルールがないと、検証のたびに確認が出ます。

複合コマンドとループも確認の対象になる

変換を何本もまとめるとき、Claudeはシェルのforループや&&でつなぎます。askルールとdenyルールは、複合コマンドのどの部分に一致しても適用されます。forループの本体や、コマンド置換の中も同じです。つまり次のようなループも、ffmpegの部分で確認に回ります。

for f in clips/*.mov; do ffmpeg -nostdin -n -i "$f" "out/${f##*/}.mp4"; done

一方、allowルールは複合コマンドの各部分に個別に一致する必要があります。ffprobeだけを許可していても、ffprobe ... && rm output.mp4のrm側は別に判定されます。

権限ルールが止められない書き方

権限ルールはClaudeが書いたコマンドの文字列に対する照合なので、同じプログラムを別の形で呼ぶと当たりません。たとえばdenyにBash(rm *)を置いても、/bin/rmやbash -c 'rm ...'は止まりません。ffmpegの-y拒否も同じで、/usr/bin/ffmpeg -y ...やsh -c 'ffmpeg -y ...'は上のルールをすり抜けます。

ルールは「Claudeが普段書く形」を止める防波堤として使い、書き込み先を本当に制限したいときはサンドボックスで補います。選び方はClaude Codeのサンドボックスの選び方にあります。ルールの書き方全体は/permissionsコマンドの解説が手がかりになります。

ほかのCLIで同じ型(読み取りだけ許可し、書き込みは別枠に置く)を組んだ例は、OpenTofuのvalidateとplanだけを許可する権限設計で読めます。

確認画面で見る4点

askにしたffmpegのコマンドは、実行前にプロンプトとして表示されます。ここで見るのは次の4点です。

ffmpegコマンドの確認チェック

  • 出力ファイルの名前が入力と別か(上書きにならないか)
  • -c copyか再エンコードか(再エンコードは画質が落ちる)
  • -mapで必要なストリームだけを選んでいるか
  • -ssや-tの位置が入力側か出力側か

再エンコードについては、ffmpegのドキュメントが「デコードして再度エンコードするとストリームの品質は多くの場合劣化する。変換が必要なときだけ行い、それ以外はストリームコピーにする」と説明しています。フィルタをかけたいときや、再生側が元のコーデックを扱えないときが、変換を選ぶ典型です。

よく使う組み立ての型

ドキュメントの例を、Claudeに頼む用途別に並べます。

用途コマンドの骨格見るところ
コンテナだけ替えるコマンドの骨格ffmpeg -i INPUT.mkv -map 0:1 -c copy OUTPUT.mp4見るところ再エンコードなし、入力ストリーム番号の指定
映像だけ再エンコードコマンドの骨格-map 0:v -map 0:a -c:v libx264 -c:a copy見るところ音声はコピーで劣化なし
音声だけ取り出すコマンドの骨格-vn を付ける見るところ映像ストリームを出力に含めない
一部分だけ切り出すコマンドの骨格-ss と -t または -to見るところ-tと-toは同時に使えず-tが優先
動画から静止画コマンドの骨格-r 1 -f image2 foo-%03d.jpeg見るところ1秒に1枚、連番で出力

-ssは入力ファイルの前に置くとその位置へシークし、出力の前に置くと入力をデコードしたうえで位置に達するまで捨てます。入力側のシークは多くの形式で正確な位置に飛べず、直前のシークポイントに移ります。再エンコードのときは既定でその差分をデコードして捨てるので、切り出し位置のずれは小さくなります。ストリームコピーでは差分が残ります。切り出し系の依頼では、確認画面で-ssがどちらに置かれているかを見ます。

-tは入力側に置くと読み込む長さを、出力側に置くと書き出す長さを制限します。-toは位置を指定する別の書き方で、-tと同時には使えません。両方指定されたら-tが優先されます。

ffprobeで出力を検証する

変換が終わっても、ファイルができたことと正しくできたことは別です。ffprobeは入力ファイルのコンテナ情報とストリーム情報を表示するので、完了の確認に向いています。

ffprobe -v error -show_entries format=duration:stream=index,codec_type,codec_name \
  -of json output.mp4

-show_entriesは、セクション名の後ろに=と項目名のリストを書く形式です。セクションは:で区切って複数指定でき、formatセクションとstreamセクションを同時に絞り込めます。-of(-output_format、-print_formatも同じ)で出力形式をjsonなどに切り替えられ、Claudeが結果を読みやすくなります。-v errorはログをエラーだけに絞ります。

検証で見る項目は、変換の種類ごとに決めておきます。

  • 映像と音声のストリームが想定どおり残っているか(codec_type)
  • コーデックが指定どおりか(codec_name)
  • 長さが入力と近いか(duration)。切り出しなら指定した長さに近いか
  • ffprobeが値を返しているか。出力が空なら、読み取りに失敗したと扱えます

音声だけを調べたいときは-select_streams aで音声ストリームに限定できます。フレーム数まで数えたいなら-count_framesがありますが、全フレームを読むので長い動画では時間がかかります。

値だけを取り出して数値比較したいときは、defaultライターのnoprint_wrappersとnokeyを使います。

ffprobe -v error -show_entries format=duration \
  -of default=noprint_wrappers=1:nokey=1 output.mp4

検証をStop hookで必須にする

「変換が終わったらffprobeで確認する」をCLAUDE.mdに書くだけだと、Claudeが省略する余地が残ります。Stop hookにすると、確認に失敗している間はClaudeが作業を終えられなくなります。

Stopイベントのコマンドhookは、終了コード2を返すとClaudeを作業に戻します。標準エラーに書いた理由が、次の作業の手がかりになります。検証スクリプトの例です。

#!/bin/bash
# .claude/hooks/check-media.sh
INPUT=$(cat)
if [ "$(echo "$INPUT" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0
fi
fail=0
for f in out/*.mp4; do
  [ -e "$f" ] || continue
  dur=$(ffprobe -v error -show_entries format=duration \
    -of default=noprint_wrappers=1:nokey=1 "$f")
  vid=$(ffprobe -v error -select_streams v -show_entries stream=codec_type \
    -of default=noprint_wrappers=1:nokey=1 "$f")
  if [ -z "$dur" ] || [ -z "$vid" ]; then
    echo "$f: ffprobeで長さか映像ストリームを読めません。変換をやり直してください" >&2
    fail=1
  fi
done
exit $fail

先頭のstop_hook_activeの確認は、hookのブロックが連続するときの暴走を防ぐためのものです。Claude Codeは、ツール呼び出しを挟まずにStop hookが8回連続でブロックすると、hookを上書きして停止を許可します。スクリプト側でも一度続行させたあとは通すように書いておきます。上限の変更はCLAUDE_CODE_STOP_HOOK_BLOCK_CAPの記事で扱っています。

設定側は、Stopイベントにコマンドhookとして登録します。

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": ".claude/hooks/check-media.sh" }
        ]
      }
    ]
  }
}

このスクリプトはout/ディレクトリの.mp4だけを見ます。出力先をout/に固定する規約をCLAUDE.mdに書いておくのが前提です。テストを回すStop hookとの使い分けはClaude Code hooksでテストを自動実行するで比較できます。

公式のhooksガイドには、Stop hookにtype: "agent"を使い、サブエージェントに「テストを実行して結果を確認する」ことを任せる例もあります。ファイルの実状態を調べて判定したい検証には向きますが、agent hookは実験的な機能で、挙動や設定が将来のリリースで変わる可能性があると注記されています。本番の流れには、まずコマンドhookのほうが安定します。

CLAUDE.mdに書く作業手順

権限とhookが「止める仕組み」なら、CLAUDE.mdは「最初から正しい形で組ませる」ための規約です。

## ffmpegの作業手順
 
- 変換前に、`ffprobe -v error -show_entries format=duration:stream=codec_type,codec_name -of json`で入力を確認する
- コマンドは `ffmpeg -hide_banner -loglevel error -nostdin -n` で始める
- 出力は `out/` に、入力と別の名前で書く。`-y` は使わない
- 音声や映像を変えないなら `-c copy`。再エンコードする理由を一言書く
- 変換後は同じffprobeで出力を確認し、長さとストリームの数を入力と比べる

-yを使わないと明記しておけば、Claudeはdenyに当たる形をそもそも出しません。denyルールは保険として働きます。

つまずきやすい点

落とし穴

denyに当たらない形のffmpegを実行してしまう

/usr/bin/ffmpeg -y ...のようにパスを付けた呼び出しや、bash -cで包んだ呼び出しは、Bash(ffmpeg -y *)では止まりません。askルールにも同じ穴があります。確認を確実にしたいなら、サンドボックスで書き込める場所をout/に限る構成を併用します。

次の点も、実際に詰まりやすいところです。

  • ffprobeを許可し忘れる。検証のたびに確認が出て、自動化の意味が薄れます。allowにBash(ffprobe *)を置きます。
  • 出力が入力と同名になる。 -nを付けていれば既存ファイルがあるときにffmpegが終了するので、素材は壊れません。ただし、その場合に変換が実行されなかったことをClaudeが気づける形でログを残す必要があります。-loglevel errorのままでもエラーとして表示されます。
  • -tと-toを両方書く。同時には使えず-tが優先されます。切り出し範囲が意図とずれる原因になるので、確認画面で片方だけになっているかを見ます。
  • 長い動画で-count_framesを使う。全フレームを読むので時間がかかります。長さとストリーム構成の確認だけなら不要です。

まとめ

ffmpegは、書き込みが伴うコマンドなので確認に回し、読み取りのffprobeを自動で許可する、という分け方が出発点です。-yのような上書きオプションはdenyで押さえ、押さえきれない呼び出し形はサンドボックスで補います。

仕上げの検証はStop hookに置くと、ffprobeで確認できていない状態でClaudeが作業を終えることがなくなります。CLAUDE.mdには、コマンドの型と出力先の規約だけを書けば足ります。

この記事を共有:XはてブLinkedIn