Video-Farbkorrektur (bewerten → korrigieren → Looks → auswählen → LUT + anwenden)
Schreibe die Korrektur von Grund auf als Code, generiere mehrere verschiedene benannte Looks auf dem echten
Filmmaterial, wähle einen durch explizite menschliche Entscheidung oder delegierte Agentenentscheidung aus, erstelle dann ein
portables .cube-LUT und wende es auf den gesamten Clip an. Keine Voreinstellungspakete.
Kernidee — korrigieren, dann stylen. Jeder Look ist korrektive Basis + kreative Schicht:
- korrektive Basis macht das Filmmaterial ehrlich — für flaches LOG/HLG ist es die Log→Rec.709 Konvertierung; für bereits Rec.709-korrigiertes Material ist es der Weißabgleich neutralisieren + Belichtung + dezenter Kontrast. Jeder Look teilt sich diese Basis, sodass ein "neutraler" Look sinnvoll bleibt und sich die Looks durch die Absicht unterscheiden.
- kreative Schicht ist der darauf aufgesetzte Look (warm / kühl / Teal-Orange / knallig / verblasst…).
Haut ist das Qualitätskriterium. Bei Personen — besonders bei dunklerer Haut — werfen teal_orange und
cool_desat regelmäßig einen Grün-/Graustich. Beurteile jeden Look zuerst an den Gesichtern; halte die Mitteltöne warm,
verschiebe den Farbstich in die Schatten/Lichter/Hintergrund, bevorzuge vibrance gegenüber roher
saturation. Treibe den Weißabgleich nicht ganz auf neutral, sonst wird die Haut leblos.
Voraussetzungen: ffmpeg/ffprobe im PATH, Python mit numpy + Pillow.
Vertrag
- Jeder Look =
Basis + kreative Schicht; die Basis wird von allen Looks geteilt. - Alle Looks werden auf dem gleichen repräsentativen Frame (ein fairer, kontrollierter Vergleich) gerendert, beschriftet, in voller Auflösung.
- Die vollständige Anwendung verwendet
-c:a copy(Audio wird nie neu kodiert → A/V-Synchronisation identisch) und behält die Dauer, fps, Abmessungen der Quelle bei. - Vollständige Look-Auswahl vor der Lieferung. Verwende den
human-Modus, wenn der Benutzer gebeten wurde, zu wählen; verwende denagent-Modus ohne Pause, wenn der Benutzer die Wahl delegiert oder einen autonomen Durchlauf angefordert hat. Bewahre in beiden Modi die Begründung im dauerhaften Plan.
Projektprotokoll-Workflow
Speichere die dauerhafte Entscheidung in 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": "Neutrale Korrektur bewahrt natürliche Haut und Quelllicht.",
"selected_lut": "../../final/selected-color-look.cube",
"evidence_refs": ["media:source"]
}
Verwende standardmäßig base-video, damit Inhaltskarten und Untertitel nicht neu eingefärbt werden. composite
ist nur gültig, wenn bereits zusammengesetzte Pixel absichtlich korrigiert werden. Lese die gemeinsame
work/understand/media.json und visuelle Beweise. Erstelle
review/02-color-grade/choose-color-look.jpg und
review/02-color-grade/skin-tone-check.jpg; erstelle optional die kurze
selected-look-preview.mp4. Dokumentiere die Entscheidung und die Beweise in
review/02-color-grade/selected-look.md.
Setze selection_mode auf human nach einer expliziten Benutzerwahl. Setze es auf agent, wenn der
Benutzer die Entscheidung delegiert oder einen ununterbrochenen Workflow anfordert, und wähle konservativ mit
Hautton, Lichtererhaltung und neutralem Gleichgewicht als Prioritäten. Schreibe immer eine nicht-leere
selection_rationale; pausiere im Agenten-Modus nicht.
Erstelle das ausgewählte LUT unter final/selected-color-look.cube. Wenn dieses LUT ausgeliefert wird, setze
selected_lut im Korrekturplan, damit der gemeinsame Renderer genau diese Datei verwendet. Ein gebackenes
Vollformat-LUT enthält bereits die korrektive Basis; der Renderer muss nur lut3d anwenden,
nicht Basis + lut3d.
Dokumentiere die genauen Revisionen der Eingabeoperation in based_on, erhöhe deren ganzzahlige
revision, wenn sich der Plan oder die ausgewählte Eingabe ändert, setze die Operationsprüfung auf pass
erst nach Abschluss der Review-Dateien und füge die vollständigen Protokollfelder hinzu:
{
"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"
}
}
Der gemeinsame Renderer validiert, dass selected_look existiert, wendet die Korrektur nach den
Timeline-Transformationen und vor den Overlays an und kodiert das endgültige Video einmal:
python skills/video-understand/scripts/build_render_plan.py .
python skills/video-understand/scripts/render_project.py work/render/render-plan.json
Standalone-Kompatibilitätseingaben
- Das Quellvideo.
- Eine
looks.json(base+ benanntelooks); beginne mitlooks.example.jsonund stimme diebasebasierend auf dem ab, wasassess.pymeldet.
Alle vorhandenen Skripte akzeptieren weiterhin das alte looks.json. gradelib.load_spec()
akzeptiert auch das kanonische grade-plan.json, sodass Kontaktbogen-, Clip-, LUT- und Standalone-Anwendungsbefehle
während der Migration kompatibel bleiben.
looks.json-Schema
{
"base": "<korrektive Basis: Log→Rec.709 Konvertierung ODER WB+Belichtung+Kontrast für normales Filmmaterial>",
"looks": [
{ "name": "clean_neutral", "label": "1 · CLEAN NEUTRAL", "desc": "nur korrigiert", "chain": "eq=contrast=1.02" },
{ "name": "teal_orange", "label": "4 · TEAL & ORANGE", "desc": "filmisch", "chain": "colorbalance=...,eq=..." }
// Ein vollständiger ffmpeg-Filter eines Looks = base + "," + chain. Setze "prepend_base": false, um chain standalone zu verwenden.
]
}
Workflow
-
Bewerten — niemals blind korrigieren.
python scripts/assess.py QUELLE.mp4 --out work/Berichtet Log-vs-Rec.709, Weißabgleich-Abweichung (signalstats U/V vs 128), Belichtung (Y), wählt einen repräsentativen Frame (
work/assess/frame.png) und schlägt Basis-Anpassungen vor. Stimme dielooks.jsonbaseentsprechend nach (z. B. warmer Farbstich → etwas Blau hinzufügen; Log → die Log→709-Konvertierung inbasesetzen).- Der repräsentative Frame wird nach medianer Luminanz gewählt, nicht nach Gesichtern. Bei einem
Einzel-Talking-Head landet das normalerweise auf einem guten Gesichts-Frame, aber bei B-Roll-lastigem/Mehrfach-Einstellungen-Material
kann es einen gesichtslosen Frame auswählen – und dann ist
face_skin_check.pngnutzlos (Haut ist das Qualitätskriterium). Erzwinge einen bekannten Gesichtsmoment mit--frame-time SEKUNDEN. - Die WB-/Belichtungsstatistiken stammen aus einem ~10s
signalstats-Fenster bei 10% der Dauer – gut für konstantes Licht; bei Filmmaterial mit wechselndem Licht verschiebe/verlängere es mit--win-start SEKUNDEN/--win-dur SEKUNDEN.
- Der repräsentative Frame wird nach medianer Luminanz gewählt, nicht nach Gesichtern. Bei einem
Einzel-Talking-Head landet das normalerweise auf einem guten Gesichts-Frame, aber bei B-Roll-lastigem/Mehrfach-Einstellungen-Material
kann es einen gesichtslosen Frame auswählen – und dann ist
-
Rendere die Looks zur Auswahl.
python scripts/render_looks.py work/assess/frame.png looks.json --out work/looksErzeugt
work/looks/looks_compare.png(Original + jeder Look, beschriftet, volle Auflösung) undface_skin_check.png(gezoomter Hautstreifen). SCHAUE BEIDE AN. Haut ist das Kriterium – stimme nach oder verwirf jeden Look, der Haut grau/grün macht, und rendere dann erneut. Motiv nicht zentriert? Füge--face-crop B:H:X:Yhinzu. Behandle diese PNGs als Arbeits-Zwischenergebnisse; veröffentliche die genehmigten Review-Kopien alsreview/02-color-grade/choose-color-look.jpgundreview/02-color-grade/skin-tone-check.jpg. -
(optional) Bewegungsvorschauen der Top 2–3, damit sie in Bewegung gesehen werden (Audio bleibt):
python scripts/make_clips.py QUELLE.mp4 looks.json --out work/clips \ --pick clean_neutral,warm_filmic,teal_orange --with-original --ss 148 --t 8 -
Wähle einen Look aus. Präsentiere den Kontaktbogen und warte nur, wenn der Benutzer gebeten wurde zu wählen. Ansonsten wähle im Agenten-Modus aus und fahre ohne Entscheidungspause fort. Schreibe
selected_look,selection_modeundselection_rationalevor der Lieferung. -
Nach der Auswahl – backe das LUT und wende es auf den gesamten Clip an.
python scripts/bake_lut.py looks.json --name AUSGEWAEHLT \ --out final/selected-color-look.cube --verify-frame work/assess/frame.png python scripts/apply_grade.py QUELLE.mp4 --looks looks.json --name AUSGEWAEHLT --out out/bewertet.mp4 # oder wende das gelieferte LUT an: --lut final/selected-color-look.cubeapply_grade.pyüberprüft, ob Dauer/Audio erhalten bleiben, druckt die WB-Verschiebung (U/V in Richtung 128) und legt einen Spot-Frame zur Sichtprüfung ab.- Die Anwendung kodiert das Video einmal (verlustbehaftet) neu; Audio ist
-c:a copy. Qualität/Zeit werden durch--crf(Standard 18) und--preset(Standardslow) eingestellt.slowist für kleine/kurze Clips in Ordnung, aber bei 1080p/4K stundenlangem Material ist es ein großer, stiller Zeitaufwand – wechsle zu--preset mediumfür die Lieferung (oderveryfastfür eine schnelle Vorschau),slownur für die endgültige Fassung. - Stimme das gelieferte Video mit dem gelieferten LUT ab: Die Kette (
--name) und das gebackene 33³.cube(--lut) können sich bei steilen Kurven um bis zu ~5/255 unterscheiden (bake_lut.pymeldet diesesmax|d|). Für die meisten Arbeiten unsichtbar, aber wenn du das.cubeauslieferst und möchtest, dass das gerenderte Video genau damit übereinstimmt, wende mit--lut final/selected-color-look.cubean, nicht mit--name.
- Die Anwendung kodiert das Video einmal (verlustbehaftet) neu; Audio ist
Konstruktionshinweise / Fallstricke
- Korrigiere den CLEAN-Schnitt, setze dann Composite-Overlays auf das bewertete Filmmaterial. Wenn du
ein Video korrigieren musst, das bereits eingebrannte Untertitel/Karten hat, vertraue NICHT auf
assess.py: seine WB-/ Belichtungsstatistiken und die Rahmenauswahl werden durch die Grafiken verfälscht (dunkle obere/untere Bildschirmleisten ziehen Y nach unten, heller Untertiteltext / Tealfarbtöne verfälschen U/V, und die Rahmenauswahl kann auf einen untertitellastigen Rahmen fallen) – du würdest die Overlays korrigieren, nicht das Filmmaterial. Verwende einebase, die auf dem sauberen Filmmaterial abgestimmt ist, und erwarte, dass die Korrektur die Overlays einfärbt, also verwende nur einen subtilen/korrigierenden Look (clean_neutral);teal_orange/vintage_fadedwerden den Text/die Farbakzente sichtbar umfärben. - Ein Look/LUT deckt alle verwandten Clips ab. Sobald du einen Look ausgewählt hast, verwende dasselbe
looks.json/.cubefür jeden verwandten Clip (z. B. sauberer Schnitt + die überlagerte Version) – führe NICHT die zweiphasige Auswahl pro Clip erneut durch. - Korrigieren, dann stylen. Die Basis ist die Verallgemeinerung des S-Log3→Rec.709-Schritts des Decks. Für normales Filmmaterial ist die Basis WB+Belichtung+Kontrast; für Log-Filmmaterial setze die Konvertierung dort hinein.
- Haut zuerst.
teal_orange/cool_desatsind die üblichen Hauttöne-Störenfriede bei dunkleren Motiven – halte die Mitteltöne warm, färbe Schatten/Lichter/Hintergrund, verwendevibrance. Neutralisiere U/V nicht vollständig. - Beschriftungen sind PIL-PNGs, die von ffmpeg überlagert werden – niemals
drawtext. Unter Windows ist derdrawtext-fontfile-Pfad (Laufwerk-Doppelpunkt) unzuverlässig. Schriftarten werden am Anfang vongradelib.pygesetzt. - Laufwerk-Doppelpunkt-Pfade brechen ffmpeg-Filtergraph-Optionen, die einen Pfad akzeptieren (
lut3d,metadata=print:file=). Die Skripte führen ffmpeg mitcwd= dem Ordner der Datei aus und referenzieren sie mit dem Basisnamen. Behalte dieses Muster für jeden neuen pfadnehmenden Filter bei. - Ausgaben landen in den von dir übergebenen
--out-Verzeichnissen (dauerhaft), nicht in einem System-Temp-Verzeichnis. - Der Gesichts-/Hautstreifen verwendet einen festen, zentrierten oberen Mittelausschnitt, keine Gesichtserkennung. Wenn sich das
Motiv nicht ungefähr in der Mitte befindet, übergib
--face-crop B:H:X:Yanrender_looks.py, sonst zeigt der Hautstreifen die falsche Region. - Der Verifikations-Spot-Frame von
apply_grade.pyliegt bei 20% der Dauer – ein anderer Frame als der, auf dem die Looks ausgewählt wurden. Wenn dieser 20%-Punkt gesichtslos ist, prüfe die Haut stattdessen an einem anderen Frame (oder übergib--frame-timeanassess.py, damit der ausgewählte Frame ein bekannter Gesichtsmoment ist). - Das LUT-Backen ist exakt, weil jeder Filter hier ein pixelweiser Punktoperator ist:
bake_lut.pyschiebt ein 33³-Identitätsgitter durch die echte Kette und prüft sich selbst dagegen (mean|d|sollte deutlich unter 1/255 liegen; ein paar Ebenenmax|d|ist nur Interpolation). - Auswahl vor Lieferung, immer. Die menschliche Wahl und die delegierte Agentenwahl sind beide gültig, aber der Plan muss festhalten, welche stattfand und warum.