/pitch-deck-video
Gere um vídeo de demonstração de produto narrado a partir de um aplicativo web local em execução. A edição é derivada, nunca cronometrada manualmente, então quando o frontend muda, você regrava e o corte acompanha — sem números de quadro para corrigir.
Construído para o caso que realmente acontece: a interface ainda está em movimento enquanto você precisa do vídeo.
Uso
/pitch-deck-video # filma o aplicativo no diretório atual
/pitch-deck-video <caminho-para-o-app> # filma um aplicativo específico
/pitch-deck-video --rebuild # regrava e recorta um projeto de vídeo existente
/pitch-deck-video --script # apenas a narração mudou; reutiliza a última gravação
/pitch-deck-video --render # apenas cards/legendas mudaram; renderiza novamente
A estrutura
Quatro arquivos gerados controlam tudo. Apenas o primeiro é escrito por um humano.
| arquivo | contém | escrito por |
|---|---|---|
shots.json | o que é dito, a voz, a música, o tema e o que o navegador faz | você |
src/vo-durations.json | quanto tempo cada linha realmente leva | scripts/tts.mjs |
capture/marks.json | quando cada cena começou na gravação e onde olhar | scripts/record.mjs |
src/frames.json | quantos quadros vieram da gravação | scripts/frames.mjs |
src/timeline.ts deriva toda a edição a partir desses arquivos. As durações das cenas vêm do áudio da narração medido. A filmagem é esticada para se ajustar a isso. Os movimentos de câmera vêm das caixas delimitadoras dos elementos medidas ao vivo durante a gravação, então um botão movido move o plano, em vez de quebrá-lo.
Fluxo de trabalho
1 · Estrutura inicial
Copie template/ desta skill para <nome-do-app>-video/ ao lado do app, depois execute npm install. O template vem com JSON de semente para que ele faça verificação de tipo e renderize antes da primeira gravação.
Nunca edite arquivos em ~/.claude/skills/pitch-deck-video/template/ para um único projeto — esse é o template compartilhado. Edite a cópia.
2 · Primeiro, verifique se há um login salvo
Se o app tiver autenticação, procure por um estado de armazenamento do Playwright que o repositório já mantém para seus próprios testes e2e (.auth/*.json, storageState, playwright/.auth/). Aponte app.storageState para ele e a gravação começa logada sem mostrar o fluxo de login na câmera. Só crie uma cena de login se não houver tal arquivo — e, nesse caso, use {"type": {"sel": "…", "env": "DEMO_PASSWORD"}} para que a senha nunca seja escrita em shots.json.
3 · Conheça o app antes de escrever uma palavra
Inicie o servidor de desenvolvimento do app e faça captura de tela de cada rota em 1920×1080, depois leia as imagens. Não escreva narração a partir do código-fonte. Você está escrevendo sobre o que um juiz verá, e apenas as capturas de tela lhe dirão isso.
// script único; o Playwright já vem com o template
for (const r of rotas) {
await p.goto(base + r, { waitUntil: 'networkidle' });
await p.waitForTimeout(700);
await p.screenshot({ path: `/tmp/shots/${r}.png` });
console.log(r, await p.evaluate(() => document.documentElement.scrollHeight));
}
Anote as alturas de rolagem: qualquer coisa acima de 1080 precisa de uma ação scroll ou nunca aparecerá na câmera.
4 · Escreva shots.json
Comece a partir de template/shots.example.json. Consulte references/shots-reference.md para cada campo e ação.
Extraia os theme.colors dos próprios design tokens do app para que os cards e a filmagem pareçam uma peça única. Preencha cards.arch.nodes com a arquitetura real — quais caixas são chamadas de modelo e quais são código determinístico. Esse detalhe é o que separa uma demonstração de um invólucro, e os juízes procuram por isso.
Regras de narração que importam:
- 12 a 16 cenas, 1:30 a 1:50 no total. Dentro de um limite de 2:00, deixe 10 segundos de margem.
- Uma ideia por cena. A cena termina quando a ideia acaba.
- Diga o número. "Seis pessoas pontuaram, Priya vence" é melhor que "seleciona alguém".
- Nunca narre a interface. Não "aqui clicamos em Aprovar" — diga o que isso significa.
- Abra com o problema nas palavras do usuário, antes que o nome do produto exista.
- Feche com uma frase que você quer que fique na tela.
5 · Construir
APP_DIR=/caminho/para/o/app npm run all
Narração → gravação → quadros → renderização. Inicia o servidor de desenvolvimento se nada estiver servindo app.url, lendo o próprio script dev do app para descobrir como aquele framework usa uma porta. Defina app.devCommand se precisar de algo específico. Típica reconstrução completa: ~2 minutos.
shots.json é validado primeiro, então um ID duplicado ou uma ação desconhecida falha em dois segundos, em vez de produzir um vídeo confuso dois minutos depois.
6 · Controle de qualidade visualizando — isso não é opcional
npm run qa # um quadro por cena, empilhados em folhas de contato
npm run qa 3 # três por cena, quando você quiser verificar uma transição
Depois leia as folhas como imagens. Avançar pelo vídeo é lento e você perderá coisas; uma folha de contato leva segundos e captura exatamente as falhas que importam.
Verifique: capture/marks.json → warnings está vazio (um seletor ausente é um aviso, não uma falha — você ainda obtém um vídeo e uma lista do que quebrou). Depois: cada legenda cai em um quadro que a suporta? Algum quadro em branco ou branco? O texto pequeno é legível ou precisa de um focus?
references/troubleshooting.md tem todas as falhas que este pipeline já encontrou e a correção para cada uma. Leia-o antes de depurar do zero.
Re-corte após mudanças na interface
Este é o ponto de tudo isso.
APP_DIR=/caminho/para/o/app npm run all
A maioria das mudanças não precisa de mais nada — elementos movidos, páginas com novo estilo, novo texto. O que ainda precisa de julgamento:
- Uma página foi deletada ou renomeada → redirecione ou substitua aquela cena.
- Uma funcionalidade foi reescrita → a narração para aquela cena agora é uma afirmação sobre algo que não existe mais. Reescreva a linha.
- Um seletor realmente desapareceu → verifique a lista de avisos e corrija-o lá.
A narração é armazenada em cache por linha, então apenas as linhas editadas são re-sintetizadas.
Voz
A voz é a maior diferença de qualidade no vídeo final, e a alternativa padrão é realmente ruim. Diga isso claramente antes da primeira construção, não depois.
tts.provider: "minimax"— bom. Pergunte ao usuário onde está a chave MiniMax dele e apontetts.envFilepara esse arquivo. Os IDs de voz vêm da lista de vozes do sistema MiniMax; a tag de idioma em uma voz é uma dica, não uma restrição (uma voz com tag ucraniana lê inglês bem comlanguageBoost: "English").tts.provider: "say"— sintetizador embutido do macOS, e geralmente apenas as vozes legadas estão instaladas. É robótico, pronuncia nomes de produtos errado e é óbvio para qualquer um que assistir. Utilizável para verificar o tempo, fraco para qualquer coisa que um juiz ou cliente verá.- Uma gravação humana supera ambos. Coloque arquivos
<id>.wavempublic/vo/e executenpm run script; as durações são medidas novamente e todo o vídeo se reajusta sem nenhuma outra edição.
Se não houver chave, construa o vídeo de qualquer forma — mas avise o usuário de antemão que a voz é o ponto fraco e que ou uma chave ou dez minutos com um microfone resolvem isso. Não envie silenciosamente uma narração com say como se estivesse pronta.
O MiniMax usa say como fallback por linha se a API estiver inacessível, então uma execução nunca falha completamente.
Nunca copie uma chave de API para dentro do projeto. scripts/tts.mjs lê MINIMAX_API_KEY / MINIMAX_GROUP_ID do ambiente e, caso contrário, os extrai de qualquer arquivo que tts.envFile nomear. Pergunte ao usuário onde a chave dele está e aponte envFile para ela.
Uma gravação humana supera qualquer sintetizador: coloque arquivos <id>.wav em public/vo/ e execute npm run script — ele as mede novamente e reajusta todo o vídeo.
Música
scripts/music.mjs baixa a faixa nomeada em music.url com yt-dlp e corta uma base na duração atual do vídeo. Avise o usuário para verificar a licença da faixa antes de publicar — faixas de biblioteca geralmente são gratuitas com atribuição na descrição e pagas sem ela. O título e a URL são salvos em public/music/SOURCE.txt para que o crédito seja fácil de copiar.
O que não fazer
- Não ajuste temporizações manualmente. Se uma cena parecer apressada, a narração é longa demais para o plano — corrija
shots.json, não a linha do tempo. - Não coloque
<Screen />dentro de um<Sequence>. Ele lê tempo absoluto por design;useCurrentFrame()dentro de uma Sequence retorna quadros locais à Sequence. Isso já quebrou o vídeo antes e o sintoma (filmagem sutilmente fora de sincronia com a voz) é difícil de detectar. - Não deixe o tempo de configuração aparecer na câmera. Navegação, carregamento de dados e descarte de um banner acontecem dentro da janela de captura da cena e são esticados sob a narração. Coloque
{"cut": true}após os passos de configuração; a filmagem então começa a partir daí. - Não filme nada que seja fictício. Se um botão retorna "conecte o backend", deixe-o de fora e diga ao usuário qual cena adicionar quando ele estiver conectado.
- Não envie sem verificar a lista de avisos.
- Não narre um número que você vê variar entre execuções, e nunca descreva uma tela como cheia quando a versão desta conta está vazia. Um juiz que detecta uma afirmação falsa desconta o resto.