Vídeo story block-toon (estilo "Busted by the Shoeshine Man")
Use isso quando o usuário quiser um vídeo vertical (720x1280 ou 1080x1920, 30 fps) de história em primeira pessoa, com 30-130 s, narrado, com personagens 3D em blocos arredondados, movimento contínuo suave e legendas coloridas impactantes. Tudo é código: cena three.js em Chromium headless, uma função frame(t) determinística, capturas de tela canalizadas para o ffmpeg.
A prioridade do usuário é fidelidade no design dos personagens e suavidade da animação. Não pule as verificações de personagens na etapa 5.
1. Como o vídeo de referência se parece (medido)
- 720x1280, 30 fps, ~122 s, AAC estéreo cerca de -14 LUFS com um fundo contínuo e suave sob a narração.
- ~47 cortes secos (cortes de quadro único, nunca crossfades), duração média do take ~2,6 s (variação de 1,2-5 s). Dentro de um take, a câmera está SEMPRE em movimento: push-in suave, pull-out, drift de grua/ângulo alto, push de ângulo baixo, whip-tilt até um teto ou janela. Menos de 1% dos quadros são estáticos.
- Legendas no terço inferior (topo cerca de 64-68% da altura), 2-3 palavras por grupo em letras minúsculas, fonte geométrica arredondada e negrito (Poppins ExtraBold), base branca com palavras-chave coloridas (dourado #f5c32c, ciano #48c9f0, coral #ff5b4d, rosa #ff7ac0, verde #2ecc8f, laranja #ffa21f), contorno preto grosso, pequeno pop de escala em cada grupo.
- Ambiente: interior creme/tan quente (parede #dcbf96, chão #c7aa7d, pedra #d9c39b, lambril #b99a6f, céu #c7dcec) com acentos marrom #570d0e, marinho #1c2a49, dourado #e0990d, teal #1f8f8c, vermelho #a3160f.
- Iluminação: suave, brilhante, ligeiramente quente; sombreamento gradiente suave e sombras suaves projetadas. Sem bandas cel duras.
2. Bíblia de design de personagens (siga exatamente)
Silhueta e proporções
- Cada parte do corpo é uma caixa arredondada. O raio do bevel é cerca de 15-18% da menor dimensão da peça. Nunca use esferas ou cápsulas para partes do corpo.
- Cabeça: um cuboide ligeiramente mais largo que alto (0,44 x 0,41 x 0,39 unidades), cerca de 1/4-1/3 da altura da figura em closes, de modo que a figura tem ~4 cabeças de altura em planos abertos. Bloco de pescoço curto. Torso 0,46 x 0,56 x 0,26. Braços 0,27 + 0,26 (superior + antebraço). Pernas 0,30 + 0,27. Altura total cerca de 1,7 unidades.
- Mãos são blocos de mitene rechonchudos: um bloco de palma com quatro dedos curtos de dois segmentos e um polegar separado. Elas podem se curvar de aberta para punho e se abrir. As mãos nunca devem parecer aranhentas.
- Faixa de punho branca em cada pulso. Sapatos pretos, um bloco plano com ponta arredondada.
- Sem quebras de junta visíveis. Onde duas caixas arredondadas se encontram em uma junta dobrada (quadril, joelho, ombro, cotovelo, punho, pescoço/torso, quadril/torso), os beveis abrem um espaço ou entalhe que parece uma quebra. Esconda cada junta sob uma camada de pano na mesma cor da roupa: uma pequena esfera (
ball()) centrada no pivô da junta, do tamanho da meia largura do membro + ~0,002 e esticada ao longo do membro (sc[1,1,2-1,4,1]); um bloco de pelve unindo os dois quadris; um bloco de colar sobre a costura pescoço/torso; uma peça de punho de cor de pele sob o punho. Não superdimensione as bolas (meia largura do membro + 0,015 criou joelhos e cotovelos irregulares). Verifique com um teste de estresse de poses flexionadas (braço levantado, caminhada no meio do passo, perna levantada), com zoom.
Rosto (pintado na frente da cabeça, redesenhado a cada quadro em uma textura de canvas)
- Olhos grandes e redondos: esclera branca, íris verde ou marrom, pupila preta, brilho branco. Linha grossa da pálpebra superior preta. Os olhos podem olhar ao redor (lookX/lookY), piscar e apertar.
- Sobrancelhas grossas e cônicas que inclinam (zangado/preocupado) e levantam. Óculos de aro preto são muito maiores que os olhos (raio do aro cerca de 2x o raio do olho). Os óculos devem ser uma moldura conectada: os dois aros são unidos por um arco de ponte curvo sobre o nariz e cada aro tem uma linha de haste saindo para a borda do rosto. Dois aros flutuantes parecem quebrados (um usuário sinalizou isso na primeira folha de elenco).
- Sem nariz. A boca é uma pequena linha/curva, ou uma boca aberta preenchida com língua rosa ao falar. Valor de sorriso -1..1.
- Blush suave opcional.
Cabelo, chapéus, acessórios (blocos empilhados)
- Herói: topete marrom com ponta pontiaguda, peças laterais e um bloco traseiro. Outros: curto, ou longo (cortinas laterais + bloco traseiro). Franja preta é um bloco angulado fino.
- Boné de policial: bloco de coroa marinho + viseira preta + distintivo dourado. Boina: bloco marrom + aba curta + botão claro.
- Homem mais velho: bigode de bloco branco + remendo branco na têmpora.
- Roupas são coloridas e simples: terno (jaqueta marrom, camisa branca, gravata escura, lapelas, cordão dourado V + crachá branco com faixa azul), uniforme (marinho, distintivo dourado, cinto preto + fivela dourada), avental (camisa branca, avental marrom com botões), além de variantes cinza/verde/tan/marinho/teal para figurantes.
- Tons de pele: pêssego #efaa71, tan #d89a62, marrom #8a5230, escuro #5a3320, claro #f4c49a. Cabelo marrom #59321a, preto #14110f.
Renderização
- Contorno de tinta escura e fina (#1c0f06) em cada parte via um shader de casco invertido com largura constante na tela. A largura é uma fração da distância da câmera:
w = px / ((H/2) / tan(fov/2))ondepxé a espessura de linha desejada em pixels da tela (cerca de 0,003 para takes de vídeo 720x1280, cerca de 1,5-2 px). Para folhas de vários personagens distantes a 1600x900 useK.setOutlineWidth(.0009), para bustos cerca de .0016; o padrão é muito grosso em uma câmera distante. Rostos e pequenas peças de acabamento pulam o contorno. - Material Lambert, luz hemisférica (cerca de 2,9) + um sol de sombra suave (cerca de 1,5). Sombras PCF suaves.
- Multidão de fundo usa
lod: 1(sem dedos, menos blocos de cabelo). Nunca crie um design único por figurante; varie tom de pele, cabelo, cor da roupa e penteado.
Regra de variedade: todo personagem nomeado deve ser reconhecível apenas pela silhueta (chapéu, forma do bloco de cabelo, óculos, bigode, tom de pele, cor da roupa). Use as mesmas proporções para todos.
Folhas de referência (faça isso quando o usuário pedir um design de personagem ou ambiente, antes de qualquer vídeo)
Construa um designsheet.html e um envsheet.html ao lado de scene.html (mesmo kit, mesmo render.cjs, execute node render.cjs designsheet.html out 0,1,2 --w 1600 --h 900). frame(t) escolhe um modo com Math.round(t); os rótulos são uma sobreposição DOM (Poppins) posicionada projetando pontos 3D com new THREE.Vector3(x,y,z).project(camera); o título da página e as amostras também são DOM.
- Arquivo de elenco
cast.js: uma tabelaDEF(nome, função, 4 amostras [pele, cabelo, roupa, acento]), uma tabelaBUILDde um construtor por personagem (makeChar+ algunsbox()extras de acessórios),makeOne(name),makeCast(), epose(name, C)que redefine o rosto e a pose a cada quadro e aplica uma pose limpa de folha de modelo. Torne cada personagem único pela silhueta: diferentesscale/headScale, penteado, chapéu, óculos, bloco de bigode, tom de pele e uma cor de acento. - Folha 0 lineup: todos os personagens em uma fileira (câmera sobre [0,1.02,15], fov 16,5), nome + função + quadrados de amostra abaixo de cada um. Folha 1 turnaround do protagonista em yaw 0, .75, PI/2, PI (frente, 3/4, lado, costas). Folha 2 expressões: seis bustos (neutro, feliz, zangado, surpreso, preocupado, presunçoso) desenhados como tiles de tesoura, porque empilhar seis personagens em uma cena 3D coloca linhas sob o chão e sobreposição. Padrão de tile:
renderer.autoClear=false; renderer.setScissorTest(true);então por tilesetViewport(x, H-y-h, w, h); setScissor(...); renderer.clear(); camera.aspect = w/h; camera.updateProjectionMatrix(); renderer.render(scene, camera);e redefina o viewport, o teste de tesoura e autoClear no final deframe. - Convenção de sinal de sobrancelha:
face.brow = [altura, inclinação, arco], inclinação > 0 = pontas internas para baixo (zangado), inclinação < 0 = pontas internas para cima (preocupado). EmdrawFaceisso éang = -s * f.brow[1] * .55. (A primeira versão tinha invertido e um policial zangado parecia triste.) - Folha de ambiente 0: quatro tiles de tesoura 760x318 (herói em plano aberto da entrada, parede final com janela em arco, canto do objeto principal, uma grua de ângulo alto com
hall.ceiling.visible = false), cada um legendado; posicione o elenco no cenário para escala e faça-os enfrentar a câmera ou um ao outro (verifique yaw: yaw 0 enfrenta +z em direção a uma câmera em +z; um personagem de costas para a câmera é um bug de posicionamento). Folha 1: cada objeto sozinho em um palco de objetos distante (x = 400 + i*20) para que nunca apareça em takes do salão, tiles 486x300. Folha 2: planta baixa vista de cima comup = [-1,0,0](o eixo longo corre horizontalmente), câmera sobre [-1,120,-13] olhando diretamente para baixo, fov estreito (cerca de 9,3) e rótulos de alfinete projetados de pontos do mundo. Inclua sempre uma faixa de paleta (10 amostras) no cabeçalho. - O teto e as vigas do salão vivem em um grupo exposto como
H.ceiling(sombras desligadas, para que o sol ainda ilumine o salão); esconda-o para tomadas de grua e vista de cima. O texto da placa deve ser dimensionado para seu plano: uma placa de 1,9 x 0,5 usa800 56px Poppins, não 78px (cortado). - Pegadinha do Three.js:
Object3D.positione.rotationsão propriedades somente leitura; defina componentes (m.rotation.x = ...,m.position.set(...)) e nuncaObject.assign(mesh, { rotation: ... }), que lança uma exceção e para__ready. - Entregue os PNGs mais os arquivos
.jscom SendUserFile; re-renderize e re-envie cada folha após qualquer alteração no kit para que o usuário nunca tenha folhas desatualizadas.
3. Regras de suavidade de animação
- A animação é uma função pura do tempo:
frame(t)define tudo a partir det. Sem estado entre quadros, para que qualquer quadro possa ser renderizado em qualquer ordem ou em paralelo. - Use
kf([[t0,v0],[t1,v1]], t, easeFn)para cada movimento. UseeaseOutpara push-ins e chegadas,easeIOpara movimentos de grua/whip,easepara coisas pequenas. Nunca linear para uma câmera ou membro. - A câmera nunca está estática. Cada take tem posição eased + look-at + fov + alguns graus de roll, mais um drift de mão sempre ativo
[sin(.5t)*.03, sin(.37t)*.02]. - Cortes secos apenas nas fronteiras dos takes, em uma palavra ou batida. Sem crossfades. Duração média do take 2-3 s.
- Camadas de ociosidade em cada personagem, o tempo todo: balanço de respiração, sway, roll de cabeça, piscadas a cada ~3-4 s (0,13 s eased), pequenos dardejos de olhos.
K.idle()faz isso. - Abertura da boca vem da intensidade real da narração (
lipsync.pyescreve env.json a 30 fps). Nunca um flap de looping. - Gestos: antecipação (pequeno puxão para trás antes de um ponto/batida), overshoot (
easeBack), segurar, estabelecer. Cabeças lideram, torso segue (useheadYawantes detwist). - Ciclo de caminhada: pernas opostas, cada braço balança oposto à sua perna do mesmo lado, joelhos só dobram para trás (
Math.max(0, knee)), bob 2x por ciclo, pequeno twist e inclinação do torso. A fase está em ciclos: use cerca de 1 ciclo/s para uma caminhada normal. - Personagens nunca devem se sobrepor a móveis: posicione-os com x/z explícitos e verifique um still.
4. Pipeline
- Roteiro: primeira pessoa, gancho nos primeiros 3 segundos, frases curtas e impactantes, escalada, reviravolta, payoff na última linha. Cerca de 2,5 palavras/s.
- Narração: o usuário geralmente fornece um arquivo ElevenLabs. Se não, peça ou sintetize uma faixa temporária. Salve como
voice.wav. - Tempos das palavras: obtenha timestamps de nível de palavra (alinhamento forçado contra o texto do roteiro, por exemplo, pocketsphinx ou timestamps de palavras do whisper). Salve
words.jsoncomo[{"w":"minha","s":0.20,"e":0.41}, ...], usando a ortografia e pontuação do roteiro (as legendas usam pontuação final para quebrar grupos). - Envelope de lip-sync:
python3 lipsync.py voice.wav env.json. - Lista de takes: uma linha por take: hora de início/fim, o que o espectador vê, movimento da câmera, quem atua. Mire em um corte a cada ~2,6 s. Varie o enquadramento: close-up de rosto, plano médio de dois, push-in de ângulo baixo, grua de ângulo alto sobre a multidão, whip-tilt, over-the-shoulder, insert em um objeto (telefone, distintivo). Escreva os takes em
SHOTSemscene.html. - Construa o elenco + cenário a partir de
blockkit.js/props.js(veja o código). Reutilizeroom(),chair(),bench(),crowd(); construa novos objetos combox()na paleta acima. - Verifique stills primeiro (barato):
node render.cjs scene.html outstills 1,4,7,...; olhe para closes de cada personagem nomeado, uma caminhada, um gesto, um plano aberto. Corrija antes de renderizar o vídeo. - Renderize o vídeo (cerca de 1-1,7 s/quadro a 720x1280 em 2 núcleos, então 40 s é cerca de 15-25 min; execute partes em paralelo e em segundo plano):
export NODE_PATH=$(npm root -g) # playwright é global no sandbox; caso contrário: npm i playwright && npx playwright install chromium
S=30; for i in 0 1; do (node render.cjs scene.html part$i.mp4 --from $((i*S)) --to $(((i+1)*S)) > r$i.log 2>&1 &); done # 2 partes de 30 s; espere ambas
printf "file 'part0.mp4'\nfile 'part1.mp4'\n" > list.txt && ffmpeg -y -f concat -safe 0 -i list.txt -c copy silent.mp4
- Áudio + mux (fundo contínuo suave sob a narração, cerca de -14 LUFS):
D=130 # >= duração do vídeo
ffmpeg -y -f lavfi -i "sine=f=55:d=$D" -f lavfi -i "sine=f=82.4:d=$D" -f lavfi -i "sine=f=110:d=$D" -filter_complex "[0][1][2]amix=inputs=3:normalize=0,lowpass=f=500,tremolo=f=0.12:d=0.35,aformat=channel_layouts=stereo,afade=t=in:d=1[bed]" -map "[bed]" -ar 44100 bed.wav
ffmpeg -y -i silent.mp4 -i voice.wav -i bed.wav -filter_complex "[2:a]volume=0.12[b];[1:a][b]amix=inputs=2:duration=first:normalize=0,loudnorm=I=-14:TP=-1.5:LRA=7,aformat=channel_layouts=stereo[a]" -map 0:v -map "[a]" -c:v copy -c:a aac -b:a 192k final.mp4
- QC antes de entregar: (a) folha de contato, um quadro a cada ~2,6 s, verifique personagens/legendas; (b) verificação de movimento, diferença média de quadro em um stream cinza reduzido, espere 0 quadros estáticos:
python3 - <<'E'
import subprocess, numpy as np
W,H=90,160
a=np.frombuffer(subprocess.run(['ffmpeg','-v','error','-i','final.mp4','-vf',f'scale={W}:{H},format=gray','-f','rawvideo','-'],capture_output=True).stdout,dtype=np.uint8).reshape(-1,H,W).astype(np.float32)
d=np.abs(np.diff(a,axis=0)).mean(axis=(1,2)); print(len(a),'quadros; estáticos:',(d<.02).sum(),';picos(cortes):',(d>14).sum())
E
(c) loudness ffmpeg -i final.mp4 -af ebur128 -f null - (cerca de -14 LUFS), (d) legendas correspondem à fala.
Entregue com SendUserFile (mp4 + um still de capa).
5. Setup
mkdir -p work && cd work && npm init -y >/dev/null && npm i [email protected] @fontsource/poppins
# crie os arquivos abaixo nesta pasta: blockkit.js props.js captions.js render.cjs lipsync.py scene.html
# (ou copie os arquivos prontos da pasta kit/ deste repositório, que também tem st...
[truncado para tradução]