Video Color Grade (avaliar → corrigir → looks → escolher → LUT + aplicar)
Escreva a correção de cor do zero como código, gere vários looks nomeados distintos no
material real, selecione um através de escolha humana explícita ou julgamento delegado do agente, e então grave um
LUT .cube portátil e aplique-o ao clipe completo. Sem packs de presets.
Ideia central — corrigir, depois estilizar. Cada look é base corretiva + camada criativa:
- base corretiva torna a filmagem honesta — para LOG/HLG plano é a conversão log→Rec.709; para filmagem já em Rec.709 é neutralização do WB + exposição + contraste suave. Cada look compartilha esta base, então "neutro" permanece significativo e os looks diferem por intenção.
- camada criativa é o look empilhado por cima (quente / frio / teal-orange / vívido / desbotado…).
A pele é o padrão de qualidade. Em pessoas — especialmente pele mais escura — teal_orange
e cool_desat rotineiramente jogam um tom verde/cinza. Julgue cada look nos rostos primeiro; mantenha o calor
dos médios, empurre a tonalidade para as sombras/realces/fundo, prefira vibrancia em vez de
saturação bruta. Não force o balanço de branco totalmente para neutro ou a pele fica sem vida.
Requisitos: ffmpeg/ffprobe no PATH, Python com numpy + Pillow.
Contrato
- Cada look =
base + camada criativa; a base é compartilhada por todos os looks. - Todos os looks renderizam no mesmo frame representativo (uma comparação justa e controlada), etiquetado, em resolução total.
- A aplicação completa usa
-c:a copy(áudio nunca re-codificado → sincronia A/V idêntica) e mantém a duração, fps, dimensões da fonte. - Seleção completa do look antes da entrega. Use o modo
humanquando o usuário pediu para escolher; use o modoagentsem pausar quando o usuário delegou a escolha ou solicitou uma execução autônoma. Em ambos os modos, preserve a justificativa no plano durável.
Fluxo de trabalho do protocolo do projeto
Armazene a decisão durável em 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": "Correção neutra preserva a pele natural e a iluminação original.",
"selected_lut": "../../final/selected-color-look.cube",
"evidence_refs": ["media:source"]
}
Use base-video por padrão para que cartões de conteúdo e legendas não sejam recoloridos. composite
é válido apenas ao intencionalmente corrigir pixels já compostos. Leia o
work/understand/media.json compartilhado e as evidências visuais. Gere
review/02-color-grade/choose-color-look.jpg e
review/02-color-grade/skin-tone-check.jpg; opcionalmente gere o curto
selected-look-preview.mp4. Registre a decisão e evidências em
review/02-color-grade/selected-look.md.
Defina selection_mode para human após uma escolha explícita do usuário. Defina para agent quando o
usuário delegar a decisão ou solicitar um fluxo de trabalho ininterrupto, e escolha conservadoramente
com tom de pele, retenção de realces e balanço neutro como prioridades. Sempre escreva um
selection_rationale não vazio; não pause no modo agente.
Grave o LUT escolhido para final/selected-color-look.cube. Quando esse LUT for enviado, defina
selected_lut no plano de correção para que o renderizador compartilhado consuma esse arquivo exato. Um LUT
gravado de look completo já contém a base corretiva; o renderizador deve aplicar apenas lut3d,
não base + lut3d.
Registre as revisões de entrada exatas da operação em based_on, incremente seu revision
inteiro quando o plano ou a entrada selecionada mudar, defina a verificação da operação para pass apenas
após os arquivos de revisão estarem completos, e contribua com os campos completos do 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"
}
}
O renderizador compartilhado valida que selected_look existe, aplica a correção após transformações
da linha do tempo e antes de sobreposições, e codifica o vídeo final uma 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 compatibilidade autônoma
- O vídeo de origem.
- Um
looks.json(base+looksnomeados); comece delooks.example.jsone reajuste obasea partir do queassess.pyrelata.
Todos os scripts existentes continuam a aceitar looks.json legado. gradelib.load_spec() também
aceita o canônico grade-plan.json, então comandos de folha de contato, clipe, LUT e aplicação autônoma
permanecem compatíveis durante a migração.
Esquema looks.json
{
"base": "<base corretiva: conversão log->Rec.709, OU WB+exposição+contraste para filmagem normal>",
"looks": [
{ "name": "clean_neutral", "label": "1 · LIMPO NEUTRO", "desc": "apenas corrigido", "chain": "eq=contrast=1.02" },
{ "name": "teal_orange", "label": "4 · TEAL & LARANJA", "desc": "cinematográfico", "chain": "colorbalance=...,eq=..." }
// o filtro ffmpeg completo de um look = base + "," + chain. Defina "prepend_base": false para usar chain sozinho.
]
}
Fluxo de trabalho
-
Avaliar — nunca corrigir no escuro.
python scripts/assess.py SOURCE.mp4 --out work/Relata log-vs-Rec.709, desvio de balanço de branco (signalstats U/V vs 128), exposição (Y), escolhe um frame representativo (
work/assess/frame.png), e sugere ajustes na base. Reajuste obasedolooks.jsonde acordo (ex. desvio quente → adicione um pouco de azul; log → coloque a conversão log→709 nobase).- O frame rep é escolhido pela mediana da luminância, não por rostos. Em uma fala única de cabeça
falante isso geralmente cai em um bom frame de rosto, mas em filmagem com muitos B-roll / multi-tomadas pode
escolher um frame sem rosto — e então
face_skin_check.pngé inútil (a pele é o padrão de qualidade). Force um momento de rosto conhecido com--frame-time SEGUNDOS. - As estatísticas de WB/exposição vêm de uma janela
signalstatsde ~10s a 10% da duração — bom para luz constante; para filmagem cuja luz muda, mova/aumente com--win-start SEGUNDOS/--win-dur SEGUNDOS.
- O frame rep é escolhido pela mediana da luminância, não por rostos. Em uma fala única de cabeça
falante isso geralmente cai em um bom frame de rosto, mas em filmagem com muitos B-roll / multi-tomadas pode
escolher um frame sem rosto — e então
-
Renderizar os looks para escolha.
python scripts/render_looks.py work/assess/frame.png looks.json --out work/looksProduz
work/looks/looks_compare.png(original + cada look, etiquetado, resolução total) eface_skin_check.png(faixa de pele ampliada). OLHE para ambos. A pele é o padrão — reajuste ou descarte qualquer look que cinza/verdeie a pele, então re-renderize. Assunto descentralizado? adicione--face-crop L:A:X:Y. Trate esses PNGs como intermediários de trabalho; publique as cópias de revisão aprovadas comoreview/02-color-grade/choose-color-look.jpgereview/02-color-grade/skin-tone-check.jpg. -
(opcional) Pré-visualizações em movimento das 2–3 principais para que sejam vistas em movimento (áudio mantido):
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 -
Selecionar um look. Apresente a folha de contato e espere apenas quando o usuário pediu para escolher. Caso contrário, selecione no modo agente e continue sem uma pausa de decisão. Escreva
selected_look,selection_modeeselection_rationaleantes da entrega. -
Após a escolha — gravar o LUT e aplicar ao clipe completo.
python scripts/bake_lut.py looks.json --name ESCOLHIDO \ --out final/selected-color-look.cube --verify-frame work/assess/frame.png python scripts/apply_grade.py SOURCE.mp4 --looks looks.json --name ESCOLHIDO --out out/graded.mp4 # ou aplique o LUT entregue: --lut final/selected-color-look.cubeapply_grade.pyverifica que duração/áudio são mantidos, imprime a mudança de WB (U/V em direção a 128), e solta um frame de verificação para inspeção.- A aplicação recodifica o vídeo uma vez (com perdas); o áudio é
-c:a copy. Qualidade/tempo são definidos por--crf(padrão 18) e--preset(padrãoslow).slowé bom para clipes pequenos/ curtos, mas em filmagem longa de 1080p/4K é um grande custo de tempo silencioso — reduza para--preset mediumpara entrega (ouveryfastpara uma pré-visualização rápida),slowapenas para final. - Corresponda o vídeo entregue ao LUT entregue: a cadeia (
--name) e o.cubede 33³ gravado (--lut) podem diferir em até ~5/255 em curvas íngremes (bake_lut.pyrelata estemax|d|). Invisível para a maioria dos trabalhos, mas se você está enviando o.cubee quer que o vídeo renderizado corresponda exatamente a ele, aplique com--lut final/selected-color-look.cube, não--name.
- A aplicação recodifica o vídeo uma vez (com perdas); o áudio é
Notas de design / pegadinhas
- Corrija o CORTE LIMPO, então composite sobreposições em cima da filmagem corrigida. Se você deve
corrigir um vídeo que já tem legendas/cartões queimados, NÃO confie em
assess.py: suas estatísticas de WB/ exposição e escolha de frame são desviadas pelos gráficos (escuridão superior/inferior arrasta Y para baixo, texto de legenda brilhante / tons teal desviam U/V, e a escolha de frame pode cair em um frame com muitas legendas) — você estaria corrigindo as sobreposições, não a filmagem. Reutilize umbaseajustado na filmagem limpa, e espere que a correção tingirá as sobreposições, então use apenas um look sutil/corretivo (clean_neutral);teal_orange/vintage_fadedrecolorirá visivelmente o texto/acentos. - Um look/LUT cobre todos os clipes relacionados. Depois de escolher um look, reutilize o mesmo
looks.json/.cubeem cada clipe relacionado (ex. corte limpo + a versão sobreposta) — NÃO execute novamente a escolha de duas fases por clipe. - Corrigir, depois estilizar. A base é a generalização da etapa S-Log3→Rec.709 do deck. Para filmagem normal, a base é WB+exposição+contraste; para filmagem log, coloque a conversão lá.
- Pele primeiro.
teal_orange/cool_desatsão os usuais ofensores de pele em sujeitos mais escuros — mantenha os médios quentes, tinga sombras/realces/fundo, usevibrancia. Não neutralize U/V completamente. - Etiquetas são PNGs PIL sobrepostos por ffmpeg — nunca
drawtext. No Windows o caminhofontfiledodrawtext(dois-pontos da unidade) é não confiável. As fontes são definidas no topo degradelib.py. - Caminhos com dois-pontos de unidade quebram opções do filtergraph do ffmpeg que aceitam um caminho (
lut3d,metadata=print:file=). Os scripts executam ffmpeg comcwd= a pasta do arquivo e referenciam pelo nome base. Mantenha este padrão para qualquer novo filtro que aceite caminho. - Os resultados caem nos diretórios
--outque você passa (duráveis), não em um diretório temporário do sistema. - A faixa de rosto/pele usa um corte fixo centralizado no terço superior, não detecção de rosto. Se o sujeito
não estiver aproximadamente centralizado, passe
--face-crop L:A:X:Ypararender_looks.pyou a faixa de pele mostrará a região errada. - O frame de verificação de
apply_grade.pyestá a 20% da duração — um frame diferente daquele em que os looks foram escolhidos. Se esse ponto de 20% estiver sem rosto, inspecione a pele em outro frame (ou passe--frame-timeparaassess.pypara que o frame escolhido seja um momento de rosto conhecido). - A gravação do LUT é exata, porque cada filtro aqui é uma operação ponto a ponto por pixel:
bake_lut.pyempurra uma grade de identidade 33³ através da cadeia real e auto-verifica contra ela (mean|d|deve estar bem abaixo de 1/255; ummax|d|de alguns níveis é apenas interpolação). - Seleção antes da entrega, sempre. A escolha humana e a escolha delegada do agente são ambas válidas, mas o plano deve registrar qual ocorreu e por quê.