Curtas PS1 Low-poly
Tudo é código: o Claude escreve um script Blender por curta, renderiza na máquina do usuário, analisa os frames, corrige, e então constrói o som e as legendas. Nenhum gerador de vídeo.
Comprovado em examples/lift-rocket/ ("E se elevadores fossem tão rápidos quanto foguetes?", 34 s).
Leia esse exemplo antes de escrever uma nova curta: ele é o modelo.
Requisitos (verifique primeiro, instale o que estiver faltando)
- Blender 4.2+ (testado na 5.3). Encontre-o:
blenderno PATH, senãoC:\Program Files\Blender Foundation\<ver>\blender.exe//Applications/Blender.app/Contents/MacOS/Blender. - Python 3.10+ com
numpy pillow soundfile faster-whisper;ffmpegno PATH. - Personagens: Quaternius "Ultimate Modular Men" (CC0, grátis, quaternius.com). Descompacte em
assets/quaternius-modular-men/ou definaLOWPOLY_PACKpara a pastaIndividual Characters/Blends. - Uma voz TTS (ElevenLabs ou qualquer ferramenta que o usuário tenha). Opcional: o MCP
earshotpara verificar o som.
O pipeline (uma pasta por curta, copie examples/lift-rocket/ como início)
| # | Etapa | Arquivo / comando | Concluído quando |
|---|---|---|---|
| 1 | Roteiro | script.md | Pergunta-gancho + cadeia de consequências, cada uma pior e mais engraçada + números reais + bordão seco. 60-110 palavras para 30-40 s. Cada número verificado com uma fonte e escrito na lista de Fatos. |
| 2 | Voz | vo/takeA.mp3 | 2 takes, escolha aquele com tempos de palavra limpos. Voz criativa energética, não calma (calmo = feedback "chato"). |
| 3 | Tempos das palavras | python ../../scripts/words.py vo/takeA.mp3 | takeA_words.json não tem palavras de comprimento zero. |
| 4 | Lista de cenas | tabela em script.md | Uma cena por frase da voz, uma nova imagem a cada 1-3 s, cada cena vinculada a um tempo de palavra. |
| 5 | Cena | build.py (Blender) | blender -b -P build.py -- frames --still 30,120,... renderiza imagens fixas; veja cada imagem antes da renderização completa. |
| 6 | Visualização + notas | python ../../scripts/preview.py . render depois serve | O usuário não tem notas em aberto (veja o loop de Visualização). |
| 7 | Renderização | blender -b -P build.py -- frames | PNGs 360x640 EEVEE, f_0001.png... |
| 8 | Som | python build_mix.py | Efeitos sonoros colocados nos quadros da imagem, voz por cima, fundo lo-fi, ducking. Gera mix_raw.wav + cues.json. |
| 9 | Pós-processamento | python post.py | Dither PS1, efeitos, legendas com destaque na palavra, chamadas de estatísticas -> out/ em 1080x1920. |
| 10 | Multiplexação | veja abaixo | short.mp4, depois meça e verifique a sincronização. |
Regra de quadro em todos os lugares: 24 fps, quadro 1 = t 0, F = lambda t: int(round(t * 24)) + 1.
Multiplexação:
ffmpeg -y -framerate 24 -i out/f_%04d.png -i mix_raw.wav -c:v libx264 -crf 16 -pix_fmt yuv420p \
-af "loudnorm=I=-15:TP=-1.5:LRA=11,aresample=48000" -c:a aac -b:a 192k -shortest short.mp4
Troque a voz depois sem recortar a imagem:
python ../../scripts/words.py vo/takeB.mp3 depois python ../../scripts/align.py vo/takeA_words.json vo/takeB.mp3 --end 34 (--end = onde o fundo da voz para; saída é end + 2 s de duração. Sem ele: última palavra + 1,5 s, o que dá um arquivo mais curto que a imagem)
-> takeB_aligned.wav + takeB_aligned_words.json. Aponte o build_mix.py (variável de ambiente VO) e o post.py para eles.
Loop de visualização (antes da renderização completa)
Visualização rápida em baixa resolução no navegador; o usuário deixa notas na linha do tempo; o Claude corrige e re-renderiza
apenas as cenas cujo conteúdo mudou. Cenas = os marcadores de câmera em build.py.
| Faça | Comando (da pasta da curta) |
|---|---|
| Renderizar a visualização | python ../../scripts/preview.py . render (primeira execução: cada cena; depois: apenas cenas alteradas) |
| Abrir a página de notas | python ../../scripts/preview.py . serve -> http://127.0.0.1:8765 |
| Ler as notas | python ../../scripts/preview.py . notes (agrupadas por cena, com números de quadro) |
| Após corrigir | edite build.py, execute render novamente, depois python ../../scripts/preview.py . done 3 5 --reply "o que você mudou" |
| Forçar cenas | render --shots c_press,c_slam ou render --all |
build.pydeve aceitar--blend-only(construir a cena, salvar, não renderizar). Veja o exemplo.- Detecção de mudança: cada quadro de visualização recebe uma impressão digital (câmera ativa, transformação de cada objeto visível, pose de armadura, trocas de rosto, materiais, luzes, dados de malha). Uma cena re-renderiza apenas se um de seus quadros mudou. Cenas inalteradas mantêm seus quadros antigos.
- Configurações em
preview.json(opcional):scale50 (%),step2 (a cada 2 quadros),samples1,audio(padrãomix_raw.wav, senão a VO),final= os comandos completos do pipeline. O botão "Aprovar e renderizar final" da página executa-os;notesimprime APROVADO quando o usuário clicou. - Corrija as notas uma a uma, responda a cada uma, re-renderize e diga ao usuário para não atualizar nada: a página recarrega o vídeo sozinha. Notas repetidas do mesmo tipo se tornam uma regra neste arquivo.
- Medido no lift-rocket (RTX 3070 Ti laptop, 820 quadros): primeira visualização 76 s, uma cena alterada 13 s, nada alterado 12 s. A maior parte do tempo de renderização de um quadro é sobrecarga fixa, então menor resolução ajuda menos do que renderizar menos quadros.
Kit API (scripts/lowpoly_kit.py, importar dentro do Blender)
load_character(scn, "Suit")->(armature, meshes, actions). Personagens: Adventurer, Beach, Casual_2, Casual_Hoodie, Farmer, King, Punk, Spacesuit, Suit, Swat, Worker.- Ações (no lugar, 24): Death, HitRecieve, HitRecieve_2, Idle, Idle_Neutral, Interact, Kick_Left/Right, Punch_Left/Right, Roll, Run, Run_Back/Left/Right, Walk, Wave, Gun_Shoot, Idle_Gun*, Idle_Sword, Sword_Slash, Run_Shoot.
add_faces(scn, arm, head, tag, kinds, spec, drop)antes de mover a armadura. Tipos: neutral, blink, side, shock, grimace, squint, dazed, xeyes.dropdeleta os olhos/sobrancelhas 3D do pacote.show_faces(faces, [(frame, kind), ...]): cortes secos, cada rosto se mantém até o próximo.nla(arm, [(action, start, end, blend[, influence]), ...]): entradas posteriores em trilhas mais altas.drive_root(arm, [(frame, x, y, yaw, ease), ...], last): yaw 0 aponta para -Y (a câmera padrão).new_cam,aim,yaw_to,ps1_render_settings(scn, outdir).- Cenários:
scripts/set_lift_lobby.py(executar comBUILD_ONLY=True, retornabox,uv_world,mat). Novos cenários seguem o mesmo padrão: caixas com texturas pixeladas (interpolation = "Closest").
Regras de aparência
- Renderizar 360x640,
filter_size 0, AgX Punchy, sombreamento plano; pós: cor de 15 bits + dither Bayer, x3 nearest. - Rostos são decalques planos de 32x28 pixels trocados com cortes secos; nunca anime um rosto suavemente.
- Achatar sob força usando chave na escala da armadura (z para baixo, xy para cima). Batidas de comédia: rápido para dentro (2-3 quadros), segurar.
- Legendas: 1-3 palavras, sem serifa pesada, branca com contorno preto, palavra atual amarela, terço inferior (y 1300 de 1920).
- Chamadas de estatísticas: caixa vermelha arredondada, terço superior, aparecer em 4 quadros, uma por número real.
Regras de som
- Cada efeito sonoro é colocado para que seu pico caia no quadro da imagem (
at(name, frame, db, peak=...)). - A voz deve ganhar: VO normalizada para 0 dBFS, barramento SFX -11 dB, música -7 dB, mais sidechain duck enquanto a voz fala (SFX -5 dB, música -9 dB). A v1 da curta do elevador tinha SFX a -3 e música a 0: "Não consigo ouvir a narração".
- Corte a música no batida de parada grande, traga-a de volta depois. Silêncio vende a piada.
- Master: loudnorm -15 LUFS, pico verdadeiro -1.5 dBTP.
- Verifique: earshot
timelineno mp4 vscues.json(alvo: cada cue dentro de 100 ms),measurepara LUFS.
Bugs já encontrados (não repita)
- Malhas de pacotes são enviadas ocultas na renderização:
hide_render = False(o kit faz isso). - Malhas de porta já estão em seu local fechado: anime apenas o deslocamento, ou elas fecham com uma lacuna de 0,7 m.
- Um teto mais baixo que ~2,75 m esconde displays de parede da câmera dentro de um carro do tamanho de um elevador.
- Um personagem que não deve ser visto ainda: chave
location.z = -50até sua cena. - Tomada única do NLA passando para uma segura: defina o
blend_out = 0do clipe ou a pose aparece por um quadro. - Cenas reversas ficam pretas (teto não iluminado): adicione uma luz frontal e um pouco de emissão no teto. Renderize o ângulo reverso cedo.
- faster-whisper em mp3 pode quebrar com uma incompatibilidade PyAV:
words.pydecodifica com ffmpeg primeiro. - Uma frase começando em -0,04 s dá uma fatia vazia no alinhamento: fixe com
max(0.0, ...)(feito).
Custo (curta do elevador, RTX 3070 Ti laptop)
Renderizar 820 quadros em 92 s, pós 17 s, $0. Personagens, SFX, música: $0 (CC0 + sintetizados em código). Voz: ElevenLabs cerca de $0,10 por take, 2 takes por curta.
Antes de entregar
- Olhe para uma folha de contato dos quadros finais (a cada ~2 s), não apenas das imagens fixas.
- Meça o volume, verifique a sincronização, verifique as legendas em relação ao texto da VO (correções ortográficas vão em
post.pyFIX). - Diga ao usuário o que foi medido e o que não foi ouvido por um humano.