Communitygithub.com

Kreesten-hsh/video

Esta habilidad se debe usar cuando el usuario solicite "agregar subtítulos animados", "hacer subtítulos para TikTok", "subtítulos tipo karaoke", "subtítulos palabra por palabra", "subtítulos automáticos", "resaltar la palabra activa", "incrustar subtítulos en un video", "sincronizar subtítulos con una locución/narración" o "convertir un SRT/transcripción en texto animado". Cubre el tiempo a nivel de palabra de Whisper, entrada por palabra con escala, resaltado de palabra activa, colocación en área segura 9:16, tipografía legible e incrustación vs. SRT/VTT externo.

¿Qué es video?

Esta habilidad permite crear subtítulos animados con sincronización palabra por palabra, ideales para plataformas de video corto vertical (9:16) como TikTok, Reels y Shorts. Utiliza marcas de tiempo a nivel de palabra, generalmente obtenidas de Whisper u otras herramientas de transcripción, y las anima con un efecto de entrada por palabra (pop) y resaltado de la palabra activa, lo que genera subtítulos dinámicos que mantienen la atención del espectador. La habilidad soporta tanto la incrustación directa en el video (MP4) como la exportación de archivos SRT/VTT externos para accesibilidad. Se basa en Remotion para la animación en el lado del renderizado, pero también proporciona una alternativa basada en CSS para reproductores web. El flujo de trabajo incluye transcripción, normalización de marcas de tiempo, agrupación en "páginas" de 1 a 4 palabras, animación, colocación en el área segura, y exportación. Las reglas clave son: usar siempre marcas de tiempo reales (no interpolación), priorizar la legibilidad sobre el estilo, y mantener los subtítulos dentro de la zona segura (centro vertical, alejados de la interfaz de usuario de la plataforma). Incluye un pipeline completo, ejemplos de código (TypeScript/React, CSS) y una lista de verificación para garantizar la calidad.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Kreesten-hsh/video/tree/HEAD/.agents/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 con sincronización 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. Construye el tipo de subtítulos que impulsan el tiempo de visualización en TikTok, Reels y Shorts: legibles, al ritmo y dentro del área segura.

Cuándo usar

  • Subtítulos palabra por palabra o karaoke en video vertical de formato corto (9:16).
  • Incrustar subtítulos abiertos sobre 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 generados automáticamente en una revelación de marca "estilo Hormozi".

El pipeline

EtapaTrabajoHerramienta
TranscribirAudio → marcas de tiempo a nivel de palabraWhisper (@remotion/install-whisper-cpp), AssemblyAI
Normalizar→ tokens { text, startMs, endMs }[]Tipo Caption de @remotion/captions
PaginarAgrupar palabras en "páginas" de 1 a 4 palabrascreateTikTokStyleCaptions()
AnimarEntrada por palabra + resaltado de palabra activaspring() de Remotion / CSS
ColocarÁrea segura, tipografía legiblereglas de diseño a continuación
ExportarIncrustación (MP4) y/o SRT/VTT externoRenderizado de Remotion / emisión de archivo

Dos reglas no negociables

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

Marca de tiempo de palabra a partir de una transcripción

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

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

// Analiza un bloque de referencia SRT "00:00:01,200 --> 00:00:01,640" en 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 referencias a nivel de oración, vuelve a transcribir para obtener marcas de tiempo de palabra; no interpoles. El modelo medium.en de Whisper es aproximadamente 2x más rápido que large y lo suficientemente preciso para audio de voz limpio.

Entrada por palabra (Remotion)

Impulsa la escala y opacidad de cada palabra con un resorte anclado a su propio startMs. El microexceso 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;            // cuadro 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]);        // aparece de 60% → 100% (con exceso)
  return (
    <span style={{
      display: "inline-block",
      transform: `scale(${scale})`,
      opacity: interpolate(p, [0, 1], [0, 1]),
      color: active ? "#FFE45E" : "#FFFFFF",             // resaltado de palabra activa
      transition: "color 80ms linear",
    }}>{token.text}&nbsp;</span>
  );
};

Calcula active probando frame contra el [startMs, endMs] de cada token. Mantén el resaltado como un solo color de acento o una "caja" rellena detrás de la palabra viva; 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–500 ms para verdadero palabra por palabra, ~1000–1500 ms para frases cortas legibles.

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

Entrada en 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–80 px (≈8% de la altura del cuadro), mínimo 45 pxLegible en silencio en un teléfono
Trazo2–6 px de contorno negro sólidoSobrevive a cualquier fondo
SombraSombra suave como respaldo al trazoSeparación en cuadros brillantes
MayúsculasMayúsculas o oración; relleno de alto contrasteImpacto + escaneabilidad
Posición verticalBanda central, ~62–70% hacia abajoPor encima de la interfaz de usuario, por debajo de la acción
Seguridad inferiorMantener despejado ~280 px / 15% inferioresEvita interfaz de subtítulos/CTA/audio
Seguridad lateralMantener dentro del 80% central del anchoEvita iconos del carril derecho

Coloca los subtítulos en la banda central, no fijados al fondo, donde vive la interfaz de usuario de la plataforma (nombre de usuario, audio, botones). Acolchona el texto a un máximo de ~2 líneas.

Sincronizar 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 tiempo del audio: los subtítulos y la voz permanecen bloqueados sin ajuste manual. Si se vuelve a grabar la VO, vuelve a transcribir; nunca desplaces compensaciones manualmente.

Incrustación vs. SRT/VTT externo

Incrustación (abiertos)Externo SRT/VTT (cerrados)
DóndePíxeles en el MP4Archivo .srt/.vtt separado
Redes sociales (TikTok/Reels)Requerido: garantizado, personalizableA menudo ignorado por la plataforma
Accesibilidad (ADA/WCAG)No satisface por sí soloRequerido (conmutable)
Mejor prácticaIncrustar subtítulos animados para redes socialesTambién enviar un archivo externo para web/SEO

Emite 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 falsificación de división uniforme.
  • Entrada por palabra con microexceso; un color de acento para la palabra activa.
  • 1 a 4 palabras por página; nunca un muro de texto.
  • Sans-serif negrita, 56–80 px, trazo de 2–6 px + sombra, alto contraste.
  • Banda central, despejada ~280 px inferiores y carriles laterales.
  • Audio colocado debajo de la composición; marcas de tiempo en la base de tiempo del audio.
  • Enviar incrustación para redes sociales y un archivo SRT/VTT externo para accesibilidad.

Entregar y verificar (fotogramas renderizados → MP4)

Asistente empaquetado (scripts/): junta tus fotogramas con scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.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> + esquema zod + defaultProps): todo el movimiento de palabras se controla por fotogramas usando useCurrentFrame(), nunca Date.now() / Math.random() / temporizadores. Entregable = out/*.mp4 (incrustados) + el proyecto + el SRT/VTT externo. El vertical 9:16 (1080×1920) es el predeterminado.

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 realizar una codificación.

# Fotogramas al inicio / un fotograma de palabra activa muestreado / final — CON PROPIEDADES ENTREGADAS (tokens reales + audio)
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 = duraciónEnFotogramas-1

# Inspeccionar cada PNG:
#  - la palabra resaltada en el fotograma 90 es la palabra cuyo [startMs,endMs] contiene 90/fps (sin deriva)
#  - incrustación legible: sans-serif negrita, trazo+sombra se mantiene, sin recortes
#  - área segura 9:16: el subtítulo se sienta en la banda central, despejado del ~12% superior y del ~20-35% inferior (subtítulos/CTA/interfaz de 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 README

Usa npx remotion compositions para leer duraciónEnFotogramas/fps y elegir los fotogramas de palabra activa y final.

Antes de terminar:

  1. Los fotogramas se renderizan limpiamente en el fotograma 0, un fotograma medio de palabra activa y el último, sin falta de fuente/audio.
  2. La palabra correcta está resaltada en el fotograma muestreado (fotograma/fps cae dentro de su token); sin falsificación 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 controlado por fotogramas: sin Date.now() / Math.random() / temporizadores.
  5. Las propiedades entregadas (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 de extremo a extremo: transcripción de Whisper y el tipo Caption, un analizador SRT completo, componente de Remotion con sincronización de palabras y resaltado 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