Communitygithub.com

caption-animation

Esta skill deve ser usada quando o usuário solicitar a criação de legendas animadas, no estilo karaokê, para vídeos verticais curtos (TikTok, Reels, Shorts).

O que é caption-animation?

Esta skill transforma transcrições de áudio (voz ou narração) em legendas animadas palavra por palavra, sincronizadas com a fala, ideais para plataformas de vídeo curto vertical (9:16) como TikTok, Reels e Shorts. Ela se baseia em timestamps de nível de palavra (fornecidos por ferramentas como Whisper ou AssemblyAI) para criar uma animação de "pop" em cada palavra, destacando a palavra ativa no momento. A implementação é oferecida tanto para Remotion (usando `spring()` e componentes React) quanto para Web/CSS puro (com `animation-delay` em cada palavra). A skill impõe regras rigorosas de legibilidade: fonte sans-serif em negrito, traço/sombra pesados, posicionamento na faixa central segura do vídeo (evitando a UI da plataforma na parte inferior) e agrupamento de palavras em "páginas" de 1 a 4 palavras para evitar blocos de texto. O resultado final pode ser uma legenda "aberta" (queimada no vídeo MP4) ou um arquivo SRT/VTT separado (para acessibilidade), sendo que a prática recomendada é gerar ambos. O fluxo de trabalho inclui transcrição, normalização dos tokens, paginação, animação, renderização de stills para verificação e codificação final do vídeo.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/iart-ai/tiktok-video-skills/tree/main/skills/caption-animation

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Animação de Legendas

Transforme uma transcrição ou narração em legendas palavra por tempo, que param a rolagem: cada palavra aparece na sílaba, a palavra ativa é destacada e o texto permanece colado à narração. Construa o tipo de legenda que aumenta o tempo de exibição no TikTok, Reels e Shorts — legível, no ritmo e dentro da área segura.

Quando usar

  • Legendas palavra por palavra ou karaokê em vídeo vertical curto (9:16).
  • Inserção de legendas abertas em uma faixa de voz/narração a partir de uma transcrição.
  • Transformar uma transcrição do Whisper/SRT em texto animado no Remotion ou na web.
  • Reestilizar legendas automáticas em uma revelação com marca, "estilo Hormozi".

O pipeline

EtapaTarefaFerramenta
TranscreverÁudio → timestamps de nível de palavraWhisper (@remotion/install-whisper-cpp), AssemblyAI
Normalizar→ tokens { text, startMs, endMs }[]Tipo Caption do @remotion/captions
PaginarAgrupar palavras em "páginas" de 1–4 palavrascreateTikTokStyleCaptions()
AnimarPop por palavra + destaque da palavra ativaspring() do Remotion / CSS
PosicionarÁrea segura, tipo legívelregras de layout abaixo
ExportarInserção (MP4) e/ou SRT/VTT lateralRenderização do Remotion / emissão de arquivo

Duas regras inegociáveis

  1. Tempo no nível da palavra, não no nível da linha. O karaokê só funciona como mágica quando cada palavra cai na sílaba. Sempre transcreva para timestamps de palavra; nunca finja dividindo uma linha igualmente por sua duração — o desvio é instantaneamente visível.
  2. Legibilidade supera estilo. Legendas são lidas em um telefone, sob luz do sol, sem som. Sans-serif em negrito, traço ou sombra pesados, alto contraste primeiro; decoração depois. Uma legenda que ninguém consegue ler é decoração, não uma legenda.

Tempo de palavra a partir de uma transcrição

O Whisper emite timestamps de nível de palavra diretamente. Normalize cada fonte em uma única forma de token plana para que o renderizador nunca se importe de onde vieram as palavras:

// Token de legenda — a única forma que tudo downstream consome
type Token = { text: string; startMs: number; endMs: number };

// Analisar um bloco de sinalização SRT "00:00:01,200 --> 00:00:01,640" em ms
const toMs = (t: string) => {
  const [h, m, rest] = t.split(":");
  const [s, ms] = rest.split(",");
  return ((+h * 60 + +m) * 60 + +s) * 1000 + +ms;
};

Se existirem apenas sinalizações de nível de frase, retranscreva para tempo de palavra — não interpole. O medium.en do Whisper é ~2x mais rápido que o large e preciso o suficiente para áudio de voz limpo.

Pop-in por palavra (Remotion)

Conduza a escala e opacidade de cada palavra com uma mola ancorada em seu próprio startMs. O micro-excesso é o que faz parecer "vivo".

import { useCurrentFrame, useVideoConfig, spring, interpolate } from "remotion";

const Word: React.FC<{ token: Token; active: boolean }> = ({ token, active }) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();
  const enter = (token.startMs / 1000) * fps;            // quadro de entrada da própria palavra
  const p = spring({ frame: frame - enter, fps, config: { damping: 12, mass: 0.6 } });
  const scale = interpolate(p, [0, 1], [0.6, 1]);        // pop de 60% → 100% (excede)
  return (
    <span style={{
      display: "inline-block",
      transform: `scale(${scale})`,
      opacity: interpolate(p, [0, 1], [0, 1]),
      color: active ? "#FFE45E" : "#FFFFFF",             // destaque da palavra ativa
      transition: "color 80ms linear",
    }}>{token.text}&nbsp;</span>
  );
};

Calcule active testando frame contra [startMs, endMs] de cada token. Mantenha o destaque como uma única cor de destaque ou uma "caixa" preenchida atrás da palavra ativa — nunca anime a cor de todas as palavras ao mesmo tempo.

Páginas, não paredes de texto

Mostre de 1 a 4 palavras por vez. Use createTikTokStyleCaptions() do Remotion (ou agrupe manualmente) e ajuste combineTokensWithinMilliseconds: ~200–500ms para verdadeiro palavra por palavra, ~1000–1500ms para frases curtas legíveis.

import { createTikTokStyleCaptions } from "@remotion/captions";
const { pages } = createTikTokStyleCaptions({
  captions, combineTokensWithinMilliseconds: 1200,       // ~2–3 palavras por página
});

Pop Web/CSS (sem Remotion)

Para um player DOM/ao vivo, dê a cada palavra seu próprio animation-delay igual a startMs:

.word { display:inline-block; opacity:0; animation: pop .26s cubic-bezier(.34,1.56,.64,1) forwards; }
@keyframes pop { from { opacity:0; transform:translateY(.18em) scale(.7) } to { opacity:1; transform:none } }
.word.active { color:#FFE45E; }
words.forEach(w => { const el = mk(w.text); el.style.animationDelay = `${w.startMs}ms`; track.append(el); });

Tipo legível e posicionamento em área segura (9:16, 1080×1920)

PropriedadeValorPor quê
FonteSans-serif Negrito/ExtraNegrito (Montserrat, Inter, Helvetica)Lê-se em telas pequenas e movimentadas
Tamanho56–80px (≈8% da altura do quadro), min 45pxLegível sem som em um telefone
Traço2–6px contorno preto sólidoSobrevive a qualquer fundo
SombraSombra suave como backup do traçoSeparação em quadros claros
CaixaMaiúscula ou frase; preenchimento de alto contrasteImpacto + escaneabilidade
Posição verticalFaixa central, ~62–70% para baixoAcima da UI, abaixo da ação
Segurança inferiorManter longe do ~280px / 15% inferiorEvita UI de legenda/CTA/áudio
Segurança lateralManter dentro dos 80% centrais da larguraEvita ícones da barra lateral direita

Posicione as legendas na faixa central — não fixadas na parte inferior, onde reside a UI da plataforma (nome de usuário, áudio, botões). Preencha o texto com no máximo ~2 linhas.

Sincronizar com uma narração

A transcrição já carrega o tempo do áudio exato. Coloque esse mesmo áudio sob a composição e mantenha os timestamps na base de tempo do áudio — legendas e voz permanecem travados sem ajustes manuais. Se a narração for regravada, retranscreva; nunca desloque manualmente.

Inserção vs. SRT/VTT lateral

Inserção (aberta)SRT/VTT lateral (fechada)
OndePixels no MP4Arquivo .srt/.vtt separado
Social (TikTok/Reels)Obrigatório — garantido, estilizávelFrequentemente ignorado pela plataforma
Acessibilidade (ADA/WCAG)Não satisfaz sozinhoObrigatório (alternável)
Melhor práticaGrave legendas animadas para redes sociaisTambém envie um lateral para a web/SEO

Emita ambos: grave as legendas animadas para redes sociais e escreva um SRT/VTT simples a partir dos mesmos tokens para reprodução acessível/SEO.

Lista de verificação de saída

  • Timestamps de nível de palavra (reais, de transcrição) — sem falsificação de divisão igual.
  • Pop por palavra com micro-excesso; uma cor de destaque para a palavra ativa.
  • 1–4 palavras por página; nunca uma parede de texto.
  • Sans-serif em negrito, 56–80px, 2–6px traço + sombra, alto contraste.
  • Faixa central, limpa do ~280px inferior e barras laterais.
  • Áudio colocado sob a composição; timestamps na base de tempo do áudio.
  • Envie inserção para redes sociais e um SRT/VTT lateral para acessibilidade.

Entregar e verificar (stills renderizados → MP4)

Auxiliar empacotado (scripts/): organize seus stills com scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png, em seguida, verifique a codificação com scripts/probe-mp4.sh out.mp4 [WxH] [fps]. Consulte scripts/README.md.

As legendas são enviadas como uma composição Remotion (<Composition> + esquema zod + defaultProps) — todo movimento de palavra é baseado em quadro com useCurrentFrame(), nunca Date.now() / Math.random() / timers. Entregável = out/*.mp4 (inserido) + o projeto + o SRT/VTT lateral. 9:16 vertical (1080×1920) é o padrão.

Loop de verificação — renderizar stills → inspecionar → codificar. O tempo das palavras é a coisa que quebra; verifique-o em quadros exatos antes de gastar uma codificação.

# Stills no início / em um quadro de palavra ativa amostrado / no final — COM PROPS ENVIADOS (tokens reais + áudio)
npx remotion still Captions out/f-start.png --frame=0   --props='{"captionsSrc":"vo.json"}'
npx remotion still Captions out/f-mid.png   --frame=90  --props='{"captionsSrc":"vo.json"}'
npx remotion still Captions out/f-end.png   --frame=N   --props='{"captionsSrc":"vo.json"}'  # N = duracaoEmQuadros-1

# Inspecionar cada PNG:
#  - a palavra destacada no quadro 90 é a palavra cujo [startMs,endMs] contém 90/fps (sem desvio)
#  - legenda inserida legível: sans-serif em negrito, traço+sombra segura, sem recorte
#  - área segura 9:16: a legenda fica na faixa central, limpa do topo ~12% e do fundo ~20-35% (UI de legendas/CTA/áudio) e da barra de ação direita

npx remotion render Captions out/captions.mp4 --props='{"captionsSrc":"vo.json"}'   # codificar quando os stills estiverem corretos
npx remotion render Captions out/demo.gif --codec=gif                                # clipe de prova README

Use npx remotion compositions para ler durationInFrames/fps e escolher os quadros de palavra ativa e final.

Antes de finalizar:

  1. Os stills renderizam limpos no quadro 0, em um quadro intermediário de palavra ativa e no final — sem fontes/áudio ausentes.
  2. A palavra correta é destacada no quadro amostrado (quadro/fps cai dentro de seu token); sem falsificação de divisão igual.
  3. A legenda inserida é legível (traço+sombra) e está totalmente dentro da área segura 9:16 em cada quadro verificado.
  4. Apenas baseado em quadro — sem Date.now() / Math.random() / timers.
  5. Props enviados (tokens reais, não apenas defaultProps) renderizam corretamente; MP4 + SRT/VTT lateral emitidos, GIF opcional.

Arquivos de referência

  • references/word-timed-captions.md — construção completa: transcrição Whisper e o tipo Caption, um analisador SRT completo, componente de tempo de palavra Remotion com destaque ativo, paginação manual, um emissor SRT/VTT, mapas de área segura por plataforma e uma ficha técnica de tipo legível.

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

Habilidades Relacionadas