Vídeo com IA: Claude Code, Codex e ElevenLabs · Capítulo 11 de 25 · 27 min
Revisar quadros com a visão do Claude e uma rubrica escrita
Extraia quadros do render com FFmpeg e peça ao Claude um QA visual guiado por rubrica em JSON que aponta o quadro exato do defeito antes da revisão humana.
Este capítulo faz parte do curso gratuito Vídeo com IA: Claude Code, Codex e ElevenLabs.
Assistir a cada render inteiro à procura de legenda cortada ou de uma tela preta de meio segundo cansa, e o olho cansado deixa passar justamente o defeito pequeno que o espectador vê no celular. Perguntar a um modelo se o vídeo ficou bom também não resolve: a resposta vem vaga, sem quadro, sem critério e sem como conferir.
Nesta aula você monta um QA visual, a conferência organizada do que foi produzido, como o controle de qualidade no fim de uma linha de produção. O Claude olha quadros, e não o vídeo. Você escolhe a amostra por tempo fixo ou por corte de cena, envia as imagens rotuladas pela Messages API dentro dos limites de quantidade e de tamanho, escreve a rubrica em JSON e recebe a resposta num formulário de campos fixos que um script valida.
Sai daqui uma triagem barata e repetível. Quarenta quadros em 960 × 540 custam cerca de US$ 0,056 de entrada visual no Sonnet 5.5, em 09/10/2026, e o relatório aponta o quadro exato para você olhar. A decisão de publicar continua sendo de uma pessoa.
Entender o que a visão do Claude pode comprovar
Pela Messages API, o Claude não recebe MP4, MOV nem WebM. Ele lê imagens JPEG, PNG, GIF e WebP, e de um GIF animado usa só o primeiro quadro. Por isso o vídeo vira uma pilha de fotografias antes da revisão. Essa amostragem encontra defeito visível no quadro parado, mas não comprova continuidade entre quadros, sincronismo da fala nem qualidade do áudio.
As limitações declaradas pela Anthropic entram no pedido como proibições: o Claude não identifica pessoas pelo rosto, não detecta com confiabilidade se uma imagem foi gerada por IA, dá contagens e coordenadas aproximadas e erra mais em imagens abaixo de 200 pixels. Os limites de envio pela API direta respondem quantas imagens e de que tamanho cabem num pedido.
Limites e custo da revisão por imagens
Quanto cabe numa requisição de imagens
Quanto cabe numa requisição de imagens à Messages API
600
imagens por requisição
modelos com contexto acima de 200 mil tokens; 20 por mensagem no Claude.ai
8000 px
lado máximo por imagem
com mais de 20 imagens, até 2000 px por lado
10 MB
por imagem em base64
na API direta; 5 MB no Bedrock e no Google Cloud
32 MB
por requisição
nos endpoints padrão; costuma chegar antes do teto de imagens
Fonte: Claude Platform Docs, Vision e API overview (tamanho máximo da requisição), em 09/10/2026.
Quanto custa cada quadro no Sonnet 5.5
Cada imagem custa tokens visuais pela fórmula da documentação: arredonde para cima a largura dividida por 28, faça o mesmo com a altura e multiplique. Os modelos 5.x usam o nível de alta resolução, com lado maior de até 2576 pixels e até 4784 tokens por imagem; uma imagem maior é reduzida, não recusada. Base64, a imagem escrita como texto para viajar dentro do JSON, ocupa cerca de um terço a mais que o arquivo, e é por isso que o limite de 32 MB por requisição costuma chegar antes do de 600 imagens. A lógica de preço por token de entrada e de saída está no curso de FinOps, em token, entrada e saída.
Quanto custa cada quadro na entrada do Sonnet 5.5
| Quadro enviado | Tokens visuais | Entrada em US$ |
|---|---|---|
| 1920 × 1080 | 2.691 | 0,0054 |
| 3840 × 2160, reduzido a 2576 × 1449 | 4.784 | 0,0096 |
| 960 × 540 | 700 | 0,0014 |
| 40 quadros em 960 × 540 | 28.000 | 0,056 |
Só entrada visual: texto, rubrica e tokens de saída somam à parte.
Fonte: Fórmula do guia de visão e preço de entrada do claude-sonnet-5-5 (US$ 2 por milhão de tokens), em 09/10/2026.

Se a figura ultrapassar a área visível, deslize para os lados. Pelo teclado, foque a figura e use as setas.
Extrair a amostra por tempo fixo ou por corte de cena
Os comandos de base do FFmpeg, inclusive a extração de quadros com fps e scale, estão na aula de FFmpeg essencial. Para QA, a decisão é como amostrar. Com fps=0.5 você fotografa a cada 2 segundos, o que cobre o vídeo por igual. Com o filtro select e a nota de mudança de cena (scene, de 0 a 1, que mede quanto um quadro difere do anterior), você fotografa a cada troca de plano, o que pega inserções curtas que o tempo fixo pula.
Se o vídeo tem cortes rápidos, como os 9:16 com texto que entra e sai, prefira a amostra por corte. Se é um plano longo com a câmera parada, o tempo fixo basta e sai mais barato. Nos dois casos, o maior lado fica em 960 pixels, e a pasta da amostra ganha um tempos.txt que vira o rótulo de cada imagem.
Se a figura ultrapassar a área visível, deslize para os lados. Pelo teclado, foque a figura e use as setas.
Dois jeitos de amostrar o render para QA
Por tempo fixo (fps)
Salve como scripts/extrair_por_tempo.sh e rode na raiz bash scripts/extrair_por_tempo.sh renders/revisao.mp4. Até 40 quadros cobrem um vídeo de 80 segundos inteiro.
#!/usr/bin/env bash
# Amostra regular para QA: 1 quadro a cada 2 s, maior lado em até 960 px (serve para 16:9 e 9:16).
set -euo pipefail
VIDEO="${1:-renders/revisao.mp4}"
SAIDA="qa/frames/qa11-fps"
test -f "$VIDEO" || { echo "Vídeo ausente: $VIDEO"; exit 1; }
test ! -e "$SAIDA" || { echo "$SAIDA já existe: arquive o ensaio anterior."; exit 1; }
mkdir -p "$SAIDA"
# Duração e dimensões antes de amostrar.
ffprobe -v error -select_streams v:0 \
-show_entries stream=width,height,r_frame_rate:format=duration -of default=nw=1 "$VIDEO"
ffmpeg -nostdin -v error -i "$VIDEO" \
-vf "fps=0.5,scale=960:960:force_original_aspect_ratio=decrease" \
-frames:v 40 -q:v 3 "$SAIDA/q_%03d.jpg"
# Tempo nominal de cada quadro: 0, 2, 4... segundos (aproximado, não é o timestamp exato).
n=$(ls "$SAIDA"/q_*.jpg | wc -l)
seq 0 2 $(( (n - 1) * 2 )) > "$SAIDA/tempos.txt"
echo "$n quadros em $SAIDA"
Por corte de cena (select)
Salve como scripts/extrair_por_corte.sh e rode na raiz bash scripts/extrair_por_corte.sh renders/revisao.mp4 0.3. Se vierem quadros demais, suba o limiar para 0,4; se vierem poucos, desça para 0,2.
#!/usr/bin/env bash
# Amostra por corte para QA: o primeiro quadro e todo quadro cuja mudança de cena passa do limiar.
set -euo pipefail
VIDEO="${1:-renders/revisao.mp4}"
LIMIAR="${2:-0.3}" # 0 = nada mudou, 1 = imagem toda nova; 0,3 é o ponto de partida do exercício
SAIDA="qa/frames/qa11-cenas"
test -f "$VIDEO" || { echo "Vídeo ausente: $VIDEO"; exit 1; }
test ! -e "$SAIDA" || { echo "$SAIDA já existe: arquive o ensaio anterior."; exit 1; }
mkdir -p "$SAIDA"
# showinfo escreve no log o instante (pts_time) de cada quadro escolhido.
# -fps_mode vfr grava só os quadros selecionados (em FFmpeg antigo: -vsync vfr).
ffmpeg -nostdin -i "$VIDEO" \
-vf "select='eq(n,0)+gt(scene,${LIMIAR})',showinfo,scale=960:960:force_original_aspect_ratio=decrease" \
-fps_mode vfr -frames:v 40 -q:v 3 "$SAIDA/c_%03d.jpg" 2> "$SAIDA/showinfo.log"
# Mapa quadro -> segundo real, usado nos rótulos enviados ao Claude.
grep -o 'pts_time:[0-9.]*' "$SAIDA/showinfo.log" | cut -d: -f2 | head -40 > "$SAIDA/tempos.txt"
paste <(ls "$SAIDA"/c_*.jpg) "$SAIDA/tempos.txt"
Escrever a rubrica e pedir a resposta em formulário fixo
A rubrica é a lista escrita do que conta como defeito, como o gabarito que o corretor recebe antes da prova. Cada categoria tem um critério observável e uma gravidade, e as proibições repetem as limitações da visão. Sem ela, o modelo inventa o próprio padrão a cada chamada, e dois relatórios do mesmo vídeo deixam de ser comparáveis. O curso de agentes mostra o mesmo princípio aplicado a qualquer agente em testes e avaliação.
Escreva cada categoria como um par sinal e ajuste, o formato das fichas de técnicas de cinema do Vibehub: o critério é o sinal, o que procurar no quadro, e o campo ajuste diz o que mudar na composição. O relatório chega com o conserto ao lado do defeito, e a correção nunca vai para o bruto. Se você não consegue escrever o ajuste de uma categoria, ela ainda é vaga demais para entrar na rubrica.
{
"versao": "2026-10-12",
"idioma_do_texto_na_tela": "pt-BR",
"limiar_confianca": 0.85,
"nota_limiar": "Regra de encaminhamento do exercício. A confiança declarada pelo modelo não é probabilidade calibrada.",
"paleta": ["#1F2937", "#F59E0B", "#FFFFFF"],
"categorias": [
{
"id": "legenda_cortada",
"criterio": "Alguma letra da legenda toca ou ultrapassa a borda do quadro.",
"gravidade": "alta",
"ajuste": "Quebrar a linha ou reduzir o corpo da legenda na composição."
},
{
"id": "texto_ilegivel",
"criterio": "Texto na tela com contraste baixo, pequeno demais para celular ou sobre área movimentada.",
"gravidade": "alta",
"ajuste": "Aumentar corpo e contraste ou pôr uma faixa sólida atrás do texto."
},
{
"id": "assunto_cortado",
"criterio": "O prato ou a mão em ação sai do quadro quando deveria aparecer inteiro.",
"gravidade": "media",
"ajuste": "Reenquadrar o plano na composição ou escolher outro trecho do bruto."
},
{
"id": "tela_preta",
"criterio": "Quadro preto ou quase preto fora da abertura e do fechamento.",
"gravidade": "alta",
"ajuste": "Conferir o tempo de entrada da cena e o arquivo de origem no ponto marcado."
},
{
"id": "quebra_de_paleta",
"criterio": "Texto, fundo ou marca em cor fora da paleta declarada.",
"gravidade": "baixa",
"ajuste": "Trocar a cor pela da paleta declarada na composição."
},
{
"id": "grafia_errada",
"criterio": "Palavra do texto na tela sem acento ou com grafia errada em português.",
"gravidade": "media",
"ajuste": "Corrigir o texto no roteiro e na composição, nunca só no quadro."
}
],
"proibicoes": [
"Não identificar pessoas pelo rosto.",
"Não afirmar se a imagem foi gerada por IA.",
"Não avaliar áudio, movimento ou sincronismo a partir de imagens paradas."
]
}
Para a resposta, use saída estruturada: em vez de pedir texto, o script declara uma ferramenta registrar_qa com esquema JSON e pede ao Claude que a chame, como entregar um formulário de campos fixos em vez de uma folha em branco. As categorias do esquema vêm da própria rubrica, e a ferramenta leva strict: true, que obriga a entrada a seguir o esquema; sem essa opção, o enum é só uma orientação e pode voltar uma categoria fora da lista. O modo estrito tem regras próprias: todo objeto precisa de additionalProperties: false, limites numéricos como minimum e maximum não são aceitos e a caixa das letras de um valor do enum não é garantida. Por isso o avaliar_qa.py continua conferindo a faixa da confiança e a categoria, sem diferenciar maiúsculas. O tool_choice fica no padrão, porque o Sonnet 5.5 recusa a chamada forçada de ferramenta com erro 400; quem conduz o modelo ao formulário é o pedido. Se a resposta vier em texto mesmo assim, o script tenta ler JSON e, se falhar, manda para revisão humana.
Antes da chamada paga, o script soma os tokens visuais, confere 10 MB por imagem e a margem dos 32 MB por requisição, grava o manifesto dos quadros e pede que você digite SIM. Os rótulos vão antes de cada imagem (Quadro 1, Quadro 2), como pede a seção de várias imagens do guia de visão, para que o relatório cite o quadro certo.
"""scripts/revisar_quadros.py: envia quadros rotulados ao Claude e recebe o QA num formulário fixo.
Uso, na raiz video-ia: python3 scripts/revisar_quadros.py qa/frames/qa11-fps
Requer: pip install anthropic, ANTHROPIC_API_KEY no ambiente e config/rubrica.json.
Triagem barata: QA_MODELO=claude-haiku-5-5 python3 scripts/revisar_quadros.py <pasta>
"""
import base64
import json
import math
import os
import subprocess
import sys
from pathlib import Path
import anthropic
# O revisor padrão é o Sonnet 5.5; QA_MODELO=claude-haiku-5-5 faz a triagem.
MODELO = os.environ.get("QA_MODELO", "claude-sonnet-5-5")
# US$ por milhão de tokens de entrada, tabela de 12/10/2026 (Haiku 5.5: até 100 mil tokens de prompt).
PRECOS_ENTRADA = {"claude-sonnet-5-5": 2.0, "claude-haiku-5-5": 0.10}
PRECO_ENTRADA_POR_MILHAO = PRECOS_ENTRADA[MODELO]
MAX_IMAGENS = 40 # política do exercício (a API aceita até 600 por requisição)
LIMITE_IMAGEM_B64 = 10_000_000 # 10 MB por imagem na API direta (5 MB no Bedrock e no Google Cloud)
LIMITE_REQUISICAO = 30_000_000 # margem abaixo dos 32 MB por requisição
TIPOS = {".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp"}
pasta = Path(sys.argv[1] if len(sys.argv) > 1 else "qa/frames/qa11-fps")
rubrica = json.loads(Path("config/rubrica.json").read_text(encoding="utf-8"))
quadros = sorted(p for p in pasta.iterdir() if p.suffix.lower() in TIPOS)[:MAX_IMAGENS]
if not quadros:
raise SystemExit(f"Nenhum quadro em {pasta}.")
arq_tempos = pasta / "tempos.txt"
tempos = arq_tempos.read_text().split() if arq_tempos.exists() else []
def dimensoes(caminho):
saida = subprocess.run(
["ffprobe", "-v", "error", "-select_streams", "v:0",
"-show_entries", "stream=width,height", "-of", "json", str(caminho)],
check=True, capture_output=True, text=True,
).stdout
s = json.loads(saida)["streams"][0]
return s["width"], s["height"]
conteudo, manifesto, tokens_visuais = [], [], 0
for numero, caminho in enumerate(quadros, start=1):
largura, altura = dimensoes(caminho)
if len(quadros) > 20 and max(largura, altura) > 2000:
raise SystemExit(f"{caminho}: com mais de 20 imagens, cada lado fica em até 2000 px.")
tokens_visuais += math.ceil(largura / 28) * math.ceil(altura / 28)
dados = base64.standard_b64encode(caminho.read_bytes()).decode("ascii")
if len(dados) >= LIMITE_IMAGEM_B64:
raise SystemExit(f"{caminho}: passa de 10 MB em base64.")
segundo = tempos[numero - 1] if numero <= len(tempos) else "desconhecido"
manifesto.append({"quadro": numero, "arquivo": str(caminho), "segundo": segundo,
"largura": largura, "altura": altura})
# Rótulo ANTES de cada imagem: é assim que o relatório consegue citar o quadro certo.
conteudo.append({"type": "text", "text": f"Quadro {numero} (aprox. {segundo} s):"})
conteudo.append({"type": "image", "source": {
"type": "base64", "media_type": TIPOS[caminho.suffix.lower()], "data": dados}})
ids = [c["id"] for c in rubrica["categorias"]]
conteudo.append({"type": "text", "text": (
"Faça o QA visual somente destas imagens, seguindo a rubrica do sistema. "
f"Responda chamando a ferramenta registrar_qa com os {len(quadros)} quadros, "
"cada um exatamente uma vez, e problemas vazio quando não houver defeito."
)})
# Saída estruturada: um formulário com campos fixos em vez de texto livre.
ferramenta = {
"name": "registrar_qa",
"description": "Registra o resultado do QA visual, um item por quadro recebido.",
"strict": True, # a entrada segue o esquema; o enum deixa de ser só orientação
"input_schema": {
"type": "object",
"properties": {"quadros": {"type": "array", "items": {
"type": "object",
"properties": {
"quadro": {"type": "integer"},
# O modo estrito não aceita minimum/maximum: a faixa 0 a 1 vai na descrição
# e o avaliar_qa.py confere.
"confianca": {"type": "number", "description": "Autoavaliação de 0 a 1."},
"problemas": {"type": "array", "items": {
"type": "object",
"properties": {
"categoria": {"type": "string", "enum": ids},
"gravidade": {"type": "string", "enum": ["alta", "media", "baixa"]},
"evidencia": {"type": "string"},
},
"required": ["categoria", "gravidade", "evidencia"],
"additionalProperties": False,
}},
},
"required": ["quadro", "confianca", "problemas"],
"additionalProperties": False,
}}},
"required": ["quadros"],
"additionalProperties": False,
},
}
sistema = (
"Você é revisor de QA visual de vídeo. Rubrica em JSON:\n"
+ json.dumps(rubrica, ensure_ascii=False)
+ "\nA confiança é uma autoavaliação de 0 a 1, não uma probabilidade calibrada."
)
tamanho = len(json.dumps(conteudo)) + len(sistema)
if tamanho >= LIMITE_REQUISICAO:
raise SystemExit("Requisição perto dos 32 MB: divida o lote.")
Path("qa").mkdir(exist_ok=True)
Path("qa/qa11-quadros.json").write_text(
json.dumps(manifesto, ensure_ascii=False, indent=2), encoding="utf-8")
if MODELO == "claude-haiku-5-5" and tokens_visuais > 90_000:
raise SystemExit("Triagem perto do degrau de 100 mil tokens do Haiku 5.5: divida o lote.")
custo = tokens_visuais * PRECO_ENTRADA_POR_MILHAO / 1_000_000
print(f"{MODELO}: {len(quadros)} imagens, {tokens_visuais} tokens visuais, US$ {custo:.4f} de entrada visual.")
print("A estimativa não inclui o texto, a rubrica nem os tokens de saída.")
if input("Autoriza esta chamada paga? Digite SIM: ").strip() != "SIM":
raise SystemExit("Chamada cancelada; quadros e manifesto preservados.")
cliente = anthropic.Anthropic() # lê ANTHROPIC_API_KEY do ambiente
resposta = cliente.messages.create(
model=MODELO,
max_tokens=8000,
system=sistema,
tools=[ferramenta], # tool_choice fica no padrão (auto): o Sonnet 5.5 recusa chamada forçada
messages=[{"role": "user", "content": conteudo}],
)
print("Uso informado pela API:", resposta.usage)
if resposta.stop_reason == "max_tokens":
raise SystemExit("Resposta truncada: encaminhe para revisão humana ou divida o lote.")
bloco = next((b for b in resposta.content if b.type == "tool_use"), None)
if bloco is not None:
relatorio = bloco.input
else:
# Plano B: o modelo respondeu em texto. Tenta ler JSON; se não der, revisão humana.
texto = "".join(b.text for b in resposta.content if b.type == "text")
try:
relatorio = json.loads(texto)
except json.JSONDecodeError:
raise SystemExit("Resposta fora do formato: encaminhe para revisão humana.")
Path("qa/qa11-relatorio.json").write_text(
json.dumps(relatorio, ensure_ascii=False, indent=2), encoding="utf-8")
print("Relatório salvo em qa/qa11-relatorio.json")
Se a figura ultrapassar a área visível, deslize para os lados. Pelo teclado, foque a figura e use as setas.
O validador abaixo confere a cobertura (todo quadro enviado volta exatamente uma vez), a confiança contra o limiar de 0,85 da rubrica e as categorias. Esse limiar é regra de encaminhamento do exercício: a confiança que o modelo declara é uma autoavaliação, não uma probabilidade calibrada de acerto. Por isso o campo publicacao_autorizada sai sempre falso.
"""scripts/avaliar_qa.py: confere cobertura e formato do QA. Nunca autoriza publicação sozinho."""
import json
import math
from pathlib import Path
rubrica = json.loads(Path("config/rubrica.json").read_text(encoding="utf-8"))
esperados = json.loads(Path("qa/qa11-quadros.json").read_text(encoding="utf-8"))
relatorio = json.loads(Path("qa/qa11-relatorio.json").read_text(encoding="utf-8"))
LIMIAR = rubrica["limiar_confianca"] # 0.85, regra do exercício
CATEGORIAS = {c["id"].lower() for c in rubrica["categorias"]}
itens = relatorio.get("quadros") if isinstance(relatorio, dict) else None
motivos = []
if not isinstance(itens, list):
itens, motivos = [], ["Campo quadros ausente ou inválido."]
recebidos = [i.get("quadro") for i in itens if isinstance(i, dict)]
if sorted(map(str, recebidos)) != sorted(str(q["quadro"]) for q in esperados):
motivos.append("Cobertura incompleta, quadro repetido ou número inesperado.")
for item in itens:
if not isinstance(item, dict):
motivos.append("Item de quadro inválido.")
continue
q, conf, probs = item.get("quadro"), item.get("confianca"), item.get("problemas")
if type(conf) not in (int, float) or not math.isfinite(conf) or not 0 <= conf <= 1:
motivos.append(f"Quadro {q}: confiança inválida.")
elif conf < LIMIAR:
motivos.append(f"Quadro {q}: confiança {conf} abaixo de {LIMIAR}.")
if not isinstance(probs, list):
motivos.append(f"Quadro {q}: lista de problemas inválida.")
continue
for p in probs:
# A caixa das letras do enum não é garantida: compara em minúsculas.
if not isinstance(p, dict) or str(p.get("categoria")).lower() not in CATEGORIAS:
motivos.append(f"Quadro {q}: categoria fora da rubrica.")
else:
motivos.append(f"Quadro {q}: {p['categoria']} ({p.get('gravidade')}): {p.get('evidencia')}")
decisao = {
"estado": "revisao_humana_prioritaria" if motivos else "sem_achados_na_amostra_aguarda_revisao_humana",
"motivos": motivos,
"publicacao_autorizada": False, # sempre falso: quem autoriza é uma pessoa
"observacao": "A amostra não comprova áudio, sincronismo nem continuidade entre quadros.",
}
Path("qa/qa11-decisao.json").write_text(
json.dumps(decisao, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(decisao, ensure_ascii=False, indent=2))
Validar o relatório e decidir com o olho humano
Quatro situações pedem decisão. Se a legenda parecer ilegível na amostra de 960 pixels, extraia o mesmo instante em resolução maior antes de concluir. Se houver confiança abaixo do limiar ou quadro faltando no relatório, encaminhe para revisão humana. Se o defeito for de movimento ou sincronismo, assista ao trecho com áudio, porque imagens paradas não bastam. Se houver mais imagens do que o orçamento comporta, faça uma triagem em baixa resolução e revise só os trechos suspeitos com mais detalhe; quadros que voltam a cada rodada podem ir por file_id da Files API, sem reenviar o base64. Para comparar duas versões de uma peça com critérios estáveis, o curso do Astra tem uma aula sobre avaliar a interface com critérios comparáveis.
Na Cozinha da Rafa, o QA do corte 9:16 do bolo de rolo aponta "açúcar" encostado na borda direita no quadro 7, categoria legenda cortada. A correção entra na composição, e não no bruto. A Rafa ainda assiste ao corte inteiro com som, para conferir se a explicação da receita sobreviveu aos cortes.
Faça a triagem no Haiku 5.5 antes do Sonnet 5.5
Quando a série cresce, pague o revisor caro só pelos quadros duvidosos. O Haiku 5.5, lançado em 07/10/2026, lê os 40 quadros de 960 × 540 por cerca de US$ 0,0028 de entrada visual, um vigésimo do Sonnet 5.5, porque custa US$ 0,10 por milhão de tokens até 100 mil tokens de prompt. Ele separa os quadros sem achado e com confiança acima do limiar; os outros vão ao Sonnet 5.5 com a mesma rubrica. O veredito não fica com o Haiku: no Chartography, teste de leitura de gráficos sem ferramentas, ele marca 46,4% contra 61,6% do Sonnet 5.5.
Rode a triagem com QA_MODELO=claude-haiku-5-5, separe os marcados com o script abaixo e rode a revisão normal só na pasta deles. Deu certo quando qa/qa11-triagem-relatorio.json cobre todos os quadros e a pasta qa11-marcados tem só os duvidosos. Erro comum: tratar quadro sem achado na triagem como aprovado; ele continua esperando a pessoa que assiste com áudio. Desde 07/10/2026, a leitura de cache do Sonnet 5.5 custa US$ 0,10 por milhão de tokens, e a rubrica que se repete entre renders fica mais barata quando vai em cache.
Se a figura ultrapassar a área visível, deslize para os lados. Pelo teclado, foque a figura e use as setas.
#!/usr/bin/env bash
# Rode na raiz video-ia, depois de extrair a amostra em qa/frames/qa11-fps.
set -euo pipefail
# 1. Triagem barata: o mesmo script, com o Haiku 5.5.
QA_MODELO=claude-haiku-5-5 python3 scripts/revisar_quadros.py qa/frames/qa11-fps
# 2. Guarda a triagem e copia os quadros duvidosos para qa/frames/qa11-marcados.
python3 scripts/separar_marcados.py
# 3. Revisor padrão (Sonnet 5.5) só nos marcados; depois, o validador de sempre.
python3 scripts/revisar_quadros.py qa/frames/qa11-marcados
python3 scripts/avaliar_qa.py
"""scripts/separar_marcados.py: guarda a triagem do Haiku 5.5 e separa os quadros para o Sonnet 5.5.
Rode na raiz video-ia logo depois da triagem. Não chama API nenhuma.
"""
import json
import shutil
from pathlib import Path
rubrica = json.loads(Path("config/rubrica.json").read_text(encoding="utf-8"))
LIMIAR = rubrica["limiar_confianca"]
qa = Path("qa")
destino = qa / "frames" / "qa11-marcados"
if destino.exists():
raise SystemExit(f"{destino} já existe: arquive a triagem anterior.")
# A triagem vira registro próprio; a rodada do Sonnet grava os arquivos padrão de novo.
(qa / "qa11-relatorio.json").rename(qa / "qa11-triagem-relatorio.json")
(qa / "qa11-quadros.json").rename(qa / "qa11-triagem-quadros.json")
relatorio = json.loads((qa / "qa11-triagem-relatorio.json").read_text(encoding="utf-8"))
lista = json.loads((qa / "qa11-triagem-quadros.json").read_text(encoding="utf-8"))
manifesto = {q["quadro"]: q for q in lista}
itens = relatorio.get("quadros", []) if isinstance(relatorio, dict) else []
vistos = {i.get("quadro") for i in itens if isinstance(i, dict)}
marcados = set(manifesto) - vistos # quadro que a triagem pulou vai para o Sonnet
for item in itens:
if not isinstance(item, dict):
continue
conf = item.get("confianca")
if item.get("problemas") or type(conf) not in (int, float) or conf < LIMIAR:
marcados.add(item.get("quadro"))
marcados &= set(manifesto)
if not marcados:
raise SystemExit("Nenhum quadro marcado: siga para a pessoa que assiste com áudio.")
destino.mkdir(parents=True)
tempos = []
for n in sorted(marcados):
origem = Path(manifesto[n]["arquivo"])
shutil.copy2(origem, destino / f"m_{n:03d}{origem.suffix}")
tempos.append(str(manifesto[n]["segundo"]))
(destino / "tempos.txt").write_text("\n".join(tempos) + "\n", encoding="utf-8")
print(f"{len(marcados)} de {len(manifesto)} quadros marcados em {destino}.")
Rodar o QA visual de um render do começo ao veredito
Use o render atual em renders/revisao.mp4 e o config/rubrica.json, rodando tudo na raiz video-ia.
Passo 1: Identifique o render e a versão que vai revisar
Deu certo quando: O arquivo corresponde à versão atual da composição.
Erro comum: Revisar quadros de um render antigo.
Passo 2: Escreva a rubrica antes da chamada
Deu certo quando: Cada categoria tem um critério que se vê no quadro e uma gravidade.
Erro comum: Pedir qualidade profissional sem dizer o que verificar.
Passo 3: Extraia a amostra e abra três quadros
Tempo fixo para plano longo; corte de cena para vídeo com cortes rápidos.
Deu certo quando: Os quadros abrem, estão legíveis e têm segundo anotado em tempos.txt.
Erro comum: Enviar quadros pretos por erro de extração ou menores que 200 px.
Passo 4: Confira a estimativa e autorize a chamada
Deu certo quando: O script mostra número de imagens, tokens visuais e custo de entrada visual.
Erro comum: Apresentar esse subtotal como o custo completo do QA.
Passo 5: Valide o relatório com scripts/avaliar_qa.py
Deu certo quando: Todo quadro enviado aparece uma vez, e a decisão fica gravada em qa/qa11-decisao.json.
Erro comum: Aceitar relatório truncado como aprovação.
Passo 6: Confronte cada achado com a imagem citada
Deu certo quando: Cada defeito confirmado tem quadro e frase de evidência.
Erro comum: Corrigir um suposto defeito sem olhar o quadro.
Passo 7: Assista ao vídeo com áudio antes da entrega
Deu certo quando: Som, sincronismo e continuidade conferidos por uma pessoa, com a decisão registrada.
Erro comum: Publicar só porque a amostra visual veio sem achados.
No fim você tem: Quadros, manifesto, relatório validado e decisão humana registrada antes da entrega.
Guia prático: QA da série em lote, com caixa no defeito
O script da aula revisa um render por vez e diz em que quadro está o problema. Quando a série cresce, duas coisas ajudam: mandar todos os renders da semana de uma vez pela Batch API, o processamento em lote que devolve em até 24 horas pela metade do preço, e receber a posição do defeito no quadro como caixa em pixels, para desenhar o retângulo antes da revisão humana. Este guia monta os dois no projeto video-ia.
Funções, casos de uso e aplicações
Funções
Recursos de visão e de cobrança para o QA da série
| Função ou recurso | O que faz | Quando usar | Desde |
|---|---|---|---|
| Batch API | Processa pedidos de forma assíncrona com 50% de desconto na entrada e na saída; no Sonnet 5.5, US$ 1 e US$ 5 por milhão de tokens | QA de todos os renders da semana, quando a resposta pode esperar | Preço em vigor em 12/10/2026 |
| Caixa em pixels [x1, y1, x2, y2] | O Claude devolve a região do defeito em pixels absolutos da imagem que ele vê; a posição é aproximada | Desenhar o retângulo no quadro antes de a pessoa revisar | Em vigor em 12/10/2026 |
| transformations com oversized_image: error | Recusa a imagem com erro 400 em vez de reduzi-la sem avisar | Garantir que a caixa devolvida bate com o quadro de 960 px que você guardou | Em vigor em 12/10/2026 |
| Imagem antes do texto | A documentação indica melhor resultado com as imagens antes da pergunta | Rótulo, imagem e, só no fim, o pedido de QA | Em vigor em 12/10/2026 |
| Imagens de sobra numa sessão do Claude Code | Corrigiu o erro "Request rejected as unprocessable" em sessões com centenas de imagens | Triagem interativa lendo qa/frames/ com Read dentro da sessão | 05/10/2026 (v2.1.290) |
| Combinação de lote e cache | Os descontos do lote e da leitura de cache se somam | Rubrica igual em todos os pedidos do lote | Em vigor em 12/10/2026 |
Um lote aceita até 100 mil pedidos ou 256 MB, a maioria termina em menos de uma hora, o pedido que não termina em 24 horas expira e os resultados ficam disponíveis por 29 dias. Para alvos pequenos, como uma letra cortada, a documentação sugere recortar a região e somar a origem do recorte às coordenadas devolvidas.
Fonte: Claude Platform Docs, Vision, Coordinates and bounding boxes e Pricing; changelog do Claude Code v2.1.290; 12/10/2026
Casos de uso
Responsável por qualidade de uma edtech de Goiânia que publica 12 videoaulas por semana
Educação, cursos em vídeo
Dor
Rodar o QA aula por aula ao longo do dia ocupava a máquina e o orçamento de revisão no preço cheio, mesmo sem pressa para o resultado.
Solução
Ela passou a extrair os quadros de todas as aulas na sexta, enviar um lote com um pedido por aula e buscar os resultados na segunda, antes da revisão humana.
Resultado
O que observar: no fim do mês, o custo de QA por aula comparado ao mês anterior e se algum defeito marcado no lote escapou da revisão com áudio.
Cenário construído para fins didáticos. Números não correspondem a cliente específico.
Gerente de uma agência de social media em Salvador que aprova cortes 9:16 com os clientes
Marketing digital, vídeos para redes sociais
Dor
O relatório dizia "legenda cortada no quadro 7", e o cliente não achava o problema no celular, o que gerava rodadas de mensagens.
Solução
Com a caixa em pixels pedida para cada achado, ele desenhou o retângulo vermelho no quadro com ffmpeg antes de mandar ao cliente a prancha de aprovação.
Resultado
O que observar: nas próximas dez aprovações, quantas rodadas de mensagem cada corte levou e se alguma caixa apontou o lugar errado.
Cenário construído para fins didáticos. Números não correspondem a cliente específico.
Na prática
O script cria um pedido por pasta de qa/frames/, com os quadros rotulados, a rubrica da aula no sistema e um formulário que pede a caixa de cada achado. As imagens levam oversized_image: error: os quadros de 960 px cabem no Sonnet 5.5 sem redução, e qualquer quadro maior, por engano, é recusado em vez de reduzido. O nome da pasta vira o custom_id, que aceita só letras, números, hífen e sublinhado.
"""QA de vários renders pela Batch API. Na raiz video-ia:
python3 scripts/qa_em_lote.py enviar (um pedido por pasta de qa/frames/)
python3 scripts/qa_em_lote.py buscar <id> (grava qa/lote/<pasta>.json)"""
import base64, json, sys
from pathlib import Path
import anthropic
cliente = anthropic.Anthropic() # lê ANTHROPIC_API_KEY do ambiente
achado = {"type": "object", "additionalProperties": False,
"required": ["quadro", "categoria", "caixa", "evidencia"],
"properties": {"quadro": {"type": "integer"}, "categoria": {"type": "string"},
"caixa": {"type": "array", "items": {"type": "integer"},
"description": "[x1, y1, x2, y2] em pixels do quadro enviado"},
"evidencia": {"type": "string"}}}
ferramenta = {"name": "registrar_achados", "strict": True, "description": "Um item por defeito.",
"input_schema": {"type": "object", "additionalProperties": False, "required": ["achados"],
"properties": {"achados": {"type": "array", "items": achado}}}}
def pedido(pasta):
conteudo = []
for n, img in enumerate(sorted(pasta.glob("*.jpg"))[:40], start=1):
conteudo += [{"type": "text", "text": f"Quadro {n}:"},
{"type": "image", "transformations": {"oversized_image": "error"},
"source": {"type": "base64", "media_type": "image/jpeg",
"data": base64.standard_b64encode(img.read_bytes()).decode()}}]
conteudo.append({"type": "text", "text": "Aplique a rubrica e chame registrar_achados; caixa em pixels absolutos."})
return {"custom_id": pasta.name, "params": {
"model": "claude-sonnet-5-5", "max_tokens": 4000, "tools": [ferramenta],
"system": "Rubrica de QA:\n" + Path("config/rubrica.json").read_text(encoding="utf-8"),
"messages": [{"role": "user", "content": conteudo}]}}
if sys.argv[1:2] == ["enviar"]:
pastas = sorted(p for p in Path("qa/frames").iterdir() if p.is_dir() and any(p.glob("*.jpg")))
if input(f"{len(pastas)} renders no lote. Digite SIM: ").strip() != "SIM":
raise SystemExit("Lote cancelado.")
print("Lote criado:", cliente.messages.batches.create(requests=[pedido(p) for p in pastas]).id)
elif sys.argv[1:2] == ["buscar"]:
lote = cliente.messages.batches.retrieve(sys.argv[2])
if lote.processing_status != "ended":
raise SystemExit(f"Ainda processando: {lote.request_counts}")
Path("qa/lote").mkdir(parents=True, exist_ok=True)
for r in cliente.messages.batches.results(lote.id):
bloco = next((b.input for b in r.result.message.content if b.type == "tool_use"), None) \
if r.result.type == "succeeded" else None
saida = bloco if bloco is not None else {"erro": r.result.type, "encaminhar": "revisão humana"}
Path(f"qa/lote/{r.custom_id}.json").write_text(json.dumps(saida, ensure_ascii=False, indent=2), encoding="utf-8")
print("Achados em qa/lote/")Do lote da semana ao quadro marcado para a revisão humana
Passo 1: Extraia os quadros de cada render numa pasta própria
Use os scripts da aula com SAIDA diferente por render, como qa/frames/bolo-de-rolo-9x16.
Deu certo quando: Cada pasta tem até 40 JPEG de 960 px no lado maior.
Erro comum: Nome de pasta com acento ou espaço, que o custom_id do lote recusa.
Passo 2: Envie o lote e guarde o id
python3 scripts/qa_em_lote.py enviar
Deu certo quando: O terminal mostra o id do lote; anote em logs/.
Erro comum: Usar o lote quando a peça precisa sair hoje: a resposta pode levar até 24 horas.
Passo 3: Busque os achados
python3 scripts/qa_em_lote.py buscar <id>
Deu certo quando: qa/lote/ tem um JSON por render; os que falharam dizem revisão humana.
Erro comum: Tratar render sem achados como aprovado.
Passo 4: Desenhe a caixa no quadro citado
Para a caixa [812, 1500, 1012, 1590]: ffmpeg -i qa/frames/<pasta>/q_007.jpg -vf "drawbox=x=812:y=1500:w=200:h=90:color=red@0.8:t=4" qa/lote/q_007-caixa.jpg
Deu certo quando: O retângulo cai sobre o defeito descrito na evidência.
Erro comum: Desenhar sobre o render em resolução cheia sem converter as coordenadas do quadro de 960 px.
No fim você tem: Achados da série inteira pela metade do preço, com o defeito marcado no quadro para a pessoa que assiste com áudio.
Armadilhas
Caixa e lote mal usados contra bem usados
Evite
Coluna a evitar
- Pedir coordenadas de 0 a 1000; a documentação diz que funciona mal
- Dividir pelas dimensões com preenchimento, que deslocam toda caixa
- Enviar o quadro em 4K e desenhar a caixa nele como se não tivesse sido reduzido
- Usar o lote para o corte que precisa ser aprovado hoje
- Tomar a caixa como medida exata para cortar a composição
Prefira
Coluna recomendada
- Pixels absolutos [x1, y1, x2, y2] pedidos no formulário
- Quadro já reduzido por você, para a caixa valer no arquivo guardado
- oversized_image: error para recusar o quadro que seria reduzido
- Lote para a série da semana e o script da aula para a peça urgente
- A caixa como indicação de onde a pessoa deve olhar
Rode a amostra, a revisão e o validador no próximo render da sua série e guarde os arquivos qa/qa11-*.json. Está pronto quando cada achado do relatório foi classificado diante da imagem, os quadros com confiança abaixo de 0,85 passaram por uma pessoa, os marcados na triagem do Haiku 5.5 passaram pelo Sonnet 5.5 e alguém assistiu ao vídeo inteiro com áudio antes de autorizar a publicação.
Seu caderno neste capítulo
Abrir o caderno completoSelecione um trecho do capítulo para destacar ou anotar. Nos vídeos e áudios, use Anotar este momento. No teclado, selecione com Shift e as setas e use Alt+Shift+D para destacar ou Alt+Shift+N para anotar.
Salvo neste navegador. Entre na sua conta para levar o caderno a outros aparelhos.
Entre na sua conta para compartilhar o que aprendeu e convidar alguém para estudar com você.
Conexões deste capítulo
Veja o que estudar antes e para onde seguir depois, explore os conceitos e compare abordagens em outros cursos. Sugestões marcadas como “sugerido pelo grafo” vêm dos assuntos em comum, não do índice do curso.
Estude antes
- CursoClaude Code CLI: Programação com IA
- Veja o que o Claude Code faz antes de instalarsugerido pelo grafoApresenta o conceito “Claude Code”, usado em “Revisar quadros com a visão do Claude e uma rubrica escrita”.
- CursoAstra profundosugerido pelo grafoA quinta parte do Astra planeja o vídeo e edita o material bruto com o modelo dirigindo a ferramenta. O curso de vídeo com IA aprofunda esse trabalho com Claude Code, Codex e ElevenLabs, do roteiro à publicação. O grafo já sugeria esta ordem pela regra de entrada da trilha; com a ordem anterior curada, ela passa a ser declarada (Etapa 4, wave 3).
- CursoSetup Completo: do zero ao Claude Codesugerido pelo grafo7 capítulos de “Vídeo com IA: Claude Code, Codex e ElevenLabs” usam conceitos apresentados em “Setup Completo: do zero ao Claude Code”.
Depois, siga para
- CursoFrontends com Vibecodingsugerido pelo grafo21 capítulos de “Frontends com Vibecoding” usam conceitos apresentados em “Vídeo com IA: Claude Code, Codex e ElevenLabs”.
- CursoDo Agile ao Agentic Operating Modelsugerido pelo grafo5 capítulos de “Do Agile ao Agentic Operating Model” usam conceitos apresentados em “Vídeo com IA: Claude Code, Codex e ElevenLabs”.
- CursoAgentes de IA Autônomos: Do Conceito à Produçãosugerido pelo grafo7 capítulos de “Agentes de IA Autônomos: Do Conceito à Produção” usam conceitos apresentados em “Vídeo com IA: Claude Code, Codex e ElevenLabs”.
- CursoLLM FinOps: o custo da IA para quem assina o orçamentosugerido pelo grafo4 capítulos de “LLM FinOps: o custo da IA para quem assina o orçamento” usam conceitos apresentados em “Vídeo com IA: Claude Code, Codex e ElevenLabs”.
Conceitos deste capítulo
O mesmo assunto em outros cursos
- Frontends com VibecodingConstruir profundidade e dirigir a montagem de vídeoExaminar conexões de Construir profundidade e dirigir a montagem de vídeo
- Prompt Engineering para ExecutivosAvaliar a IA com uma planilha de 20 a 50 tarefas reaisExaminar conexões de Avaliar a IA com uma planilha de 20 a 50 tarefas reais
- RAG em produçãoA régua: 120 perguntas com resposta conhecidaExaminar conexões de A régua: 120 perguntas com resposta conhecida
- Astra profundoConstruir uma visualização arquitetônica no BlenderExaminar conexões de Construir uma visualização arquitetônica no Blender
Voltar ao capítulo anterior: Paralelizar cenas com subagentes sem estourar o limite das APIs
Leve uma ideia para a prática
Escolha uma atividade no painel e volte a este curso quando quiser.
Atividades e evolução ficam reunidas no painel da sua conta.
Laboratórios ao vivo deste curso
Cada laboratório monta a biblioteca real dentro da aula, com controles para você mexer e o código do exemplo para copiar. O texto de reserva descreve a demonstração para quem lê sem JavaScript. O link abre o capítulo já no ponto do laboratório.
MediaRecorder, Canvas 2D e Long Animation Frames (plataforma)
Grave a cena em tempo real e veja os quadros que se perdem
Suba as partículas, force um bloqueio de 300 ms e grave seis segundos; compare com o render quadro a quadro desta aula.
Pacote
Abrir no capítuloMontar vídeo em HTML com HyperFrames e escolher entre ele e o Remotionsem pacoteMotion
Ver agentes em paralelo trocando mensagens numa linha do tempo
Reproduza a cena, percorra o tempo pelo controle e compare o tempo de parede com a soma das barras de cada agente.
Pacote
Abrir no capítuloParalelizar cenas com subagentes sem estourar o limite das APIsmotionReact (sem dependência extra), com as APIs do Veo e do fal.ai
Prompt de vídeo em cinco camadas, pronto para a API
Escolha plano, câmera, luz, ação e áudio e copie o prompt e o JSON para o Veo ou para o fal.ai.
Pacote
Abrir no capítuloEscrever prompt de vídeo em cinco camadas e manter o personagem consistentereactReact (sem dependência extra), com o comando para o FFmpeg
Corte o vídeo apagando palavras da transcrição
Aperte as palavras para removê-las, ligue a remoção de pausas e copie o comando FFmpeg e a legenda SRT gerados.
Pacote
Abrir no capítuloRemover pausas e reenquadrar o vídeo de 16:9 para 9:16 com legenda por palavrareactReact (sem dependência extra)
Quanto custa cada etapa do seu pipeline de vídeo
Troque a ferramenta de cada etapa e o percentual de b-roll e veja qual etapa pesa no custo por minuto e por vídeo.
Pacote
Abrir no capítuloOrquestrar o pipeline com etapas idempotentes e teto de gastoreact
Capítulos vizinhos em Vídeo com IA: Claude Code, Codex e ElevenLabs
- 09Conectar fal, Replicate e ElevenLabs por MCP com permissões mínimas
- 10Paralelizar cenas com subagentes sem estourar o limite das APIs
- 11Revisar quadros com a visão do Claude e uma rubrica escrita
- 12Configurar o Codex com GPT-6 Astra num sandbox que pede aprovação
- 13Gerar storyboard e miniaturas com gpt-image no lugar do Sora