Legendas 3D com câmera
O movimento do After Effects (câmera 3D, profundidade de campo, texto em caminho, Posterize Time 15, motion blur) reconstruído como um pintor determinístico quadro a quadro para HyperFrames. Um relógio desenha plato, alfa e cada glifo. Nada é animação CSS.
Quando usar / quando não usar
- Use em um take de cabeça falante com câmera estática (avatar ou real), paisagem, plano médio ou mais aberto, 5–20 s por peça. As palavras devem atuar em profundidade ao redor do falante.
- Não use para legendas simples, câmera em movimento ou handheld (o alfa e a transformação do plato assumem um plano travado), close-ups extremos (sem espaço atrás ou ao redor da cabeça), ou um sujeito que um modelo de segmentação de pessoas não consegue separar.
Requisitos e efeitos colaterais (lista completa)
- Ferramentas:
- Node 22+ e o HyperFrames CLI travado na versão testada,
npx [email protected](baixa deregistry.npmjs.org). Use essa versão exata para todos os comandos abaixo. ffmpegno PATH.- Python 3 com
fonttools,brotli,numpy,pillow.
- Node 22+ e o HyperFrames CLI travado na versão testada,
- Alfa:
npx [email protected] remove-backgroundexecuta localmente. Na primeira execução, baixa o modelo de segmentação de pessoas (~170 MB) para~/.cache/hyperframes/. - Tempo de renderização: as composições carregam GSAP 3.14.2 de
cdn.jsdelivr.netno momento da pré-visualização/renderização. - Relógio de palavras: qualquer transcritor no nível da palavra que o usuário já execute localmente. A precisão do início é importante; o Whisper simples começa atrasado.
- Fontes: arquivos fornecidos pelo usuário. Confirme se a licença permite incorporação.
- Estilo desenhado à mão (opcional): precisa da habilidade
p5-paint-animationinstalada e configurada (sua configuração baixa o puppeteer travado, Chrome for Testing, p5 e p5.brush).build-sprites.pyexecuta isso sem cabeça; os quadros são escritos na pastaout/dessa habilidade e removidos depois. - Nada mais: sem credenciais, sem chamadas pagas, nada enviado. Arquivos são escritos apenas no projeto (
assets/,renders/).
Preparação (uma vez por take)
- Porção e plato.
bash scripts/prep-take.sh <take.mp4> <start_s> <dur_s> <project>/assetsproduz:portion.mp4: a fonte de áudio.plate-tall.mp4: plato com padding refletido de 240 px cada lado, 480 px topo, 240 px base, para que whips, trucks e pull-backs nunca mostrem uma borda.person.webm: o alfa, mesmos quadros.- O script então verifica o plato e o alfa contra a porção (tamanho, taxa de quadros, número de quadros) e falha em qualquer incompatibilidade.
- Relógio de palavras. Tempos das palavras para a porção, em segundos a partir do seu início.
- Fontes. Escolha 2–3 papéis (legenda / herói / anel), depois
python3 scripts/font-metrics.py assets/metrics.js key=font.woff2[@wght=…,opsz=…] ….- Uma fonte variável deve receber seus eixos aqui E fixados no CSS (
font-variation-settings,font-optical-sizing: none). Caso contrário, o opsz do navegador segue o tamanho e as palavras se sobrepõem.
- Uma fonte variável deve receber seus eixos aqui E fixados no CSS (
- Silhueta. Leia a caixa da cabeça e a linha do ombro do alfa
person.webmantes de posicionar qualquer coisa.
Construção
- Copie
assets/kit/{cam3d.js,tables.js,finish.js}emetrics.jspara a pastaassets/do projeto. references/worked-film.htmlé o filme de referência completo. Ele tem dois planos, uma wipe de corpo, ambos os tipos de anel, um fly-through, um finale e a gradagem; sua mídia não está incluída.references/recipes.mdcontém os grupos de profundidade, o anel envolvente, a escada e as palavras desenhadas à mão.C3.init(metrics, {W, H}):- A lente mantém o campo de visão vertical do tutorial (F = 1326.1·H/1080), então
tables.jsse aplica sem escala em qualquer largura. - Em um quadro mais largo que 1440, adicione
(W − 1440) / 2awipe.x.
- A lente mantém o campo de visão vertical do tutorial (F = 1326.1·H/1080), então
Gramática do plano
Escolha 4–6 beats. Divida cada cláusula falada em seu próprio grupo.
| Beat | Construção | Regra |
|---|---|---|
| Whip-in punch-in | tabela camA (19 f a partir de 502 px abaixo) depois um creep linear | plato S0 1.1–1.3; o quadro nunca pousa |
| Grupos de profundidade | 2–3 grupos de legenda por cláusula em D 1250 / 1500 / 2200 | perto = maior com sombra maior; o creep e o truck revelam a paralaxe |
| Herói atrás | palavra herói na camada de trás na profundidade do plato, estilo masthead sobre a cabeça | a cabeça esconde as letras do meio; ≥ 60 % permanece legível; o ascendente está dentro do quadro |
| Truck de paralaxe | segmento swing, tx 420 | o layout antigo desvanece em 3 passos; o próximo layout chega por paralaxe |
| Wipe de corpo | o próprio alfa do falante, brightness(0) blur(), borda principal de wipe.x | plano A corta à esquerda da faixa, plano B à direita; pull-out em ambos os lados |
| Anel envolvente | círculo horizontal NA FRENTE do falante (centro Z 2000), 11–16° de cima, girando | palavras entram pequenas na lateral ao serem faladas, balançam pela frente, se assentam; arco inferior vira para ler na vertical |
| Fly-through | frases em D 1500 / 2000 / 2500, câmera balança entre elas | foco segue a frase |
| Final em escada | segmento push; palavras da legenda cada vez mais profundas e menores em uma diagonal | o push separa as palavras próximas |
Regras
- Movimento:
- O texto é posterizado para 15 fps (sua própria animação e a câmera que ele vê). Plato e alfa se movem a cada quadro.
- Motion blur tem 7 fantasmas sobre metade de um passo de 15 fps. Use
shutter: 0.5em anéis girando. - Legendas lideram a voz por 0.2 s, ajustado na grade de 15 fps:
fr(t) = 2·round((t − 0.2)·15). - Entradas sobem por
ENTRY: legendas 30 px, heróis 110–260 px. Palavras do anel suavizam seus últimos 30–44 px ao longo do caminho. Use o overshoot deRING_ENTRYapenas quando nenhum vizinho está assentado ainda.
- Profundidade:
- Cada grupo recebe seu próprio D. Plato 3000, legendas 1250–2400, anel envolvente ≈ 1100–2700.
- Sombra de profundidade
[6, 16, 0.5, 1500]escala como 1500/(D − cz). - DOF σ = 6.7·|D − focus|/D px a 1080 de altura. Legendas planas limitam a 1.2 px; glifos do anel recebem o blur completo.
- Tamanhos:
size/risesão px na tela na câmera de pouso (tland). Sprites são renderizados no seu tamanho na tela. - Espaçamento:
- Texto do anel sempre passa por
ringLine(avanço na tela; tipo: tracking 1.10, gap 26–30; desenhado à mão: tracking 1.0, gap 28). - Um anel girando é recolocado a cada pose.
- Texto do anel sempre passa por
- Oclusão:
- Anéis desenham na frente do falante.
- Divida uma palavra em instâncias atrás/frente (
side,zSplit) apenas quando ela DEVE passar atrás da cabeça.
- Legibilidade:
- Legendas vão sobre o lado escuro do quadro.
- Texto cruzando pele ou roupa clara recebe um halo escuro.
- Sem pontos finais na tela.
- Gradagem (opcional):
finish.jsfornece curvas, saturação 1.12 para pele e grão recalculado por quadro. Coloque a cena opaca (plato + texto atrás + alfa) em um wrapper filtrado. Dê ao texto da frente a mesma gradagem com grão composto sobre ele, para que uma palavra dividida atrás/frente permaneça uma cor (vejareferences/worked-film.html).
Estilos
- Editorial: uma serifa condensada de exibição, itálico para heróis; uma serifa de texto para legendas; creme mais um acento.
- Negrito: grotesco pesado para heróis, leve para legendas, um acento saturado (o filme trabalhado).
- Desenhado à mão: sprites p5.brush write-on de
scripts/hand/build-sprites.py:- Cada palavra se escreve a partir do seu início.
- Anéis colocam palavras inteiras, rotacionadas para a tangente e escaladas por profundidade, em um anel maior e mais plano.
- Receita em
references/recipes.md.
Verificação
npx [email protected] check . --no-contrast. As camadas fantasma ativam a verificação de contraste WCAG; as costuras do whip e do wipe se sobrepõem por design.- Na página de pré-visualização, cole
scripts/frame-bounds.jse execute__frameBounds(0, dur).- Deve retornar
[]: nenhum grupo fora do quadro por 5+ passos enquanto visível.
- Deve retornar
- Cole
scripts/glyph-gaps.jse execute__glyphGaps(t)em repouso.- Sem toques entre palavras.
- Palavras planas medem como uma caixa (descendentes sinalizam falsos positivos). O overhang de um f itálico lê negativo. Palavras em lados opostos da faixa do wipe aparecem ambas.
- Lacunas entre palavras: a lacuna renderizada entre palavras ≈ um espaço de avanço.
- Faça snapshot de cada beat, renderize com
npx [email protected] render . --crf 12e verifique quadros em resolução total por beat.
Limites
- Bordas do alfa piscam em cabelos finos. O padding espelhado aparece brevemente durante um whip.
- Um falante que preenche o quadro deixa pouco espaço atrás: vá para altura masthead, ou esconda atrás de um ombro.
snapshot --atcompartilha uma página, então o grão não é recalculado entre capturas. Julgue o grão em uma renderização.- Renderizar com a gradagem e muitas camadas fantasma custa cerca de 2× uma renderização simples.