Communitygithub.com

caption-animation

Transforme un script ou une voix off en sous-titres animés synchronisés mot par mot, idéaux pour les vidéos courtes.

Qu'est-ce que caption-animation ?

Ce skill permet de créer des sous-titres animés de type karaoké, où chaque mot apparaît en synchronisation avec la voix, le mot actif étant mis en évidence. Il est conçu pour les vidéos verticales courtes (TikTok, Reels, Shorts) et les lecteurs web. Le pipeline part d'une transcription audio (via Whisper ou AssemblyAI) pour obtenir des horodatages par mot, puis normalise ces données, les regroupe en pages de 1 à 4 mots, et les anime avec un effet de pop-in via Remotion (spring) ou du CSS. Des règles strictes garantissent la lisibilité : polices sans-serif grasses, contour épais, placement dans la zone de sécurité centrale. Le skill prend en charge le rendu avec incrustation dans la vidéo (burn-in) et la génération de fichiers SRT/VTT séparés pour l'accessibilité. Il inclut également des scripts de vérification pour valider le timing et le rendu avant l'export final.

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

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Animation de sous-titres

Transformez un script ou une voix off en sous-titres animés mot par mot, captivants et synchronisés : chaque mot apparaît sur la syllabe, le mot actif est mis en évidence, et le texte reste collé à la narration. Créez le genre de sous-titres qui retiennent l'attention sur TikTok, Reels et Shorts — lisibles, en rythme et dans la zone de sécurité.

Quand l'utiliser

  • Sous-titres mot par mot ou karaoké sur des vidéos verticales courtes (9:16).
  • Incrustation de sous-titres ouverts sur une piste voix off/narration à partir d'un script.
  • Transformation d'une transcription Whisper/SRT en texte animé dans Remotion ou sur le web.
  • Restylage de sous-titres automatiques en un dévoilement de type « Hormozi » et personnalisé.

Le pipeline

ÉtapeTâcheOutil
TranscrireAudio → horodatages par motWhisper (@remotion/install-whisper-cpp), AssemblyAI
Normaliser→ tableau { text, startMs, endMs }[]type Caption de @remotion/captions
PaginerGrouper les mots en « pages » de 1 à 4 motscreateTikTokStyleCaptions()
AnimerPop-in par mot + mise en évidence du mot actifspring() Remotion / CSS
PlacerZone de sécurité, typographie lisiblerègles de disposition ci-dessous
ExporterIncrustation (MP4) et/ou fichier SRT/VTTrendu Remotion / émission de fichier

Deux règles non négociables

  1. Temporalité par mot, pas par ligne. Le karaoké n'est magique que lorsque chaque mot tombe sur la syllabe. Transcrivez toujours avec des horodatages par mot ; ne les simulez jamais en divisant une ligne uniformément sur sa durée — le décalage est immédiatement visible.
  2. La lisibilité prime sur le style. Les sous-titres sont lus sur un téléphone, en plein soleil, en mode silencieux. Gras sans-serif, contour épais ou ombre, contraste élevé d'abord ; la décoration vient après. Un sous-titre que personne ne peut lire est une décoration, pas un sous-titre.

Synchronisation mot par mot à partir d'un script

Whisper produit directement des horodatages par mot. Normalisez chaque source en une forme de jeton plate unique afin que le moteur de rendu n'ait jamais à se soucier de l'origine des mots :

// Jeton de sous-titre — la forme unique que tout en aval consomme
type Token = { text: string; startMs: number; endMs: number };

// Analyser un bloc 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;
};

S'il n'existe que des repères de niveau phrase, retranscrivez pour obtenir des horodatages par mot — n'interpolez pas. Whisper medium.en est environ 2× plus rapide que large et suffisamment précis pour un audio vocal propre.

Pop-in par mot (Remotion)

Animez l'échelle et l'opacité de chaque mot à l'aide d'un ressort (spring) ancré à son propre startMs. Le micro-dépassement est ce qui donne l'impression de « vie ».

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

const Mot: React.FC<{ jeton: Token; actif: boolean }> = ({ jeton, actif }) => {
  const frame = useCurrentFrame();
  const { fps } = useVideoConfig();
  const entree = (jeton.startMs / 1000) * fps;            // image d'entrée du mot
  const p = spring({ frame: frame - entree, fps, config: { damping: 12, mass: 0.6 } });
  const echelle = interpolate(p, [0, 1], [0.6, 1]);        // pop de 60 % → 100 % (dépasse)
  return (
    <span style={{
      display: "inline-block",
      transform: `scale(${echelle})`,
      opacity: interpolate(p, [0, 1], [0, 1]),
      color: actif ? "#FFE45E" : "#FFFFFF",             // mise en évidence du mot actif
      transition: "color 80ms linear",
    }}>{jeton.text}&nbsp;</span>
  );
};

Calculez actif en testant frame par rapport à [startMs, endMs] de chaque jeton. Utilisez une seule couleur d'accent ou une « boîte » remplie derrière le mot en cours — n'animez jamais la couleur de tous les mots à la fois.

Pages, pas des murs de texte

Affichez 1 à 4 mots à la fois. Utilisez createTikTokStyleCaptions() de Remotion (ou groupez manuellement) et réglez combineTokensWithinMilliseconds : ~200–500 ms pour du mot par mot, ~1000–1500 ms pour de courtes phrases lisibles.

import { createTikTokStyleCaptions } from "@remotion/captions";
const { pages } = createTikTokStyleCaptions({
  captions, combineTokensWithinMilliseconds: 1200,       // ~2–3 mots par page
});

Pop-in Web/CSS (sans Remotion)

Pour un lecteur DOM/en direct, donnez à chaque mot son propre animation-delay égal à startMs :

.mot { 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 } }
.mot.actif { color:#FFE45E; }
mots.forEach(m => { const el = creer(m.texte); el.style.animationDelay = `${m.startMs}ms`; piste.append(el); });

Typographie lisible et placement dans la zone de sécurité (9:16, 1080×1920)

PropriétéValeurPourquoi
PoliceGras/Extra-Gras sans-serif (Montserrat, Inter, Helvetica)Lisible sur les petits écrans chargés
Taille56–80 px (≈8 % de la hauteur du cadre), min 45 pxLisible en mode silencieux sur un téléphone
Contour2–6 px noir uniRésiste à tout arrière-plan
OmbreOmbre portée douce en complément du contourSéparation sur les plans lumineux
CasseMajuscules ou phrase ; remplissage à fort contrasteImpact + scannabilité
Position verticaleBande centrale, ~62–70 % vers le basAu-dessus de l'interface, en dessous de l'action
Sécurité basseRester libre en bas ~280 px / 15 %Évite l'interface des sous-titres/CTA/audio
Sécurité latéraleRester dans les 80 % de largeur centrauxÉvite les icônes du rail droit

Placez les sous-titres dans la bande centrale — pas en bas, là où se trouve l'interface de la plateforme (nom d'utilisateur, audio, boutons). Limitez le texte à 2 lignes maximum.

Synchronisation avec une voix off / narration

Le script contient déjà le timing exact de l'audio. Placez ce même audio sous la composition et conservez les horodatages dans la base de temps audio — les sous-titres et la voix restent verrouillés sans aucun ajustement manuel. Si la VO est réenregistrée, retranscrivez ; ne décalez jamais manuellement les décalages.

Incrustation vs fichier SRT/VTT séparé

Incrustation (ouvert)SRT/VTT séparé (fermé)
OùPixels dans le MP4Fichier .srt/.vtt séparé
Social (TikTok/Reels)Requis — garanti, stylisableSouvent ignoré par la plateforme
Accessibilité (ADA/WCAG)Ne suffit pas seulRequis (commutable)
Meilleure pratiqueIncruster les sous-titres animés pour le socialFournir également un fichier séparé pour le web/SEO

Émettez les deux : incrustez les sous-titres animés pour le social, et écrivez un SRT/VTT simple à partir des mêmes jetons pour une lecture accessible/SEO.

Liste de vérification de la sortie

  • Horodatages par mot (réels, issus de la transcription) — pas de fausse division uniforme.
  • Pop-in par mot avec micro-dépassement ; une couleur d'accent pour le mot actif.
  • 1 à 4 mots par page ; jamais un mur de texte.
  • Gras sans-serif, 56–80 px, contour 2–6 px + ombre, contraste élevé.
  • Bande centrale, libre en bas ~280 px et des rails latéraux.
  • Audio placé sous la composition ; horodatages dans la base de temps audio.
  • Incrustation pour le social et fichier SRT/VTT séparé pour l'accessibilité.

Livrer et vérifier (images rendues → MP4)

Assistant packagé (scripts/): disposez vos images avec scripts/contact-sheet.sh sheet.png f-debut.png f-milieu.png f-fin.png, puis vérifiez l'encodage avec scripts/probe-mp4.sh out.mp4 [LxH] [fps]. Voir scripts/README.md.

Les sous-titres sont livrés sous forme de composition Remotion (<Composition> + schéma zod + defaultProps) — tout mouvement de mot est piloté par l'image via useCurrentFrame(), jamais Date.now() / Math.random() / minuteurs. Livrable = out/*.mp4 (incrusté) + le projet + le fichier SRT/VTT séparé. Le format vertical 9:16 (1080×1920) est le format par défaut.

Boucle de vérification — rendre des images → inspecter → encoder. Le timing des mots est ce qui casse ; vérifiez-le sur des images exactes avant d'encoder.

# Images au début / à une image de mot actif échantillonnée / à la fin — AVEC LES PROPRIÉTÉS FOURNIES (jetons réels + audio)
npx remotion still Captions out/f-debut.png --frame=0   --props='{"captionsSrc":"vo.json"}'
npx remotion still Captions out/f-milieu.png   --frame=90  --props='{"captionsSrc":"vo.json"}'
npx remotion still Captions out/f-fin.png   --frame=N   --props='{"captionsSrc":"vo.json"}'  # N = durationInFrames-1

# Inspectez chaque PNG :
#  - le mot mis en évidence à l'image 90 est le mot dont [startMs,endMs] contient 90/fps (aucun décalage)
#  - incrustation lisible : gras sans-serif, contour+ombre tient, pas de coupure
#  - zone de sécurité 9:16 : le sous-titre se trouve dans la bande centrale, libre du haut ~12 % et du bas ~20-35 % (interface sous-titres/CTA/audio) et du rail d'action droit

npx remotion render Captions out/sous-titres.mp4 --props='{"captionsSrc":"vo.json"}'   # encoder une fois les images correctes
npx remotion render Captions out/demo.gif --codec=gif                                # clip de démonstration README

Utilisez npx remotion compositions pour lire durationInFrames/fps et choisir les images du mot actif et de fin.

Avant de terminer :

  1. Les images se rendent proprement à l'image 0, à une image de mot actif intermédiaire et à la dernière — pas de police/audio manquant.
  2. Le mot correct est mis en évidence à l'image échantillonnée (image/fps se trouve dans son jeton) ; pas de fausse division uniforme.
  3. L'incrustation est lisible (contour+ombre) et le sous-titre est entièrement dans la zone de sécurité 9:16 à chaque image vérifiée.
  4. Piloté uniquement par l'image — pas de Date.now() / Math.random() / minuteurs.
  5. Les propriétés fournies (jetons réels, pas seulement defaultProps) s'affichent correctement ; MP4 + fichier SRT/VTT séparé émis, GIF optionnel.

Fichiers de référence

  • references/word-timed-captions.md — construction de bout en bout : transcription Whisper et le type Caption, un analyseur SRT complet, un composant Remotion avec mise en évidence du mot actif, pagination manuelle, un émetteur SRT/VTT, des cartes de zones de sécurité par plateforme et une fiche technique de typographie lisible.

Individual skills in this repo

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

Skills associés