Communitygithub.com

vidéo-étalonnage

Utilisez cette compétence lorsqu'un métrage a besoin d'étalonnage, de correction des blancs, de réparation d'exposition, de conversion Log vers Rec.709, de looks créatifs nommés, de vérification des tons chair ou d'un LUT .cube portable pour une livraison en « cut-as-code ».

Qu'est-ce que vidéo-étalonnage ?

Cette compétence d'agent IA permet d'étalonner une vidéo intégralement par code, en générant plusieurs looks nommés distincts sur le métrage réel, puis en sélectionnant l'un d'eux (via un choix humain explicite ou une décision déléguée de l'agent) avant de cuire un LUT .cube portable et de l'appliquer à l'intégralité du clip. Elle repose sur deux principes fondamentaux : corriger d'abord, puis styliser. Chaque look est composé d'une base corrective partagée (conversion Log→Rec.709, ou correction de balance des blancs/exposition/contraste pour les images déjà en Rec.709) et d'une couche créative unique (chaud, froid, teal-orange, punchy, délavé, etc.). La qualité des tons chair est primordiale : la compétence juge chaque look d'abord sur les visages, conserve la chaleur des tons moyens et privilégie la vibrance à la saturation brute. Le workflow complet comprend une phase d'analyse (assess), de rendu des looks pour comparaison, de sélection (humaine ou agent), de cuisson du LUT et d'application finale avec ffmpeg, le tout sans perte audio (copie du flux) et en préservant la durée, les FPS et les dimensions sources. La compétence prend en charge les modes `human` (pause pour choix explicite) et `agent` (décision autonome sans interruption), et enregistre chaque décision avec sa justification dans un plan JSON durable.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/WhiteTowerAI/cut-as-code/tree/main/skills/video-color-grade

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Étalonnage vidéo (analyser → corriger → looks → choisir → LUT + appliquer)

Écrire l'étalonnage depuis zéro sous forme de code, générer plusieurs looks nommés distincts sur le métrage réel, en sélectionner un via un choix humain explicite ou une décision déléguée de l'agent, puis cuire un LUT .cube portable et l'appliquer à l'intégralité du clip. Aucun pack de préréglages.

Idée centrale — corriger, puis styliser. Chaque look est base corrective + couche créative :

  • base corrective rend le métrage honnête — pour les formats log/HLG plats, il s'agit de la conversion log→Rec.709 ; pour les images déjà en Rec.709, il s'agit de la neutralisation de la balance des blancs + exposition + contraste léger. Chaque look partage cette base, donc « neutre » reste pertinent et les looks diffèrent par leur intention.
  • couche créative est le look empilé par-dessus (chaud / froid / teal-orange / punchy / délavé…).

La peau est le juge de qualité. Sur les personnes — surtout les peaux foncées — teal_orange et cool_desat jettent régulièrement un voile vert/gris. Jugez chaque look d'abord sur les visages ; conservez la chaleur des tons moyens, poussez la teinte dans les ombres/hauts-lumières/fond, préférez la vibrance à la saturation brute. Ne poussez pas la balance des blancs entièrement à neutre, sinon la peau devient terne.

Prérequis : ffmpeg/ffprobe dans le PATH, Python avec numpy + Pillow.

Contrat

  • Chaque look = base + couche créative ; la base est partagée par tous les looks.
  • Tous les looks sont rendus sur la même image représentative (une comparaison juste et contrôlée), étiquetés, en pleine résolution.
  • L'application complète utilise -c:a copy (l'audio n'est jamais ré-encodé → synchronisation A/V identique) et conserve la durée, les FPS, les dimensions sources.
  • Sélection complète du look avant livraison. Utilisez le mode human lorsque l'utilisateur a demandé à choisir ; utilisez le mode agent sans pause lorsque l'utilisateur a délégué le choix ou demandé une exécution autonome. Dans les deux modes, conservez la justification dans le plan durable.

Flux de travail du protocole projet

Stockez la décision durable dans 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": "La correction neutre préserve les tons chair naturels et l'éclairage source.",
  "selected_lut": "../../final/selected-color-look.cube",
  "evidence_refs": ["media:source"]
}

Utilisez base-video par défaut afin que les cartes de contenu et les sous-titres ne soient pas re-colorés. composite n'est valide que lorsque vous étalonnez intentionnellement des pixels déjà composités. Lisez le fichier partagé work/understand/media.json et les preuves visuelles. Générez review/02-color-grade/choose-color-look.jpg et review/02-color-grade/skin-tone-check.jpg ; générez éventuellement la courte vidéo selected-look-preview.mp4. Enregistrez la décision et les preuves dans review/02-color-grade/selected-look.md.

Définissez selection_mode à human après un choix explicite de l'utilisateur. Définissez-le à agent lorsque l'utilisateur délègue la décision ou demande un flux de travail ininterrompu, et choisissez de manière conservatrice avec les tons chair, la rétention des hautes lumières et l'équilibre neutre comme priorités. Écrivez toujours un selection_rationale non vide ; ne faites pas de pause en mode agent.

Cuisez le LUT choisi dans final/selected-color-look.cube. Lorsque ce LUT est livré, définissez selected_lut dans le plan d'étalonnage afin que le moteur de rendu partagé consomme ce fichier exact. Un LUT cuit contenant le look complet contient déjà la base corrective ; le moteur de rendu doit appliquer lut3d seul, et non base + lut3d.

Enregistrez les révisions exactes des entrées de l'opération dans based_on, incrémentez son revision entier lorsque le plan ou l'entrée sélectionnée change, définissez la vérification de l'opération à pass uniquement après que les fichiers de révision sont complets, et contribuez les champs complets du protocole :

{
"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"
}
}

Le moteur de rendu partagé valide que selected_look existe, applique l'étalonnage après les transformations de la timeline et avant les incrustations, et encode la vidéo finale une seule fois :

python skills/video-understand/scripts/build_render_plan.py .
python skills/video-understand/scripts/render_project.py work/render/render-plan.json

Entrées pour compatibilité autonome

  • La vidéo source.
  • Un fichier looks.json (base + looks nommés) ; commencez à partir de looks.example.json et réajustez la base en fonction de ce que rapporte assess.py.

Tous les scripts existants continuent d'accepter l'ancien looks.json. gradelib.load_spec() accepte également le grade-plan.json canonique, donc les commandes de planche contact, de clip, de LUT et d'application autonome restent compatibles pendant la migration.

Schéma looks.json

{
  "base": "<base corrective : conversion log->Rec.709, OU WB+exposition+contraste pour les images normales>",
  "looks": [
    { "name": "clean_neutral", "label": "1 · CLEAN NEUTRAL", "desc": "corrigé uniquement", "chain": "eq=contrast=1.02" },
    { "name": "teal_orange",   "label": "4 · TEAL & ORANGE", "desc": "cinématographique",      "chain": "colorbalance=...,eq=..." }
    // le filtre ffmpeg complet d'un look = base + "," + chain. Définissez "prepend_base": false pour utiliser la chaîne seule.
  ]
}

Flux de travail

  1. Analyser — ne jamais étalonner à l'aveugle.

    python scripts/assess.py SOURCE.mp4 --out work/
    

    Rapporte log-vs-Rec.709, la dominante de balance des blancs (signalstats U/V vs 128), l'exposition (Y), choisit une image représentative (work/assess/frame.png) et suggère des ajustements de base. Réajustez la base de looks.json en conséquence (par exemple, dominante chaude → ajoutez un peu de bleu ; log → mettez la conversion log→709 dans base).

    • L'image rep est choisie par la luminance médiane, pas par les visages. Sur un plan fixe d'une personne qui parle, cela donne généralement une bonne image de visage, mais sur des plans larges/plans multiples riches en B-roll, cela peut choisir une image sans visage — et alors face_skin_check.png est inutile (la peau est le baromètre de qualité). Forcez un moment connu avec visage avec --frame-time SECONDES.
    • Les statistiques WB/exposition proviennent d'une fenêtre signalstats d'environ 10 s à 10% de la durée — correct pour une lumière constante ; pour une lumière qui change, déplacez/allongez-la avec --win-start SECONDES / --win-dur SECONDES.
  2. Rendre les looks pour le choix.

    python scripts/render_looks.py work/assess/frame.png looks.json --out work/looks
    

    Produit work/looks/looks_compare.png (original + chaque look, étiquetés, pleine résolution) et face_skin_check.png (bande de peau zoomée). REGARDEZ les deux. La peau est le baromètre — réajustez ou abandonnez tout look qui grise/verdise la peau, puis re-rendez. Sujet décentré ? ajoutez --face-crop W:H:X:Y. Traitez ces PNG comme des intermédiaires de travail ; publiez les copies de révision approuvées sous review/02-color-grade/choose-color-look.jpg et review/02-color-grade/skin-tone-check.jpg.

  3. (optionnel) Aperçus animés des 2–3 meilleurs pour les voir en mouvement (audio conservé) :

    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
    
  4. Sélectionner un look. Présentez la planche contact et attendez uniquement lorsque l'utilisateur a demandé à choisir. Sinon, sélectionnez-le en mode agent et continuez sans pause de décision. Écrivez selected_look, selection_mode et selection_rationale avant livraison.

  5. Après le choix — cuire le LUT et l'appliquer à l'intégralité du clip.

    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
    #   ou appliquez le LUT livré : --lut final/selected-color-look.cube
    

    apply_grade.py vérifie que la durée/l'audio sont conservés, imprime le décalage WB (U/V vers 128) et génère une image de repère pour inspection.

    • L'application ré-encode la vidéo une fois (avec perte) ; l'audio est -c:a copy. La qualité/le temps sont définis par --crf (par défaut 18) et --preset (par défaut slow). slow est correct pour les petits/courts clips, mais sur des plans longs 1080p/4K, c'est un coût temporel important et silencieux — passez à --preset medium pour la livraison (ou veryfast pour un aperçu rapide), slow uniquement pour le final.
    • Faites correspondre la vidéo livrée au LUT livré : la chaîne (--name) et le .cube 33³ cuit (--lut) peuvent différer jusqu'à ~5/255 sur les courbes raides (bake_lut.py rapporte ce max|d|). Invisible pour la plupart des travaux, mais si vous livrez le .cube et voulez que la vidéo rendue lui corresponde exactement, appliquez avec --lut final/selected-color-look.cube, pas --name.

Notes de conception / pièges

  • Étalonnez le montage NET, puis composez les incrustations PAR-DESSUS l'image étalonnée. Si vous devez étalonner une vidéo qui a déjà des sous-titres/cartes incrustés, ne faites PAS confiance à assess.py : ses statistiques WB/exposition et le choix de l'image sont faussés par les graphiques (les bannières sombres haut/bas tirent Y vers le bas, le texte clair des sous-titres / les accents teal faussent U/V, et le choix de l'image peut tomber sur une image riche en sous-titres) — vous corrigeriez les incrustations, pas l'image. Réutilisez une base ajustée sur l'image nette, et attendez-vous à ce que l'étalonnage teinte les incrustations, donc n'utilisez qu'un look subtil/correctif (clean_neutral) ; teal_orange/vintage_faded recolorera visiblement le texte/les accents.
  • Un look/LUT couvre tous les clips associés. Une fois que vous avez choisi un look, réutilisez le même looks.json / .cube sur tous les clips associés (par exemple, montage net + la version avec incrustations) — ne relancez PAS la sélection en deux phases par clip.
  • Corriger, puis styliser. La base est la généralisation de l'étape S-Log3→Rec.709 du deck. Pour les images normales, la base est WB+exposition+contraste ; pour les images log, mettez la conversion dedans.
  • La peau d'abord. teal_orange/cool_desat sont les habituels agresseurs de peau sur les sujets plus foncés — gardez les tons moyens chauds, teintez les ombres/hauts-lumières/fond, utilisez la vibrance. Ne neutralisez pas U/V complètement.
  • Les étiquettes sont des PNG PIL superposés par ffmpeg — jamais drawtext. Sur Windows, le chemin fontfile de drawtext (deux-points du lecteur) n'est pas fiable. Les polices sont définies en haut de gradelib.py.
  • Les chemins avec deux-points du lecteur cassent les options du graphe de filtres ffmpeg qui prennent un chemin (lut3d, metadata=print:file=). Les scripts exécutent ffmpeg avec cwd = le dossier du fichier et le référencent par son nom de base. Gardez ce modèle pour tout nouveau filtre prenant un chemin.
  • Les sorties atterrissent dans les répertoires --out que vous passez (durables), pas dans un répertoire temporaire système.
  • La bande visage/peau utilise un recadrage fixe centré dans la moitié supérieure, pas une détection de visage. Si le sujet n'est pas à peu près centré, passez --face-crop W:H:X:Y à render_looks.py ou la bande de peau montrera la mauvaise région.
  • L'image de repère de vérification de apply_grade.py est à 20% de la durée — une image différente de celle sur laquelle les looks ont été choisis. Si ce point à 20% est sans visage, inspectez la peau sur une autre image (ou passez --frame-time à assess.py pour que l'image choisie soit un moment connu avec visage).
  • La cuisson du LUT est exacte, car chaque filtre ici est une opération point par pixel : bake_lut.py pousse une grille d'identité 33³ à travers la chaîne réelle et se vérifie contre elle (mean|d| devrait être bien en dessous de 1/255 ; un max|d| de quelques niveaux n'est que de l'interpolation).
  • Sélection avant livraison, toujours. Le choix humain et le choix délégué à l'agent sont tous deux valides, mais le plan doit enregistrer lequel a eu lieu et pourquoi.

Individual skills in this repo

This repo contains 8 individual skills — each has its own dedicated page.

Skills associés