ビデオカラーグレード(評価→補正→ルック→選択→LUT+適用)
グレードをコードとしてゼロから作成し、実際の映像上で複数の異なる名前付きルックを生成し、明示的な人間の選択または委任されたエージェントの判断で1つを選択し、ポータブルな.cube LUTをベイクしてフルクリップに適用します。プリセットパックは使いません。
中核となる考え方 — まず補正、次にスタイル。 すべてのルックは補正ベース + クリエイティブ層です:
- 補正ベース は映像を素直にします — フラットなLOG/HLGの場合はlog→Rec.709変換、すでにRec.709の映像の場合はWBニュートラル化+露出+マイルドなコントラストです。 すべてのルックがこのベースを共有するため、「ニュートラル」は意味を持ち、ルックは意図によって異なります。
- クリエイティブ層 はその上に重ねるルックです(ウォーム/クール/ティールオレンジ/パンチィ/フェードなど)。
肌が品質基準です。 人物 — 特に肌の色が濃い人 — に対して、teal_orange や cool_desat はしばしば緑/灰色のキャストを発生させます。まず顔で全てのルックを評価し、中間調の暖かさを維持し、ティントをシャドウ/ハイライト/背景に押し込み、生のsaturationよりもvibranceを優先します。ホワイトバランスを完全にニュートラルにまで追い込まないでください。肌が死にます。
必要条件:ffmpeg/ffprobe がPATHに存在すること、Python に numpy + Pillow がインストールされていること。
契約
- すべてのルック =
ベース + クリエイティブ層、ベースは全ルックで共有。 - すべてのルックは同じ代表フレーム(公平で制御された比較用)に、ラベル付きでフル解像度でレンダリングされます。
- フル適用では**
-c:a copy(音声は再エンコードされない → A/V同期は同一)を使用し、ソースの長さ、fps、寸法**を維持します。 - 納品前にルック選択を完了します。ユーザーが選択を求めた場合は
humanモードを使用し、ユーザーが選択を委任したか自動実行を要求した場合はagentモードを一時停止なしで使用します。両方のモードで、永続的な計画に rationale(根拠)を保存します。
プロジェクトプロトコルワークフロー
永続的な決定を work/color-grade/grade-plan.json に保存します:
{
"schema_version": 1,
"target": "base-video",
"base": "eq=contrast=1.05",
"looks": [{"name": "clean", "chain": "null"}],
"selected_look": "clean",
"selection_mode": "agent",
"selection_rationale": "Neutral correction preserves natural skin and source lighting.",
"selected_lut": "../../final/selected-color-look.cube",
"evidence_refs": ["media:source"]
}
デフォルトでは base-video を使用し、コンテンツカードやキャプションが再着色されないようにします。composite は、すでに合成されたピクセルを意図的にグレーディングする場合にのみ有効です。共有の work/understand/media.json と視覚的エビデンスを読み込みます。review/02-color-grade/choose-color-look.jpg と review/02-color-grade/skin-tone-check.jpg を生成し、オプションで短い selected-look-preview.mp4 を生成します。決定とエビデンスを review/02-color-grade/selected-look.md に記録します。
明示的なユーザー選択の後は selection_mode を human に設定します。ユーザーが決定を委任するか、中断のないワークフローを要求した場合は agent に設定し、肌色、ハイライト保持、ニュートラルバランスを優先して保守的に選択します。常に空でない selection_rationale を記述し、agent モードでは一時停止しないでください。
選択された LUT を final/selected-color-look.cube にベイクします。その LUT が出荷されるときは、grade plan の selected_lut を設定して、共有レンダラーがその正確なファイルを消費するようにします。ベイクされたフルルック LUT にはすでに補正ベースが含まれています。レンダラーは base + lut3d ではなく、lut3d のみを適用する必要があります。
操作の正確な入力リビジョンを based_on に記録し、計画または選択された入力が変更されたときにその整数 revision をインクリメントし、レビューファイルが完了した後にのみ操作チェックを pass に設定し、完全なプロトコルフィールドを提供します:
{
"target": {"sequence": "main", "scope": "base-video"},
"effects": {
"changes_timeline": false,
"changes_geometry": false,
"changes_video_pixels": true,
"changes_audio": false,
"adds_track": null
},
"check": {
"status": "pass",
"report": "../review/02-color-grade/selected-look.md"
},
"render": {
"kind": "video-filter",
"target": "base-video",
"plan": "color-grade/grade-plan.json"
}
}
共有レンダラーは selected_look が存在することを検証し、タイムライン変換後、オーバーレイ前にグレードを適用し、最終ビデオを一度エンコードします:
python skills/video-understand/scripts/build_render_plan.py .
python skills/video-understand/scripts/render_project.py work/render/render-plan.json
スタンドアロン互換性のための入力
- ソースビデオ。
looks.json(base+ 名前付きlooks);looks.example.jsonから始めて、assess.pyが報告する内容に基づいてbaseを再調整します。
既存のすべてのスクリプトは、引き続き従来の looks.json を受け入れます。gradelib.load_spec() は正規の grade-plan.json も受け入れるため、コンタクトシート、クリップ、LUT、スタンドアロン適用コマンドは移行中も互換性を保ちます。
looks.json スキーマ
{
"base": "<補正ベース:log→Rec.709 変換、または通常の映像用の WB+露出+コントラスト>",
"looks": [
{ "name": "clean_neutral", "label": "1 · CLEAN NEUTRAL", "desc": "補正のみ", "chain": "eq=contrast=1.02" },
{ "name": "teal_orange", "label": "4 · TEAL & ORANGE", "desc": "シネマティック", "chain": "colorbalance=...,eq=..." }
// ルックの完全な ffmpeg フィルター = base + "," + chain。チェーンを単独で使用するには "prepend_base": false を設定。
]
}
ワークフロー
-
評価 — 決して盲目的にグレーディングしないでください。
python scripts/assess.py SOURCE.mp4 --out work/log-vs-Rec.709、ホワイトバランスのキャスト(128に対する signalstats U/V)、露出(Y)を報告し、代表フレーム(
work/assess/frame.png)を選び、ベース調整案を提示します。それに応じてlooks.jsonのbaseを再調整します(例:暖色キャスト → 少し青を追加;log → log→709 変換をbaseに入れる)。- 代表フレームは顔ではなく、輝度の中央値で選ばれます。 シングルショットのトーキングヘッドでは通常良い顔フレームになりますが、Bロールが多い/マルチショットの映像では顔のないフレームを選ぶ可能性があります — その場合
face_skin_check.pngは役に立ちません(肌が品質基準です)。既知の顔の瞬間を--frame-time SECONDSで強制します。 - WB/露出の統計は、再生時間の10%の位置にある約10秒間の
signalstatsウィンドウから取得されます — 一定の照明には問題ありませんが、照明が変化する映像の場合は--win-start SECONDS/--win-dur SECONDSで移動/延長します。
- 代表フレームは顔ではなく、輝度の中央値で選ばれます。 シングルショットのトーキングヘッドでは通常良い顔フレームになりますが、Bロールが多い/マルチショットの映像では顔のないフレームを選ぶ可能性があります — その場合
-
選択用にルックをレンダリングします。
python scripts/render_looks.py work/assess/frame.png looks.json --out work/lookswork/looks/looks_compare.png(オリジナル+全ルック、ラベル付き、フル解像度)とface_skin_check.png(拡大肌ストリップ)を生成します。両方を見てください。 肌が基準です — 肌を灰色/緑にするルックは再調整するか削除してから再レンダリングします。被写体が中央にいない場合は--face-crop W:H:X:Yを追加します。 これらのPNGを作業中の中間ファイルとして扱い、承認されたレビューコピーをreview/02-color-grade/choose-color-look.jpgとreview/02-color-grade/skin-tone-check.jpgとして公開します。 -
(オプション) モーションプレビュー 上位2〜3のルックを動きで確認します(音声は保持):
python scripts/make_clips.py SOURCE.mp4 looks.json --out work/clips \ --pick clean_neutral,warm_filmic,teal_orange --with-original --ss 148 --t 8 -
1つのルックを選択します。 ユーザーが選択を求めた場合のみ、コンタクトシートを提示して待機します。それ以外の場合は agent モードで選択し、決定のための一時停止なしで続行します。納品前に
selected_look、selection_mode、selection_rationaleを記述します。 -
選択後 — LUTをベイクしてフルクリップに適用します。
python scripts/bake_lut.py looks.json --name CHOSEN \ --out final/selected-color-look.cube --verify-frame work/assess/frame.png python scripts/apply_grade.py SOURCE.mp4 --looks looks.json --name CHOSEN --out out/graded.mp4 # または配送されたLUTを適用:--lut final/selected-color-look.cubeapply_grade.pyは長さ/音声が保持されていることを確認し、WBシフト(U/Vが128に向かう)を表示し、スポットフレームを出力して目視確認できるようにします。- 適用ではビデオを一度再エンコードします(ロスあり);音声は
-c:a copyです。 品質/時間は--crf(デフォルト18)と--preset(デフォルトslow)で設定します。slowは小規模/短いクリップには適していますが、1080p/4Kの長時間映像では大きな時間コストがかかります — 配信時は--preset mediumに下げ(クイックプレビューにはveryfast)、最終版のみslowにします。 - 配信ビデオを配信LUTに一致させる: チェーン(
--name)とベイクされた33³の.cube(--lut)は、急峻なカーブで最大約5/255異なる可能性があります(bake_lut.pyはこのmax|d|を報告します)。ほとんどの作業では目に見えませんが、.cubeを出荷する場合でレンダリングされたビデオをそれに正確に一致させたい場合は、--nameではなく--lut final/selected-color-look.cubeで適用します。
- 適用ではビデオを一度再エンコードします(ロスあり);音声は
設計ノート / 落とし穴
- クリーンカットをグレーディングし、グレーディングされた映像の上にコンポジットオーバーレイを重ねます。 すでに焼き込みキャプション/カードがあるビデオをグレーディングしなければならない場合、
assess.pyを信頼してはいけません:そのWB/露出統計とフレーム選択はグラフィックスによって歪められます(暗い上下スクリムはYを引き下げ、明るいキャプションテキスト/ティールアクセントはU/Vを歪め、フレーム選択がキャプションの多いフレームにランディングする可能性があります)— 映像ではなくオーバーレイを補正することになります。クリーン映像で調整されたbaseを再利用し、グレードがオーバーレイに色味を付けることを想定して、微妙な/補正的なルック(clean_neutral)のみを使用します;teal_orange/vintage_fadedはテキスト/アクセントを明らかに再着色します。 - 1つのルック/LUTがすべての関連クリップをカバーします。 ルックを選択したら、すべての関連クリップ(例:クリーンカット+オーバーレイ版)で同じ
looks.json/.cubeを再利用します — クリップごとに2フェーズの選択を再実行しないでください。 - まず補正、次にスタイル。 ベースはデッキのS-Log3→Rec.709ステップの一般化です。通常の映像ではベースはWB+露出+コントラストです。log映像の場合はそこに変換を入れます。
- 肌が最優先。
teal_orange/cool_desatは、肌の色が濃い被写体で通常の問題となるルックです — 中間調を暖かく保ち、シャドウ/ハイライト/bgにティントを入れ、vibranceを使用します。U/Vを完全にニュートラルにしないでください。 - ラベルはffmpegによってオーバーレイされるPIL PNGです —
drawtextは使用しません。 Windowsではdrawtextのfontfileパス(ドライブコロン)が信頼できません。フォントはgradelib.pyの先頭で設定されます。 - ドライブコロンパスは、パスを受け取るffmpegフィルターグラフオプション(
lut3d、metadata=print:file=)を壊します。 スクリプトはcwdをファイルのフォルダに設定してffmpegを実行し、ベース名で参照します。新しいパスを受け取るフィルターではこのパターンを維持してください。 - 出力は渡した
--outディレクトリ(永続的)に格納され、システムの一時ディレクトリには格納されません。 - 顔/肌ストリップは、顔検出ではなく、固定された中央上部のクロップを使用します。 被写体がおおよそ中央にいない場合は、
render_looks.pyに--face-crop W:H:X:Yを渡してください。そうしないと、肌ストリップが間違った領域を表示します。 apply_grade.pyの検証用スポットフレームは再生時間の20%の位置にあります — ルックが選択されたフレームとは異なるフレームです。その20%のポイントに顔がない場合は、代わりに別のフレームで肌を目視確認します(または--frame-timeをassess.pyに渡して、選択されたフレームが既知の顔の瞬間になるようにします)。- LUTベイクは正確です — ここでのすべてのフィルターはピクセルごとのポイント操作だからです:
bake_lut.pyは33³のアイデンティティグリッドを実際のチェーンに押し込み、それに対して自己チェックします(mean|d|は1/255を十分に下回るはずです。数レベルのmax|d|は単なる補間です)。 - 常に納品前に選択を行います。 人間の選択と委任されたエージェントの選択は両方とも有効ですが、計画にはどちらが発生したかとその理由を記録する必要があります。