Communitygithub.com

Kreesten-hsh/video

Cette compétence doit être utilisée lorsque l'utilisateur demande "ajouter des sous-titres animés", "faire des sous-titres TikTok", "sous-titres karaoké", "sous-titres mot par mot", "sous-titres automatiques", "mettre en surbrillance le mot actif", "graver des sous-titres dans une vidéo", "synchroniser les sous-titres avec une voix off/narration" ou "transformer un fichier SRT/transcription en texte animé". Couvre le timing au niveau du mot depuis Whisper, l'apparition échelle/scale-in par mot, la mise en surbrillance du mot actif, le placement dans la zone de sécurité 9:16, la typographie lisible, et le burn-in par rapport au fichier SRT/VTT annexe.

Qu'est-ce que video ?

Cette compétence permet de transformer une transcription ou une voix off en sous-titres animés synchronisés mot par mot, idéaux pour les vidéos courtes au format vertical (9:16) sur TikTok, Reels ou Shorts. Elle s'appuie sur un pipeline complet : transcription audio avec Whisper (timings mot par mot), normalisation des tokens, regroupement en pages de 1 à 4 mots, animation avec effet d'échelle (pop) et surbrillance du mot actif, puis placement dans la zone de sécurité tout en garantissant une typographie lisible (police sans sérif en gras, contour ombré). La compétence prend en charge à la fois le burn-in (sous-titres incrustés dans la vidéo MP4) et la génération de fichiers SRT/VTT annexes pour l'accessibilité. Elle propose une implémentation pour Remotion (avec spring() et useCurrentFrame()) ainsi qu'une version web/CSS (avec animation-delay). Une checklist de vérification (rendu d'images clés, inspection des sous-titres, encodage MP4) assure la qualité finale. Les fichiers de référence fournis détaillent la construction complète, du parsing SRT à l'émetteur SRT/VTT, en passant par les cartes de zones de sécurité par plateforme.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/Kreesten-hsh/video/tree/HEAD/.agents/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 une transcription ou une voix off en sous-titres synchronisés mot par mot qui captent l'attention : chaque mot apparaît sur la syllabe, le mot actif est mis en surbrillance, et le texte reste collé à la narration. Créez le genre de sous-titres qui augmentent le temps de visionnage sur TikTok, Reels et Shorts — lisibles, en rythme, et à l'intérieur de la zone de sécurité.

Quand l'utiliser

  • Sous-titres mot par mot ou karaoké pour vidéo verticale courte (9:16).
  • Graver des sous-titres ouverts sur une piste de voix off/narration à partir d'une transcription.
  • Transformer une transcription Whisper/SRT en texte animé dans Remotion ou sur le web.
  • Restyler des sous-titres automatiques en un effet de révélation de marque de type "Hormozi".

Le pipeline

ÉtapeTravailOutil
TranscriptionAudio → horodatages mot par motWhisper (@remotion/install-whisper-cpp), AssemblyAI
Normalisation→ jetons { text, startMs, endMs }[]Type Caption de @remotion/captions
PageGrouper les mots en "pages" de 1 à 4 motscreateTikTokStyleCaptions()
AnimationApparition échelle par mot + surbrillance mot actifspring() Remotion / CSS
PlacementZone de sécurité, typographie lisiblerègles de mise en page ci-dessous
ExportBurn-in (MP4) et/ou SRT/VTT annexerendu Remotion / émission de fichier

Deux règles non-négociables

  1. Timing mot par mot, pas par ligne. Le karaoké n'est magique que lorsque chaque mot tombe sur la syllabe. Transcrivez toujours avec des horodatages mot par mot ; ne les simulez jamais en divisant uniformément une ligne sur sa durée — la dérive est immédiatement visible.
  2. La lisibilité prime sur le style. Les sous-titres se lisent sur un téléphone, en plein soleil, en mode muet. Police sans sérif en gras, contour ou ombre épais, contraste élevé d'abord ; la décoration ensuite. Un sous-titre que personne ne peut lire n'est pas un sous-titre, c'est de la décoration.

Timing mot par mot à partir d'une transcription

Whisper émet directement des horodatages mot par mot. Normalisez chaque source en une seule forme de jeton plate afin que le moteur de rendu n'ait jamais à se soucier de la provenance des mots :

// Jeton de sous-titre — la seule forme que tout l'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 phrase, retranscrivez pour obtenir un timing mot par mot — n'interpolez pas. Whisper medium.en est environ 2x plus rapide que large et assez précis pour un audio vocal propre.

Apparition échelle 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 cette sensation de "vie".

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;            // image d'entrée propre au mot
  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% (dépasse)
  return (
    <span style={{
      display: "inline-block",
      transform: `scale(${scale})`,
      opacity: interpolate(p, [0, 1], [0, 1]),
      color: active ? "#FFE45E" : "#FFFFFF",             // surbrillance mot actif
      transition: "color 80ms linear",
    }}>{token.text}&nbsp;</span>
  );
};

Calculez active en testant frame par rapport à [startMs, endMs] de chaque jeton. Gardez la surbrillance avec une seule couleur d'accent ou une "boîte" remplie derrière le mot en cours — n'animez jamais la couleur de chaque mot à la fois.

Pages, pas de murs de texte

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

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

Apparition Web/CSS (sans Remotion)

Pour un lecteur DOM/en direct, donnez à chaque mot son propre animation-delay égal à 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); });

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

PropriétéValeurPourquoi
PoliceSans sérif Gras/ExtraGras (Montserrat, Inter, Helvetica)Se lit sur des écrans petits et chargés
Taille56–80px (≈8% de la hauteur du cadre), min 45pxLisible en mode muet sur un téléphone
Contour2–6px de contour noir uniRésiste à tout arrière-plan
OmbreOmbre portée douce en complément du contourSéparation sur les images lumineuses
CasseMajuscules ou phrase ; remplissage à contraste élevéImpact + scannabilité
Position verticaleBande centrale, ~62–70% vers le basAu-dessus de l'interface, sous l'action
Sécurité basseRester hors des ~280px / 15% inférieursÉvite l'interface des sous-titres/CTA/audio
Sécurité latéraleRester dans les 80% centraux de la largeurÉvite les icônes latérales droites

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

Synchronisation avec une voix off / narration

La transcription porte déjà le timing de l'audio exact. Disposez ce même audio sous la composition et conservez les horodatages dans la base de temps de l'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 les décalages à la main.

Burn-in vs fichier SRT/VTT annexe

Burn-in (ouverts)SRT/VTT annexe (fermés)
OùPixels dans le MP4Fichier .srt/.vtt séparé
Social (TikTok/Reels)Requis — garanti, stylisableSouvent ignoré par la plateforme
Accessibilité (ADA/WCAG)Ne satisfait pas seulRequis (commutable)
Bonne pratiqueGraver les sous-titres animés pour le socialÉgalement fournir un fichier annexe pour le web/SEO

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

Liste de vérification de la sortie

  • Horodatages mot par mot (réels, issus de la transcription) — pas de simulation par division uniforme.
  • Apparition échelle par mot avec micro-dépassement ; une seule couleur d'accent pour le mot actif.
  • 1 à 4 mots par page ; jamais un mur de texte.
  • Sans sérif gras, 56–80px, contour de 2–6px + ombre, contraste élevé.
  • Bande centrale, hors des ~280px inférieurs et des rails latéraux.
  • Audio placé sous la composition ; horodatages dans la base de temps audio.
  • Fournir burn-in pour le social et un fichier SRT/VTT annexe pour l'accessibilité.

Livrer et vérifier (images rendues → MP4)

Assistant packagé (scripts/) : assemblez vos images avec scripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.png, puis vérifiez l'encodage avec scripts/probe-mp4.sh out.mp4 [LxH] [imgs/s]. Voir scripts/README.md.

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

Boucle de vérification — rendu d'images → inspection → encodage. Le timing des mots est ce qui casse ; vérifiez-le à des images précises avant de lancer un encodage.

# Images au début / une image échantillon de mot actif / fin — AVEC LES PROPS LIVRÉES (jetons réels + 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 = durationInFrames-1

# Inspectez chaque PNG :
#  - le mot en surbrillance à l'image 90 est le mot dont [startMs,endMs] contient 90/fps (pas de dérive)
#  - burn-in lisible : sans sérif gras, contour+ombre tient, pas de coupure
#  - zone de sécurité 9:16 : le sous-titre se trouve dans la bande centrale, hors des ~12% supérieurs et ~20-35% inférieurs (sous-titres/CTA/audio UI) et du rail d'action droit

npx remotion render Captions out/captions.mp4 --props='{"captionsSrc":"vo.json"}'   # encoder une fois que les images sont bonnes
npx remotion render Captions out/demo.gif --codec=gif                                # clip de preuve 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 intermédiaire de mot actif, et la dernière — pas de police/audio manquant.
  2. Le mot correct est en surbrillance à l'image échantillonnée (image/fps se trouve à l'intérieur de son jeton) ; pas de simulation par division uniforme.
  3. Le burn-in 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 images — pas de Date.now() / Math.random() / minuteurs.
  5. Les props livrées (jetons réels, pas seulement defaultProps) s'affichent correctement ; MP4 + fichier SRT/VTT annexe é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 timing mot par mot et surbrillance active, 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