make-ai-short — curtas generativos, do início ao fim
Os pixels vêm de um modelo de vídeo; o Remotion apenas compõe clipes + legendas. Comprovado no
homem-azul #1 "The Door". Irmão do /make-short (100% TSX) — mesma disciplina, mais três
portões que o TSX nunca precisou: custo informado antes de gerar, personagem fixado a partir de
character.json, loop preso via condicionamento do frame final.
Execute tudo a partir da raiz do repositório. Precisa de FAL_KEY (clipes + imagens) e ELEVENLABS_API_KEY
(voz) no .env.
As três regras de ferro
- NUNCA gere um personagem a partir de texto duas vezes.
ai-shorts/<char>/character.json+character.pngé a referência fixada. Qualquer imagem que precise do personagem égen_image.py --ref character.png; cada clipe é imagem-para-vídeo a partir de um quadro que já o contém. Se ainda não existir uma ficha do personagem, criar UMA (e obter a aprovação do usuário) é uma etapa própria antes de qualquer outra. - INFORME O CUSTO ANTES DE GASTÁ-LO. Derivado, não cotado: Seedance 1080p sem áudio =
h×w×24×dur/1024tokens × $1,2/1M ≈ $0,0583/s (API de precificação da fal validada em 2026-07-14 — veja ai-shorts/IDEAS.md para a tabela completa + as armadilhas do Wan/Veo-Lite). Sempregenerate_audio:false— a voz é ElevenLabs, o som é nosso pipeline de SFX. - Loop por RESTRIÇÃO, não por sorte. O verdadeiro quadro 0 do primeiro clipe (extraído do clipe, não da
imagem de prompt) é o alvo do loop. O último clipe o passa como
end_image_url(Seedance/Kling), e a composição se acomoda nessa imagem exata ao longo dos últimos ~10 quadros (padrãoLoopSettleemremotion/src/shots/ai-1/Ai1Door.tsx).
Contrato de artefatos
ai-shorts/<name>/
script.md · beats.json — mesmo contrato do /make-short (vo[] com tempos reais de palavras)
character.json — A REFERÊNCIA FIXADA (+ bloco video_model = o modelo escolhido)
shots/NN-slug.png — quadro INICIAL de cada clipe (gen_image.py --ref character.png [--ref scene])
shots/NN-slug.json — sidecar do gen_clip (modelo, payload, id da requisição)
shots/upload-urls.json — urls de armazenamento da fal (uploads são reutilizáveis entre re-rolls)
voice/ · sfx-plan.json · output/ [voice/output gitignored]
remotion/src/shots/ai-N/ — composição + vo.gen.ts
media/projects/<name>/ — AS cópias dos clipes para staticFile() (commitados — pixels não
são reproduzíveis; cópias de trabalho shots/*.mp4 são gitignored)
Ordem das operações (voz antes dos pixels)
- Roteiro — script.md + beats.json. Gancho composto no quadro 0; a tese DEVE desejar o loop (homem-azul #1: "a chegada é um mito" → final = caminhando em direção à mesma porta). ~80-100 palavras, janelas de 2,5-2,7 pps. Nunca ter um CTA de engajamento no final — termine no clímax, o loop é o final.
- VOZ PRIMEIRO —
python tools/gen_voice.py --beats ... --emit-ts remotion/src/shots/ai-N/vo.gen.ts(voz e modelo do.env, ou um bloco"voice"no beats.json quando uma série quiser um narrador como GeorgeJBFqnCBsd6RMkjVDRZzbcom tags eleven_v3; aja nas flags do LINE CHECK como no make-short Stage 4 antes de qualquer clipe ser gerado). Os tempos reais das palavras decidem as janelas dos clipes; o Seedance aceita durações inteiras de 4 a 12s, portanto arredonde os comprimentos dos clipes PARA CIMA a partir das janelas. Diálogo (dois ou mais personagens): o"cast"no beats.json mapeia cada falante para seucharacter.json(cujo bloco"voice"é usado), cada linha de voz nomeia seu falante em"who", e--pack 0.3coloca as linhas ponta a ponta a partir de seus comprimentos reais, em vez de comprimi-las em janelas estimadas. Uma voz por personagem vive em seu character.json, em nenhum outro lugar. Bocas no diálogo: o modelo de vídeo não sabe quem está falando; ele faz ouvintes falarem, congela quem fala e estica uma risada para a próxima frase. Depois dos clipes, executepython tools/mouth_sync.py ai-shorts/<name>(o"shots"no beats.json dá a cada clipe seu início e comprimento globais; cada bloco"mouth"no character.json dá lado, raio, zona, estilo ekeep_above). Ele apaga as bocas do modelo e desenha a boca de cada personagem a partir da intensidade da trilha de voz. Faça QA da mesma forma: ambos os rostos a cada meio segundo contra quem está falando. Uma mudança de ação dentro de uma linha (rir, depois falar) são dois clipes, o segundo iniciado a partir do quadro do primeiro no corte. Em seguida, escaneie cada clipe com boca quadro a quadro: um pico acima de quatro vezes a mediana do clipe é um corte, um gesto rápido ou uma morfose de dois quadros com características fantasmas; liste os quadros de morfose no"hold"desse clipe e execute novamente, para que o salto seja reproduzido como um corte limpo. - Quadros iniciais — um por clipe,
python tools/gen_image.py --model pro --aspect 9:16 --ref character.png(+ o quadro estático da cena do clipe 1 como uma 2ª referência quando um local/prop deve corresponder entre os clipes). Faça QA de CADA PNG antes de qualquer chamada de vídeo — é aqui que corrigir derivações é barato.- Observe mãos, detalhes de roupa, proporções — nomeie-os explicitamente no prompt nos re-rolls.
- A geometria deve ser caminhável (lição do clipe 6, custou 2 re-rolls): com um pino de quadro final, o quadro inicial deve permitir que o movimento ALCANCE o estado final caminhando para frente — se o quadro final do personagem estiver MAIS PERTO da câmera do que o inicial, o modelo gera um clone. Inicie-o maior/mais próximo do que o estado final para que o movimento para frente o encolha.
- Clipes — informe o custo total derivado, depois
python tools/gen_clip.pypor clipe em paralelo (--set image_url=<url de armazenamento fal> --set 'duration="N"' --set resolution=1080p --set generate_audio=false, clipe final--set end_image_url=<url do frame0>). Faça upload das referências uma vez viabakeoff_clip.upload_ref; mantenha as urls em shots/upload-urls.json. O formulário fal acima é o padrão deste checkout (CLIP_PROVIDER=fal,FAL_VIDEO_MODELno .env). No kie.ai (--provider kie): os nomes dos campos seguem o modelo kie, não o fal. Parabytedance/seedance-2:--aspect none --set first_frame_url=@ai-shorts/<name>/shots/NN-slug.png --set duration=N --set resolution=1080p --set generate_audio=false, clipe final--set last_frame_url=@<frame0.png>.@pathfaz upload do arquivo local (o kie o mantém por 24h, então a reutilização de upload-urls.json só é válida dentro de um dia). Custo: kie.ai/pricing, não a tabela fal; a execução imprime os créditos usados.bakeoff_clip.pyainda é apenas para fal. Forneça a cada clipe: frase exata do personagem + "EXATAMENTE UM personagem na cena durante todo o clipe" (clones são a falha #1) + movimento de câmera + "sem morfose" + linha de estilo. - QA dos clipes, quadro a quadro — tira início/meio/fim por clipe + uma comparação lado a lado do último quadro do clipe final vs o alvo do loop. Re-role as falhas (informe o custo do re-roll); um re-roll de 5s 1080p é ~$0,29 — barato perto de enviar um clone.
- Composição — Sequências de
OffthreadVideoem UMA única linha do tempo, sobreposição de crossfade de ~8 quadros (os clipes são ~1s mais longos que suas janelas — essa é a folga),Captions+ProgressBarna raiz,LoopSettlecom dissolve no final. Copie os clipes finais paramedia/projects/<name>/.npm run gen, frames.mjs nos limites + heróis, LEIA cada PNG, depois render-all --scale=1 e multiplexe voice.wav. - SFX — fluxo do /suggest-sfx. Calibração de VO com lacunas (homem-azul #1): as pistas nas lacunas da VO não recebem mascaramento nem duck — elas precisam de ~7-8 dB MENOS do que a tabela de narração densa (whooshes em lacunas -9..-13, não -3..-5). Verifique com volumedetect RMS contra uma referência de média da fala; a camada herói pode beijar o nível da voz uma vez. A mixagem de audição aguarda o ouvido do usuário (portão rígido).
- Salvar de volta — catalogar used_in, timings reais do beats.json (gen_voice os escreve), sidecars commitados, ledger de custos no relatório final.
Pronto =
personagem consistente em todos os quadros de todos os clipes (QA quadro a quadro) · loop verificado
(último quadro vs quadro 0 lado a lado) · legendas com tempos reais das palavras · custos informados antes de
cada gasto e totalizados após · audição de SFX renderizada, aguardando o ouvido do usuário · nenhum CTA de
saída. · tools/check_text.py limpo no texto falado e na tela · cópia para celular feita com
tools/export_phone.py · uma entrada datada em LESSONS.md (e o skill atualizado, em termos gerais,
se a lição mudar a forma como funciona).