Communitygithub.com

video-color-grade

Úsalo cuando el metraje necesite corrección de color, balance de blancos o reparación de exposición, conversión de Log a Rec.709, estilos creativos con nombre, revisión de tonos de piel o un LUT portátil .cube para una entrega de corte como código.

¿Qué es video-color-grade?

Esta habilidad aplica un flujo de trabajo completo de etalonaje de video programático, desde la evaluación inicial hasta la entrega final de un LUT .cube portátil. Está diseñada para agentes de IA (como Claude Code) y desarrolladores que trabajan con herramientas de línea de comandos como `ffmpeg` y Python. El proceso sigue un enfoque de dos fases: primero se aplica una base correctiva (conversión Log→Rec.709, neutralización de balance de blancos, ajuste de exposición) y luego una capa creativa (por ejemplo, teal-orange, cálido, frío, desaturado). Soporta los modos de selección `human` (el usuario elige entre los looks renderizados) y `agent` (el agente selecciona automáticamente priorizando tonos de piel, retención de brillos y balance neutral). La habilidad genera un `looks.json` con múltiples variantes, renderiza un fotograma representativo con todas las opciones etiquetadas para comparación, hornea el look elegido en un LUT 33³ exacto y lo aplica al clip completo preservando la duración, el audio (sin recodificación) y la geometría. Un aspecto crítico es que evalúa la calidad del tono de piel en cada look, evitando que el teal-orange o la desaturación fría produzcan un matiz gris/verde en sujetos de piel oscura. El flujo completo se registra en `grade-plan.json` y produce artefactos de revisión como `choose-color-look.jpg` y `skin-tone-check.jpg`.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Video Color Grade (evaluar → corregir → looks → elegir → LUT + aplicar)

Escribe la gradación desde cero como código, genera varios looks con nombre distintos sobre el metraje real, selecciona uno mediante elección humana explícita o juicio delegado del agente, luego hornea un LUT .cube portátil y lo aplica al clip completo. Sin paquetes preestablecidos.

Idea central: corregir, luego estilizar. Cada look es base correctiva + capa creativa:

  • base correctiva hace que el metraje sea honesto — para LOG/HLG plano es la conversión log→Rec.709; para metraje ya en Rec.709 es neutralización de WB + exposición + contraste suave. Cada look comparte esta base, por lo que "neutral" sigue siendo significativo y los looks difieren por intención.
  • capa creativa es el look apilado encima (cálido / frío / teal-orange / intenso / desvaído…).

La piel es el rasero de calidad. En personas —especialmente piel oscura— teal_orange y cool_desat suelen arrojar un matiz verde/gris. Juzga cada look primero en los rostros; mantén la calidez de los tonos medios, empuja el tinte hacia sombras/altas luces/fondo, prefiere vibrance sobre saturation crudo. No fuerces el balance de blancos hasta el neutral o la piel se vuelve sin vida.

Requisitos: ffmpeg/ffprobe en PATH, Python con numpy + Pillow.

Contrato

  • Cada look = base + capa creativa; la base es compartida por todos los looks.
  • Todos los looks se renderizan en el mismo fotograma representativo (una comparación controlada y justa), etiquetados, a resolución completa.
  • La aplicación completa usa -c:a copy (el audio nunca se recodifica → sincronización A/V idéntica) y mantiene la duración, fps, dimensiones de la fuente.
  • Selección completa del look antes de la entrega. Usa el modo human cuando el usuario pidió elegir; usa el modo agent sin pausas cuando el usuario delegó la elección o solicitó una ejecución autónoma. En ambos modos, conserva la justificación en el plan duradero.

Flujo de trabajo del protocolo del proyecto

Almacena la decisión duradera en 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 corrección neutral preserva la piel natural y la iluminación de la fuente.",
  "selected_lut": "../../final/selected-color-look.cube",
  "evidence_refs": ["media:source"]
}

Usa base-video por defecto para que las tarjetas de contenido y los subtítulos no se vuelvan a colorear. composite solo es válido cuando se gradan intencionalmente píxeles ya compuestos. Lee el work/understand/media.json compartido y la evidencia visual. Genera review/02-color-grade/choose-color-look.jpg y review/02-color-grade/skin-tone-check.jpg; opcionalmente genera el breve selected-look-preview.mp4. Registra la decisión y la evidencia en review/02-color-grade/selected-look.md.

Establece selection_mode en human después de una elección explícita del usuario. Establécelo en agent cuando el usuario delegue la decisión o solicite un flujo de trabajo ininterrumpido, y elige de forma conservadora priorizando el tono de piel, la retención de altas luces y el balance neutral. Siempre escribe un selection_rationale no vacío; no hagas pausa en el modo agente.

Hornea el LUT elegido en final/selected-color-look.cube. Cuando ese LUT se entregue, establece selected_lut en el plan de gradación para que el renderizador compartido consuma ese archivo exacto. Un LUT horneado de look completo ya contiene la base correctiva; el renderizador debe aplicar solo lut3d, no base + lut3d.

Registra las revisiones exactas de entrada de la operación en based_on, incrementa su revision entera cuando el plan o la entrada seleccionada cambie, establece la verificación de la operación en pass solo después de que los archivos de revisión estén completos, y contribuye con los campos completos del protocolo:

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

El renderizador compartido valida que selected_look exista, aplica la gradación después de las transformaciones de la línea de tiempo y antes de las superposiciones, y codifica el video final una vez:

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

Entradas de compatibilidad independiente

  • El video fuente.
  • Un looks.json (base + looks con nombre); comienza desde looks.example.json y reajusta la base a partir de lo que informe assess.py.

Todos los scripts existentes continúan aceptando el looks.json heredado. gradelib.load_spec() también acepta el grade-plan.json canónico, por lo que los comandos de hoja de contactos, clip, LUT y aplicación independiente permanecen compatibles durante la migración.

Esquema de looks.json

{
  "base": "<base correctiva: conversión log->Rec.709, O WB+exposición+contraste para metraje normal>",
  "looks": [
    { "name": "clean_neutral", "label": "1 · CLEAN NEUTRAL", "desc": "solo corregido", "chain": "eq=contrast=1.02" },
    { "name": "teal_orange",   "label": "4 · TEAL & ORANGE", "desc": "cinematográfico",      "chain": "colorbalance=...,eq=..." }
    // el filtro ffmpeg completo de un look = base + "," + chain. Establece "prepend_base": false para usar chain independiente.
  ]
}

Flujo de trabajo

  1. Evaluar — nunca gradar a ciegas.

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

    Informa log-vs-Rec.709, tinte de balance de blancos (signalstats U/V vs 128), exposición (Y), elige un fotograma representativo (work/assess/frame.png) y sugiere ajustes de base. Reajusta la base de looks.json en consecuencia (ej. tinte cálido → añadir un poco de azul; log → poner la conversión log→709 en base).

    • El fotograma representativo se elige por luma mediana, no por rostros. En un talking-head de un solo plano eso suele dar con un buen fotograma de rostro, pero en metraje con muchos B-roll / múltiples planos puede elegir un fotograma sin rostro — y entonces face_skin_check.png es inútil (la piel es el rasero de calidad). Fuerza un momento conocido de rostro con --frame-time SEGUNDOS.
    • Las estadísticas de WB/exposición provienen de una ventana signalstats de ~10s al 10% de la duración — bien para luz constante; para metraje cuya luz cambia, muévela/alárgala con --win-start SEGUNDOS / --win-dur SEGUNDOS.
  2. Renderizar los looks para elegir.

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

    Produce work/looks/looks_compare.png (original + cada look, etiquetado, resolución completa) y face_skin_check.png (tira de piel ampliada). MIRA AMBOS. La piel es el rasero — reajusta o descarta cualquier look que vuelva gris/verde la piel, luego vuelve a renderizar. ¿Sujeto descentrado? añade --face-crop W:H:X:Y. Trata esos PNG como intermediarios de trabajo; publica las copias de revisión aprobadas como review/02-color-grade/choose-color-look.jpg y review/02-color-grade/skin-tone-check.jpg.

  3. (opcional) Previsualizaciones en movimiento de los 2–3 mejores para verlos en movimiento (audio conservado):

    python scripts/make_clips.py FUENTE.mp4 looks.json --out work/clips \
      --pick clean_neutral,warm_filmic,teal_orange --with-original --ss 148 --t 8
    
  4. Seleccionar un look. Presenta la hoja de contactos y espera solo cuando el usuario pidió elegir. De lo contrario, selecciónalo en modo agente y continúa sin una pausa de decisión. Escribe selected_look, selection_mode y selection_rationale antes de la entrega.

  5. Después de la elección — hornea el LUT y aplícalo al clip completo.

    python scripts/bake_lut.py looks.json --name ELEGIDO \
      --out final/selected-color-look.cube --verify-frame work/assess/frame.png
    python scripts/apply_grade.py FUENTE.mp4 --looks looks.json --name ELEGIDO --out out/graded.mp4
    #   o aplica el LUT entregado: --lut final/selected-color-look.cube
    

    apply_grade.py verifica que duración/audio se conserven, imprime el desplazamiento de WB (U/V hacia 128) y suelta un fotograma de verificación para inspeccionar.

    • La aplicación recodifica el video una vez (con pérdida); el audio es -c:a copy. Calidad/tiempo se establecen con --crf (por defecto 18) y --preset (por defecto slow). slow está bien para clips pequeños/cortos, pero en metraje de 1080p/4K de una hora es un gran coste de tiempo silencioso — baja a --preset medium para entrega (o veryfast para una vista previa rápida), slow solo para final.
    • Haz coincidir el video entregado con el LUT entregado: la cadena (--name) y el .cube horneado de 33³ (--lut) pueden diferir hasta ~5/255 en curvas pronunciadas (bake_lut.py informa este max|d|). Invisible para la mayoría del trabajo, pero si estás entregando el .cube y quieres que el video renderizado coincida exactamente con él, aplica con --lut final/selected-color-look.cube, no con --name.

Notas de diseño / problemas conocidos

  • Grada el corte LIMPIO, luego compón las superposiciones encima del metraje gradado. Si debes gradar un video que ya tiene subtítulos/tarjetas quemadas, NO confíes en assess.py: sus estadísticas de WB/exposición y elección de fotograma están sesgadas por los gráficos (las cortinas oscuras superior/inferior arrastran Y hacia abajo, el texto brillante de subtítulos / los acentos teal sesgan U/V, y la elección de fotograma puede caer en uno con muchos subtítulos) — estarías corrigiendo las superposiciones, no el metraje. Reutiliza una base ajustada en el metraje limpio, y espera que la gradación tiña las superposiciones, así que usa solo un look sutil/correctivo (clean_neutral); teal_orange/vintage_faded recolorearán visiblemente el texto/los acentos.
  • Un look/LUT cubre todos los clips relacionados. Una vez que hayas elegido un look, reutiliza el mismo looks.json / .cube en cada clip relacionado (ej. corte limpio + la versión con superposiciones) — NO vuelvas a ejecutar la elección en dos fases por clip.
  • Corregir, luego estilizar. La base es la generalización del paso S-Log3→Rec.709 del material de origen. Para metraje normal la base es WB+exposición+contraste; para metraje log coloca allí la conversión.
  • La piel primero. teal_orange/cool_desat son los habituales agresores de la piel en sujetos más oscuros — mantén los tonos medios cálidos, tiñe sombras/altas luces/fondo, usa vibrance. No neutralices U/V por completo.
  • Las etiquetas son PNG de PIL superpuestos por ffmpeg — nunca drawtext. En Windows la ruta fontfile de drawtext (dos puntos de unidad) no es fiable. Las fuentes se establecen al inicio de gradelib.py.
  • Las rutas con dos puntos de unidad rompen las opciones del filtergraph de ffmpeg que toman una ruta (lut3d, metadata=print:file=). Los scripts ejecutan ffmpeg con cwd = la carpeta del archivo y lo referencian por nombre base. Mantén este patrón para cualquier nueva opción que tome rutas.
  • Las salidas caen en los directorios --out que pases (duraderos), no en un directorio temporal del sistema.
  • La tira de rostro/piel usa un recorte fijo centrado en la parte superior media, no detección de rostros. Si el sujeto no está aproximadamente centrado, pasa --face-crop W:H:X:Y a render_looks.py o la tira de piel mostrará la región incorrecta.
  • El fotograma de verificación de apply_grade.py está al 20% de la duración — un fotograma diferente de aquel en el que se eligieron los looks. Si ese punto del 20% no tiene rostro, inspecciona la piel en otro fotograma (o pasa --frame-time a assess.py para que el fotograma elegido sea un momento conocido de rostro).
  • El horneado del LUT es exacto, porque cada filtro aquí es una operación punto a punto por píxel: bake_lut.py empuja una cuadrícula identidad de 33³ a través de la cadena real y se autoverifica contra ella (mean|d| debería estar muy por debajo de 1/255; un max|d| de unos pocos niveles es solo interpolación).
  • Selección antes de la entrega, siempre. Tanto la elección humana como la elección delegada al agente son válidas, pero el plan debe registrar cuál ocurrió y por qué.

Individual skills in this repo

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

Skills relacionados