E1 · Captura
5 minRode o bloco "capturar o app".
Pronto quando: captura: 0, as cinco fotos de 00-home.png a 04-resultado.png e passos no steps.json: 5.
Aula 3 · 20 min
Para mostrar um app, a tela real convence mais que qualquer animação. O video-demonstrativo dirige um navegador pelo seu app, tira uma foto por passo com a posição do elemento clicado, narra com voz local e monta o vídeo com cursor e zoom. Nesta aula você faz isso inteiro, com um app de exemplo, sem crédito.
Começar a aula
Objetivo: capturar um app passo a passo com o agente e renderizar a demonstração narrada que passa no conferidor.
Clique em cada etapa.
Etapas da skill video-demonstrativo no commit a87b382 (05/10/2026). Captura antes de animar: o vídeo mostra a tela de verdade.
Um app de uma página no kit (quantas palavras cabem numa narração) e o actions.json com cinco passos.
{
"url": "http://127.0.0.1:18791/",
"viewport": [1280, 800],
"window": { "top": 96, "titleH": 52, "urlLabel": "127.0.0.1:18791" },
"eyebrow": "DEMONSTRAÇÃO · MINUTOS DE VÍDEO",
"ctaNarration": "Pronto: agora você sabe quantas palavras cabem no seu vídeo.",
"ctaCaption": "Roteiro do tamanho certo",
"steps": [
{ "id": "home", "intro": true, "caption": "Minutos de vídeo · do zero", "narration": "Este app diz quantas palavras cabem numa narração. Vamos usar em três cliques." },
{ "id": "minutos", "do": { "type": "setValue", "selector": "#min", "value": "3" }, "target": "#min", "caption": "1 · Duração: 3 minutos", "narration": "Primeiro, a duração do vídeo: três minutos." },
{ "id": "ritmo", "do": { "type": "setValue", "selector": "#ppm", "value": "140" }, "target": "#ppm", "caption": "2 · Ritmo: 140 palavras por minuto", "narration": "Depois, o ritmo da fala. Uma narração calma fica perto de cento e quarenta palavras por minuto." },
{ "id": "calcular", "do": { "type": "clickText", "tag": "button", "text": "Calcular" }, "target": { "tag": "button", "text": "Calcular" }, "click": true, "caption": "3 · Calcular", "narration": "Agora é só clicar em calcular." },
{ "id": "resultado", "target": "#res", "zoom": true, "caption": "Cabem 420 palavras", "narration": "Cabem quatrocentas e vinte palavras. Esse é o tamanho do seu roteiro." }
]
}O bloco sobe o app numa porta local, roda a captura numa sessão própria do agent-browser e fecha tudo no fim.
K=~/projetos/formacao-cinema-ia-fabrica-v72/kit
command -v agent-browser > /dev/null || { echo "FALTA agent-browser: npm i -g agent-browser@0.6.0 && agent-browser install"; exit 1; }
E=${TMPDIR:-/tmp}/ensaio-fabrica/a3; rm -rf "$E"; mkdir -p "$E"; cd "$E"
git clone -q https://github.com/inematds/video-demonstrativo.git && git -C video-demonstrativo checkout -q a87b382
cp video-demonstrativo/skills/video-demonstrativo/scripts/capture.mjs "$K/exemplos/a3-demo/actions.json" .
python3 -m http.server 18791 --bind 127.0.0.1 --directory "$K/exemplos/a3-demo" > servidor.log 2>&1 & S=$!
until curl -s -o /dev/null http://127.0.0.1:18791/; do sleep 0.3; done
export AGENT_BROWSER_SESSION=ensaio-fabrica
node capture.mjs actions.json > captura.log 2>&1; echo "captura: $?"
agent-browser close > /dev/null 2>&1; kill $S
ls assets/shots
python3 -c "import json; s=json.load(open('steps.json')); print('passos no steps.json:', len(s['steps']))"captura: 0 00-home.png 01-minutos.png 02-ritmo.png 03-calcular.png 04-resultado.png passos no steps.json: 5
Uma foto por passo e o steps.json com as caixas medidas. O until curl espera a porta responder antes de abrir o navegador; sleep fixo falha em máquina carregada.
O bloco para na primeira linha com FALTA agent-browser: npm i -g agent-browser@0.6.0 && agent-browser install. O pacote é Apache-2.0 (npm, 19/10/2026); o install baixa o navegador. A sessão AGENT_BROWSER_SESSION=ensaio-fabrica evita mexer em outra sessão que você tenha aberta.
Cria o projeto HyperFrames, copia fotos, fontes e steps.json, gera uma fala por passo e escreve a composição.
export HYPERFRAMES_NO_TELEMETRY=1 NO_COLOR=1
E=${TMPDIR:-/tmp}/ensaio-fabrica/a3; S=$E/video-demonstrativo/skills/video-demonstrativo
cd "$E" && rm -rf demo
npx -y hyperframes@0.8.176 init demo --non-interactive > init.log 2>&1; echo "init: $?"
cd demo && mkdir -p assets/audio assets/txt
cp -r ../assets/shots assets/ && cp -r "$S/assets/fonts" assets/ && cp ../steps.json .
cp "$S/scripts/composition-template.mjs" build-demo.mjs
node -e 'const d=require("./steps.json"); const l=d.steps.map(s=>s.narration); l.push(d.ctaNarration);
l.forEach((t,i)=>require("fs").writeFileSync(`assets/txt/s${i+1}.txt`, t.trim()+"\n")); console.log("falas:", l.length)'
for f in assets/txt/s*.txt; do
n=$(basename "$f" .txt)
npx -y hyperframes@0.8.176 tts "$f" --voice pf_dora --lang pt-br --speed 0.98 --output "assets/audio/$n.wav" > /dev/null 2>&1 || echo "falhou $n"
done
ls assets/audio | wc -l
node build-demo.mjs | head -1init: 0 falas: 6 6 index.html gerado · 1920x1080 · TOTAL=30.389s · 6 cenas (5 passos + CTA)
Seis falas (cinco passos e o fecho), seis WAVs. O TOTAL vem da soma das falas com uma folga antes e depois de cada uma: mude a fala, muda a duração.
A skill no commit a87b382 chama npx hyperframes sem versão, e o curso antigo fixava 0.8.66. Os blocos deste curso usam hyperframes@0.8.176 (npm, 19/10/2026) e HYPERFRAMES_NO_TELEMETRY=1. O porquê, e o resto do HyperFrames, está no curso 3.
Check, render em rascunho e o conferidor com a duração que a própria composição declara.
export HYPERFRAMES_NO_TELEMETRY=1 NO_COLOR=1
K=~/projetos/formacao-cinema-ia-fabrica-v72/kit
cd ${TMPDIR:-/tmp}/ensaio-fabrica/a3/demo
npx -y hyperframes@0.8.176 check 2>&1 | grep -E "◇|✗" | head -3
npx -y hyperframes@0.8.176 render --quality draft --workers 2 --quiet --output renders/demo.mp4 > render.log 2>&1
echo "render: $?"
D=$(grep -o 'id="root"[^>]*data-duration="[0-9.]*"' index.html | grep -o "[0-9.]*\"$" | tr -d \")
echo "duração pedida: $D s"
python3 "$K/conferir.py" renders/demo.mp4 "$D" 1920x1080 --audio; echo "saída: $?"◇ 0 errors, 0 warnings ◇ 0 issues across 9 sample(s) ◇ 0 errors, 0 warnings render: 0 duração pedida: 30.389 s OK: 30,4 s · 1920×1080 · 30 fps · com áudio saída: 0
Nesta máquina o render de 30 s levou menos de um minuto (medido fora da captura; render por software, --workers 2). A duração pedida sai do data-duration da raiz, então o conferidor vale para qualquer roteiro seu.
Do app de exemplo ao seu app.
Rode o bloco "capturar o app".
Pronto quando: captura: 0, as cinco fotos de 00-home.png a 04-resultado.png e passos no steps.json: 5.
Rode "narrar e montar" e depois "render e conferir".
Pronto quando: index.html gerado · 1920x1080, render: 0 e OK: … · 1920×1080 · 30 fps · com áudio com saída: 0.
Peça ao seu agente um actions.json com 4 a 6 passos para um app seu que roda em localhost. Troque a URL, rode a captura com o seu arquivo e depois os dois blocos seguintes.
Pronto quando: Na pasta do ensaio, python3 -c "import json; print(len(json.load(open('steps.json'))['steps']))" → o número de passos do seu actions.json, e o render termina em OK: … · com áudio e saída: 0.
A explicação aparece depois da sua resposta.
Pergunta 1 de 3
Por que o viewport do actions.json é fixo?
Pergunta 2 de 3
O que define a duração final da demonstração?
Pergunta 3 de 3
O bloco espera a porta do app com um laço until curl. Por que não sleep 1?
Escolha uma alternativa. A explicação aparece depois da sua tentativa.