Vídeo de produto em Remotion
Uma demonstração de produto sem rosto (formato 3 em short-video-formats) construída como código. A voz define o ritmo: cada linha do roteiro é falada, as tomadas são colocadas uma após a outra, e as cenas e legendas do vídeo seguem esses marcadores de tempo. A UI é reconstruída em React usando os estilos reais do produto, então fica nítida em qualquer zoom e é re-renderizada quando o produto ou o roteiro mudam. Tudo, exceto o MP4 renderizado, fica no git, então qualquer versão antiga está a um checkout e uma renderização de distância.
Entradas: Node 22, ffmpeg, um TTS (edge-tts para rascunhos; uma voz licenciada antes de publicar), os tokens de CSS
e marcação do produto (a página de demonstração do produto é muitas vezes a melhor fonte), e o roteiro.
Fluxo de trabalho
1. Configuração
npx create-video@latest --blank reel && cd reel && npm i @remotion/google-fonts
Mantenha um theme.ts com o quadro (1080×1920, 30 fps), as faixas de layout e os tokens de cores do produto copiados
do seu CSS. Nossas faixas: faixa de título centralizada em y=118, a faixa branca da UI de y=220 por 1250 px (65% da
altura, largura total), legendas centralizadas em y=1640.
2. Escreva o roteiro como dados
script.json, uma entrada por linha de legenda; *estrelas* marcam as palavras destacadas:
{ "voice": { "name": "en-US-AndrewMultilingualNeural", "rate": "+10%" }, "gap": 0.16,
"lines": [
{ "scene": "wall", "text": "I stopped *prompting* Claude Code." },
{ "scene": "wall", "text": "I *hired* it.", "pause": 0.5 },
{ "scene": "end", "text": "Hire your first *AI employee*.", "say": "Crewmark. Hire your first AI employee." }
],
"post": { "tiktok": { "caption": "…", "hashtags": [] }, "instagram": { "caption": "…", "hashtags": [] } } }
say é para quando a voz difere da legenda; pause mantém uma cena por mais tempo após uma linha.
3. Voz → linha do tempo
Um pequeno script Node fala cada linha em seu próprio WAV, corta o silêncio, as coloca uma após a outra com gap/pause
entre elas, as mescla em public/voice.wav (adelay + amix, depois loudnorm=I=-14 para volume social),
e escreve public/timeline.json com os from/to de cada linha em quadros:
{ "durationInFrames": 959, "lines": [ { "scene": "wall", "text": "I stopped *prompting* Claude Code.", "from": 12, "to": 51 } ] }
Armazene em cache cada gravação pelo seu texto e voz para que editar uma linha não regrave o resto. A durationInFrames da composição
vem deste arquivo.
4. Cenas seguem a linha do tempo
Agrupe linhas consecutivas com a mesma scene em uma <Sequence>; dê a cada cena seus deslocamentos de linha ("beats") para
que animações atinjam as palavras. Legendas são um componente que lê a linha atual.
A peça chave é uma câmera sobre a faixa da UI: disponha a UI em pixels CSS reais, depois aplique escala e panorâmica.
export function Cam({ zoom, x = 0, y = 0, children }: { zoom: number; x?: number; y?: number; children: React.ReactNode }) {
return (
<div style={{ position: "absolute", inset: 0, overflow: "hidden", background: "#fff" }}>
<div style={{ position: "absolute", width: 1080 / zoom + x, height: STAGE_H / zoom + y,
transform: `scale(${zoom}) translate(${-x}px, ${-y}px)`, transformOrigin: "0 0" }}>{children}</div>
</div>
);
}
Com zoom = 1080 / 520, um layout de 520 px de largura (dois cartões de 248 px) preenche a largura exatamente. Anime zoom, x e
y com interpolação suave entre os beats para aproximar o botão sendo clicado.
Ideias de cena que funcionam bem: um mercado de cartões, um formulário se preenchendo (texto digitado com cursor), um quadro onde um cartão se move entre colunas, uma thread de atividade, um gráfico se desenhando (rotulado "Dados de demonstração"), um chat onde o agente pergunta e você aprova, um cartão final no estilo do hero da página.
5. Renderize e dê uma olhada
node voice.mjs && npx tsc --noEmit
npx remotion still src/index.ts Reel /tmp/f0.png --frame=0 # a capa
npx remotion render src/index.ts Reel out/reel.mp4
ffmpeg -v error -y -i out/reel.mp4 -vf fps=1,scale=200:-1,tile=8x5 -frames:v 1 out/sheet.jpg
Olhe para a folha de contato e para um still de cada cena: a faixa da UI inteira, nada cortado na parte inferior da faixa, legendas dentro da faixa inferior, a cena certa sob cada linha, quadro 0 não em branco.
6. Versionamento
Commite o código-fonte, o roteiro e voice.wav/timeline.json; mantenha out/ e downloads em .gitignore. Para recuperar
uma versão antiga, faça checkout do commit e renderize novamente.
Lista de verificação
- Cores, fontes e texto copiados do CSS e texto reais do produto
- Faixa da UI ≥ 50% da altura, largura total; faixas apenas acima e abaixo
- Quadro 0 renderizado e verificado como capa
- Cada cena mostra uma funcionalidade que o produto realmente tem
- Números de demonstração rotulados
- Folha de contato revisada após cada renderização
- Código-fonte e voz commitados; MP4 não
Armadilhas que enfrentamos
- Fade-in na primeira cena deixou o quadro 0, a capa, em branco. Pule a animação de entrada para a primeira cena.
- Conteúdo cortado pela câmera. Um gráfico dimensionado para um nível de zoom ultrapassou a parte inferior da faixa no zoom da cena. Verifique um still no final de cada movimento de câmera, não apenas no início.
- Metades inferiores esparsas. Com zoom baixo, cenas curtas deixaram a parte inferior da faixa branca vazia. Aumente mais o zoom ou ancore algo na parte inferior (um campo de entrada de mensagem, um rodapé) para que a faixa pareça uma tela real.
- Um roteiro de revisão que precisava de um .srt. A ferramenta de revisão do nosso pipeline ffmpeg antigo lia timings de legenda
que o Remotion não escreve. Uma folha de contato com
fps=1,tilefaz o mesmo trabalho. transformduplicado em um único objeto de estilo (uma animação de pressionar mais uma animação de entrada) falha na verificação de tipos. Envolva o elemento e coloque uma transform em cada camada.- Edge TTS para a versão final. É gratuito e bom para rascunhos, mas robótico perto de qualquer coisa com aparência humana, e não vem com licença comercial. Troque a função TTS antes de publicar em escala.
Habilidades relacionadas
- short-video-formats: regras de layout e qual formato construir.
- social-publish: o texto da postagem mantido em
script.json. - video-retro: o que mudar na próxima renderização.