Communitygithub.com

caption-animation

Esta skill se utiliza cuando el usuario solicita crear subtítulos animados palabra por palabra a partir de una transcripción o locución.

¿Qué es caption-animation?

Esta skill permite transformar una transcripción o locución en subtítulos animados sincronizados palabra por palabra, ideales para plataformas de video corto como TikTok, Reels y Shorts. Cada palabra aparece siguiendo el ritmo de la sílaba, la palabra activa se resalta y el texto se mantiene dentro del área segura del video. La skill utiliza un pipeline que comienza con la transcripción a nivel de palabra mediante herramientas como Whisper o AssemblyAI, luego normaliza los datos en tokens con marcas de tiempo, los agrupa en fragmentos de 1 a 4 palabras usando `createTikTokStyleCaptions()`, y finalmente los anima con Remotion o CSS. Soporta tanto la incrustación directa en el video (MP4) como la generación de archivos SRT/VTT para accesibilidad. Su diseño prioriza la legibilidad en dispositivos móviles, usando tipografía sans-serif en negrita, trazo y sombra para alto contraste, y evitando muros de texto. Es compatible con Remotion y entornos web sin Remotion, y requiere marcas de tiempo reales obtenidas de transcripción, nunca interpoladas.

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

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Animación de Subtítulos

Convierte una transcripción o locución en subtítulos sincronizados palabra por palabra que detienen el desplazamiento: cada palabra aparece en la sílaba, la palabra activa se resalta y el texto se mantiene pegado a la narración. Crea el tipo de subtítulos que aumentan el tiempo de visualización en TikTok, Reels y Shorts: legibles, al ritmo y dentro del área segura.

Cuándo usarlo

  • Subtítulos palabra por palabra o estilo karaoke en video vertical corto (9:16).
  • Incrustar subtítulos abiertos en una pista de locución/narración a partir de una transcripción.
  • Convertir una transcripción de Whisper/SRT en texto animado en Remotion o en la web.
  • Reestilizar subtítulos automáticos en una revelación "estilo Hormozi" con marca.

El pipeline

EtapaTareaHerramienta
TranscripciónAudio → marcas de tiempo a nivel de palabraWhisper (@remotion/install-whisper-cpp), AssemblyAI
Normalización→ tokens { text, startMs, endMs }[]Tipo Caption de @remotion/captions
PaginaciónAgrupar palabras en "páginas" de 1 a 4createTikTokStyleCaptions()
AnimaciónAparición por palabra + resalte de palabra activaspring() de Remotion / CSS
ColocaciónÁrea segura, tipografía legiblereglas de diseño a continuación
ExportaciónIncrustado (MP4) y/o SRT/VTT externoRenderizado de Remotion / emisión de archivo

Dos reglas no negociables

  1. Sincronización a nivel de palabra, no a nivel de línea. El karaoke se siente mágico solo cuando cada palabra cae en la sílaba. Siempre transcribe a marcas de tiempo de palabra; nunca las falsifiques dividiendo una línea uniformemente en su duración: la deriva es inmediatamente visible.
  2. La legibilidad supera al estilo. Los subtítulos se leen en un teléfono, con luz solar, sin volumen. Sans-serif en negrita, trazo pesado o sombra, alto contraste primero; decoración después. Un subtítulo que nadie puede leer es decoración, no un subtítulo.

Sincronización de palabras a partir de una transcripción

Whisper emite marcas de tiempo a nivel de palabra directamente. Normaliza cada fuente en una forma plana de token para que el renderizador nunca se preocupe de dónde vinieron las palabras:

// Token de subtítulo: la forma única que consume todo el flujo descendente
type Token = { text: string; startMs: number; endMs: number };

// Analizar un bloque de señal SRT "00:00:01,200 --> 00:00:01,640" a ms
const toMs = (t: string) => {
  const [h, m, rest] = t.split(":");
  const [s, ms] = rest.split(",");
  return ((+h * 60 + +m) * 60 + +s) * 1000 + +ms;
};

Si solo existen señales a nivel de oración, vuelve a transcribir para obtener marcas de tiempo de palabra — no interpoles. El modelo medium.en de Whisper es ~2 veces más rápido que large y suficientemente preciso para audio de voz limpio.

Aparición palabra por palabra (Remotion)

Impulsa la escala y opacidad de cada palabra con un resorte anclado a su propio startMs. El micro-sobreimpulso es lo que lo hace sentir "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;            // fotograma de entrada propio de la palabra
  const p = spring({ frame: frame - enter, fps, config: { damping: 12, mass: 0.6 } });
  const scale = interpolate(p, [0, 1], [0.6, 1]);        // aparecer desde 60% → 100% (sobreimpulsa)
  return (
    <span style={{
      display: "inline-block",
      transform: `scale(${scale})`,
      opacity: interpolate(p, [0, 1], [0, 1]),
      color: active ? "#FFE45E" : "#FFFFFF",             // resalte de palabra activa
      transition: "color 80ms linear",
    }}>{token.text}&nbsp;</span>
  );
};

Calcula active probando frame contra [startMs, endMs] de cada token. Mantén el resalte en un solo color de acento o en un "recuadro" relleno detrás de la palabra activa — nunca animes el color de todas las palabras a la vez.

Páginas, no muros de texto

Muestra de 1 a 4 palabras a la vez. Usa createTikTokStyleCaptions() de Remotion (o agrupa manualmente) y ajusta combineTokensWithinMilliseconds: ~200–500ms para palabra por palabra real, ~1000–1500ms para frases cortas legibles.

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

Aparición Web/CSS (sin Remotion)

Para un reproductor DOM/en vivo, asigna a cada palabra su propio 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); });

Tipografía legible y colocación en área segura (9:16, 1080×1920)

PropiedadValorPor qué
FuenteSans-serif Negrita/ExtraNegrita (Montserrat, Inter, Helvetica)Se lee en pantallas pequeñas y ocupadas
Tamaño56–80px (~8% de la altura del fotograma), mínimo 45pxLegible sin volumen en un teléfono
Trazo2–6px de contorno negro sólidoSobrevive a cualquier fondo
SombraSombra suave como respaldo del trazoSeparación en fotogramas brillantes
MayúsculasMayúsculas o oracional; relleno de alto contrasteImpacto + escaneabilidad
Posición verticalBanda central, ~62–70% hacia abajoEncima de la UI, debajo de la acción
Seguridad inferiorMantener libre de ~280px / 15% inferiorEvita subtítulos/CTA/UI de audio
Seguridad lateralMantener dentro del 80% central de anchoEvita iconos de la barra lateral derecha

Coloca los subtítulos en la banda central — no fijados al fondo, donde reside la UI de la plataforma (nombre de usuario, audio, botones). Aplica un máximo de ~2 líneas de texto.

Sincronización con una locución / narración

La transcripción ya lleva el tiempo del audio exacto. Coloca ese mismo audio debajo de la composición y mantén las marcas de tiempo en la base de tiempos del audio — los subtítulos y la voz se mantienen bloqueados sin ajustes manuales. Si la locución se regraba, vuelve a transcribir; nunca cambies los desplazamientos a mano.

Incrustado vs SRT/VTT externo

Incrustado (abierto)SRT/VTT externo (cerrado)
DóndePíxeles en el MP4Archivo .srt/.vtt separado
Social (TikTok/Reels)Obligatorio — garantizado, personalizableA menudo ignorado por la plataforma
Accesibilidad (ADA/WCAG)No satisface por sí soloObligatorio (conmutable)
Mejor prácticaIncrustar subtítulos animados para redes socialesTambién enviar un archivo externo para web/SEO

Emitir ambos: incrusta los subtítulos animados para redes sociales y escribe un SRT/VTT simple a partir de los mismos tokens para reproducción accesible/SEO.

Lista de verificación de salida

  • Marcas de tiempo a nivel de palabra (reales, de transcripción) — sin falsificaciones de división uniforme.
  • Aparición palabra por palabra con micro-sobreimpulso; un color de acento para la palabra activa.
  • 1–4 palabras por página; nunca un muro de texto.
  • Sans-serif en negrita, 56–80px, trazo de 2–6px + sombra, alto contraste.
  • Banda central, libre de ~280px inferiores y barras laterales.
  • Audio colocado debajo de la composición; marcas de tiempo en la base de tiempos del audio.
  • Enviar incrustado para redes sociales y un SRT/VTT externo para accesibilidad.

Entregar y verificar (fotogramas renderizados → MP4)

Asistente empaquetado (scripts/): organiza tus fotogramas con scripts/contact-sheet.sh sheet.png f-inicio.png f-medio.png f-fin.png, luego verifica la codificación con scripts/probe-mp4.sh out.mp4 [WxH] [fps]. Consulta scripts/README.md.

Los subtítulos se entregan como una composición de Remotion (<Composition> + schema zod + defaultProps) — todo el movimiento de palabras es impulsado por fotogramas usando useCurrentFrame(), nunca Date.now() / Math.random() / temporizadores. Entregable = out/*.mp4 (incrustado) + el proyecto + el SRT/VTT externo. El valor predeterminado es 9:16 vertical (1080×1920).

Bucle de verificación — renderizar fotogramas → inspeccionar → codificar. La sincronización de palabras es lo que se rompe; verifícala en fotogramas exactos antes de gastar una codificación.

# Fotogramas al inicio / un fotograma de palabra activa muestreado / fin — CON PROPS ENVIADAS (tokens reales + audio)
npx remotion still Captions out/f-inicio.png --frame=0   --props='{"captionsSrc":"vo.json"}'
npx remotion still Captions out/f-medio.png   --frame=90  --props='{"captionsSrc":"vo.json"}'
npx remotion still Captions out/f-fin.png   --frame=N   --props='{"captionsSrc":"vo.json"}'  # N = durationInFrames-1

# Inspeccionar cada PNG:
#  - la palabra resaltada en el fotograma 90 es la palabra cuyo [startMs,endMs] contiene 90/fps (sin deriva)
#  - incrustado legible: sans-serif negrita, trazo+sombra se mantiene, sin recorte
#  - área segura 9:16: el subtítulo se sitúa en la banda central, libre de ~12% superior y ~20-35% inferior (UI de subtítulos/CTA/audio) y del carril de acción derecho

npx remotion render Captions out/captions.mp4 --props='{"captionsSrc":"vo.json"}'   # codificar una vez que los fotogramas estén bien
npx remotion render Captions out/demo.gif --codec=gif                                # clip de prueba para README

Usa npx remotion compositions para leer durationInFrames/fps y seleccionar la palabra activa y los fotogramas finales.

Antes de terminar:

  1. Los fotogramas se renderizan limpios en el fotograma 0, un fotograma de palabra activa intermedio y el último — sin errores de fuente/audio.
  2. La palabra correcta está resaltada en el fotograma muestreado (frame/fps cae dentro de su token); sin falsificaciones de división uniforme.
  3. La incrustación es legible (trazo+sombra) y el subtítulo está completamente dentro del área segura 9:16 en cada fotograma verificado.
  4. Solo impulsado por fotogramas — sin Date.now() / Math.random() / temporizadores.
  5. Las props enviadas (tokens reales, no solo defaultProps) se renderizan correctamente; se emiten MP4 + SRT/VTT externo, GIF opcional.

Archivos de referencia

  • references/word-timed-captions.md — construcción completa: transcripción con Whisper y el tipo Caption, un analizador SRT completo, componente de Remotion con sincronización de palabras y resalte activo, paginación manual, un emisor SRT/VTT, mapas de área segura por plataforma y una hoja de especificaciones de tipografía legible.

Individual skills in this repo

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

Skills relacionados