03 de novembro de 2026·7 min de leitura
Meu agente agora narra, trilha e revisa o vídeo do que construiu
O agentic-media-kit ganhou som: narração, trilha composta para o corte, legendas sincronizadas e cortes na batida. E um segundo agente, sem contexto, que revisa cada quadro antes de mim. O vídeo deste post foi feito assim.
Em setembro eu abri o agentic-media-kit: o agente de código grava o app que acabou de construir, monta um Reel e me devolve um link para eu assistir no celular. Funcionava. Mas todo vídeo saía mudo.
Mudo serve para validar uma tela. Para divulgar, não. Um tutorial sem voz explica pela metade, e um anúncio sem música parece inacabado. Então o kit ganhou som. O vídeo abaixo foi feito inteiro por ele: narração, trilha, legendas e cortes, sem editor de vídeo, sem música de banco e sem locutor.
Seis modos, e um que escolhe sozinho
Nem todo vídeo quer as mesmas coisas, então o áudio virou um modo:
| Modo | Voz | Música | Legenda da fala |
|---|---|---|---|
captions | – | – | – |
voice | ✓ | – | – |
voice+captions | ✓ | – | ✓ |
music+captions | – | ✓ | – |
music+voice | ✓ | ✓ | – |
music+voice+captions | ✓ | ✓ | ✓ |
O texto de cada etapa aparece sempre na tela, porque o feed toca sem som por padrão. A legenda da fala é uma faixa a mais, com a palavra dita destacada na hora em que é falada.
O mais útil, na prática, é o --mode auto. O agente passa o objetivo do vídeo e o kit escolhe o modo e explica o porquê. Um tutorial de onboarding ganha voz, música e legenda, porque voz explica melhor que manchete. Um anúncio de lançamento ganha música e texto. Um depoimento ganha voz e legenda.
node scripts/audio.mjs tour.json tour-voiced.json --name onboarding \
--mode auto --goal "tutorial do onboarding" --tts gemini
# mode: music+voice+captions (goal matches "tutorial")
O script gera a narração de cada etapa, compõe a trilha e ajusta a duração de cada cena para caber na fala. No fim, escreve um arquivo de props pronto para o Remotion renderizar. A voz pode vir do Gemini, do ElevenLabs, que entrega o tempo exato de cada palavra, ou do say do Mac, para rascunho offline.
Uma trilha composta para cada corte
Música foi a parte em que eu mais mudei de ideia. A primeira versão usava um acorde sintetizado no ffmpeg, que era livre de licença mas pobre. As APIs de música generativa cobram, têm licença que muda por plano, ou, no caso do Lyria, simplesmente não funcionavam na chave gratuita.
A saída veio de outro projeto open source, o teardown, que gera a trilha dos seus vídeos com um motor de síntese em Python puro: bateria, baixo, e-piano, reverb e masterização, só com a biblioteca padrão. Sem modelo, sem nada para compilar, sem custo por faixa. Portei esse motor (MIT, com o crédito no repositório) e escrevi quatro climas em cima dele: calm, tech, upbeat e warm.
Como a música é composta a partir da linha do tempo do vídeo, o kit sabe exatamente onde cai cada batida. Então os cortes são alinhados a ela: a duração de cada cena é arredondada para um número inteiro de batidas, e cada corte ganha um acento discreto. A trilha sai masterizada em −14 LUFS, o volume que Reels, TikTok e Shorts usam como referência, e abaixa sozinha quando a narração fala.
E, como não confio em "deve estar sincronizado", tem um verificador: o check-beats.mjs detecta os ataques da música no arquivo final e mede a distância de cada corte até a batida mais próxima. No tour de exemplo do kit, todos os cortes caíram a menos de 15 ms da batida.
Um revisor que nunca viu o processo
Essa foi a ideia de que mais gostei, e veio de olhar como outros projetos fazem.
O agente que montou o vídeo leu o briefing, escolheu o texto e acompanhou o render. Ele enxerga o que quis fazer, não o que está na tela. Então a última verificação vai para um segundo agente, novo, que recebe só três coisas: uma folha com um quadro de cada cena, o arquivo de props e as regras. Não recebe a conversa nem o histórico.
As regras são numéricas e rodam antes, no lint-video.mjs: cena com pelo menos 1,5 s, legenda com tempo de leitura suficiente, narração que cabe na cena, fonte mínima de 28 px, no máximo um zoom a cada duas cenas, Reel com até 90 s. O revisor cuida do que regra não pega: se a legenda está legível, se algo ficou cortado, se a história serve ao objetivo.
Na primeira rodada, ele reprovou o vídeo com razão. A legenda era uma caixa cinza que cobria um quarto da tela do celular e parecia um botão. Também apontou que a marca no texto não batia com a do app mostrado. Corrigi as duas coisas. Na segunda rodada, ele insistiu que a legenda sobre a tela parecia parte do app. Aí a regra que escrevi para o próprio processo entrou em ação: se o mesmo problema sobrevive a duas rodadas, quem decide é a pessoa, não o loop. Decidi manter a legenda sobre a tela, que é a convenção do TikTok e do Reels, e registrei a decisão na documentação para os próximos revisores.
O que quebrou no caminho
De novo, a parte mais útil foram os erros que não aparecem como erro.
- O áudio chegava 43 ms atrasado. O verificador de batida reprovou todos os cortes por quase o mesmo valor, +42 ms. A causa: o encoder AAC dos MP4s do Remotion coloca 2048 amostras de silêncio no início e não marca isso no arquivo. Ninguém percebe ouvindo, mas a medição pegou. O
webm-to-mp4.shagora corta esse atraso antes de publicar. - Normalizar silêncio quebra o encoder. Todo render do Remotion tem uma faixa de áudio, mesmo quando é muda, e o filtro de loudness do ffmpeg transforma silêncio digital em NaN. A normalização agora só roda quando o pico da faixa passa de −70 dB.
- A regra de ritmo media a coisa errada. A primeira versão do lint dividia o número de palavras pela duração da fala gerada. Isso mede a velocidade da voz sintética, que nenhum corte de palavra muda, e o lint mandava "use menos palavras" para sempre. A regra certa compara as palavras com o tempo que a cena tem.
- O contorno da legenda saiu vazado.
-webkit-text-strokecompaint-orderdeveria desenhar o contorno por trás das letras. No Chrome headless, ele desenhou por cima, e as letras ficaram ocas. A solução foi montar o contorno com um anel de sombras. - O modelo de voz lia a instrução em voz alta. O TTS do Gemini 3.x falou o prefixo de estilo junto com o texto: "Experimente hoje." passou de 1,9 s para 4,9 s. O script agora manda a direção de tom num formato que esses modelos não leem.
- A cota gratuita mistura vozes. Quando um modelo do Gemini estoura a cota, o script passa para o próximo. O vídeo sai, mas falas diferentes podem vir de modelos diferentes, com timbres levemente distintos. Para uma peça final, vale uma chave paga ou o ElevenLabs.
Experimente
Continua MIT, e roda sem nenhuma chave: a voz offline do say mais a trilha procedural já geram um vídeo completo para você ouvir o resultado.
git clone https://github.com/rwspatin/agentic-media-kit && cd agentic-media-kit
npm install
node scripts/audio.mjs apps/studio/examples/tour-audio.json /tmp/tour.json \
--name demo --mode music+voice+captions --tts say
cd apps/studio && npx remotion render Tour-Reel out/tour.mp4 --props=/tmp/tour.json
O repositório tem o guia de áudio, as regras de revisão e o código deste vídeo de lançamento: https://github.com/rwspatin/agentic-media-kit.
Ninguém ouve um vídeo pelo agente. Se você testar, me conte o que soou estranho. É o tipo de feedback que nenhuma medição de LUFS substitui.
- Renan
Leitura relacionada
Meu agente agora grava o vídeo do que construiu
Programo pelo celular: o T3 Code comanda os agentes, eles rodam num devbox na Railway e eu recebo um "Done". Faltava ver a interface. O agentic-media-kit fecha esse ciclo: o agente grava o app, monta um Reel e me devolve um link que eu abro no celular.
O processo que está no código, não o que está na cabeça
Criei uma skill que lê o código de uma aplicação e reconstrói seus processos de negócio em BPMN validado. No caminho, abri um PR no Mermaid, descobri que outra pessoa estava mais adiantada e escolhi colaborar em vez de competir.
O bucket é o banco: um app de 430 linhas que eu não precisei manter
O media-viewer existe para eu assistir e baixar no celular os vídeos que meus agentes geram. Ele tem um arquivo, cinco dependências, nenhum banco e nenhum JavaScript no cliente. Este post explica cada decisão e o que eu abri mão para tomá-la.