Was macht caption-animation?
Verwandle ein Transkript oder einen Voiceover in wortgenaue, aufmerksamkeitsstarke Untertitel: Jedes Wort erscheint silbensynchron, das aktive Wort wird hervorgehoben, und der Text bleibt fest mit der Erzählung verbunden. Erstelle die Art von Untertiteln, die die Verweildauer auf TikTok, Reels und Shorts steigern – lesbar, im Takt und im sicheren Bereich.
Wann verwenden
- Wort-für-Wort- oder Karaoke-Untertitel für kurze vertikale Videos (9:16).
- Einbrennen offener Untertitel auf einer Voiceover-/Narration-Spur von einem Transkript.
- Umwandlung eines Whisper-/SRT-Transkripts in animierten Text in Remotion oder im Web.
- Neugestaltung automatisch generierter Untertitel in eine markenkonforme „Hormozi“-Enthüllung.
Die Pipeline
| Schritt | Aufgabe | Werkzeug |
|---|---|---|
| Transkribieren | Audio → Wort-Zeitstempel | Whisper (@remotion/install-whisper-cpp), AssemblyAI |
| Normalisieren | → { text, startMs, endMs }[] Tokens | @remotion/captions Caption-Typ |
| Seite | Wörter in Gruppen von 1–4 Wörtern einteilen | createTikTokStyleCaptions() |
| Animieren | Wort-Erscheinen + Hervorhebung des aktiven Wortes | Remotion spring() / CSS |
| Platzieren | Sicherer Bereich, lesbare Schrift | Layout-Regeln unten |
| Exportieren | Einbrennen (MP4) und/oder separate SRT/VTT | Remotion-Rendering / Datei-Ausgabe |
Zwei unverhandelbare Regeln
- Wortgenaue Zeitsteuerung, nicht auf Zeilenebene. Karaoke wirkt nur dann magisch, wenn jedes Wort auf die Silbe fällt. Transkribiere immer mit Wort-Zeitstempeln; teile niemals eine Zeile gleichmäßig über ihre Dauer auf – Drift ist sofort sichtbar.
- Lesbarkeit vor Stil. Untertitel werden auf dem Handy, im Sonnenlicht und stumm gelesen. Fette serifenlose Schrift, starker Strich oder Schatten, hoher Kontrast zuerst; Dekoration ist zweitrangig. Ein Untertitel, den niemand lesen kann, ist Dekoration, kein Untertitel.
Wort-Zeitsteuerung aus einem Transkript
Whisper liefert direkt Wort-Zeitstempel. Normalisiere jede Quelle in ein einheitliches flaches Token-Format, damit der Renderer nie wissen muss, woher die Wörter kamen:
// Caption-Token – die eine Form, die alles nachgelagert konsumiert
type Token = { text: string; startMs: number; endMs: number };
// Parst einen SRT-Cue-Block "00:00:01,200 --> 00:00:01,640" in ms
const toMs = (t: string) => {
const [h, m, rest] = t.split(":");
const [s, ms] = rest.split(",");
return ((+h * 60 + +m) * 60 + +s) * 1000 + +ms;
};
Wenn nur Zeitstempel auf Satzebene existieren, transkribiere erneut, um Wort-Zeitstempel zu erhalten – interpoliere nicht. Whispers medium.en ist etwa 2× schneller als large und genau genug für saubere Sprachaufnahmen.
Wort-für-Wort-Erscheinen (Remotion)
Treibe die Skalierung und Deckkraft jedes Wortes über einen spring()-Wert an, der auf seinem eigenen startMs basiert. Der mikroskopische Überschwinger verleiht ihm das Gefühl von „Lebendigkeit“.
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; // eigener Eintrittsframe des Wortes
const p = spring({ frame: frame - enter, fps, config: { damping: 12, mass: 0.6 } });
const scale = interpolate(p, [0, 1], [0.6, 1]); // erscheint von 60% → 100% (mit Überschwinger)
return (
<span style={{
display: "inline-block",
transform: `scale(${scale})`,
opacity: interpolate(p, [0, 1], [0, 1]),
color: active ? "#FFE45E" : "#FFFFFF", // Hervorhebung des aktiven Wortes
transition: "color 80ms linear",
}}>{token.text} </span>
);
};
Berechne active, indem du frame gegen die [startMs, endMs] jedes Tokens prüfst. Behalte die Hervorhebung in einer einzigen Akzentfarbe oder als gefüllte „Box“ hinter dem aktiven Wort – animiere niemals die Farbe jedes Wortes gleichzeitig.
Seiten, keine Textwände
Zeige 1–4 Wörter gleichzeitig an. Verwende createTikTokStyleCaptions() von Remotion (oder gruppiere manuell) und passe combineTokensWithinMilliseconds an: ~200–500ms für wortgenaue Anzeige, ~1000–1500ms für kurze lesbare Phrasen.
import { createTikTokStyleCaptions } from "@remotion/captions";
const { pages } = createTikTokStyleCaptions({
captions, combineTokensWithinMilliseconds: 1200, // ~2–3 Wörter pro Seite
});
Web/CSS-Erscheinen (ohne Remotion)
Für einen DOM/Live-Player gib jedem Wort eine eigene animation-delay gleich 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); });
Lesbare Schrift und Platzierung im sicheren Bereich (9:16, 1080×1920)
| Eigenschaft | Wert | Warum |
|---|---|---|
| Schriftart | Fett/Extrafett serifenlos (Montserrat, Inter, Helvetica) | Lesbar auf kleinen, überladenen Bildschirmen |
| Größe | 56–80px (≈8% der Rahmenhöhe), min 45px | Lesbar bei Stummschaltung auf dem Handy |
| Strich | 2–6px schwarzer, durchgehender Rand | Überlebt jeden Hintergrund |
| Schatten | Weicher Schlagschatten als Backup zum Strich | Trennung bei hellen Rahmen |
| Groß-/Kleinschreibung | Großbuchstaben oder Satz; hoher Kontrastfüllung | Prägnanz + Lesbarkeit |
| Vertikale Position | Mittlerer Bereich, ~62–70% von oben | Über der UI, unter der Handlung |
| Unterer Sicherheitsabstand | Frei von unteren ~280px / 15% | Vermeidet Untertitel-/CTA-/Audio-UI |
| Seitlicher Sicherheitsabstand | Innerhalb der mittleren 80% Breite | Vermeidet Symbole in der rechten Leiste |
Platziere Untertitel im mittleren Bereich – nicht am unteren Rand, wo die Plattform-UI (Benutzername, Audio, Buttons) ist. Polstere den Text auf maximal ~2 Zeilen.
Synchronisation mit einem Voiceover / einer Narration
Das Transkript trägt bereits die Zeitsteuerung der exakten Audio. Lege dieselbe Audio unter die Komposition und behalte die Zeitstempel in der Zeitbasis der Audio – Untertitel und Stimme bleiben ohne manuelle Anpassung fest synchron. Wenn der VO neu aufgenommen wird, transkribiere erneut; verschiebe niemals Offsets von Hand.
Einbrennen vs. separate SRT/VTT
| Einbrennen (Open) | Separate SRT/VTT (Closed) | |
|---|---|---|
| Wo | Pixel in der MP4 | Separate .srt/.vtt-Datei |
| Social (TikTok/Reels) | Erforderlich – garantiert, gestaltbar | Wird oft von der Plattform ignoriert |
| Barrierefreiheit (ADA/WCAG) | Erfüllt allein nicht die Anforderungen | Erforderlich (umschaltbar) |
| Beste Praxis | Animierte Untertitel für Social einbrennen | Auch eine separate SRT/VTT für Web/SEO ausgeben |
Gib beides aus: Brenne die animierten Untertitel für Social ein und schreibe eine einfache SRT/VTT aus denselben Tokens für barrierefreie/SEO-Wiedergabe.
Checkliste für die Ausgabe
- Wort-Zeitstempel (echt, aus Transkription) – keine gleichmäßig aufgeteilte Fälschung.
- Wort-Erscheinen mit mikroskopischem Überschwinger; eine Akzentfarbe für das aktive Wort.
- 1–4 Wörter pro Seite; niemals eine Textwand.
- Fette serifenlose Schrift, 56–80px, 2–6px Strich + Schatten, hoher Kontrast.
- Mittlerer Bereich, frei von unten ~280px und den seitlichen Leisten.
- Audio unter der Komposition; Zeitstempel in der Zeitbasis der Audio.
- Einbrennen für Social und eine separate SRT/VTT für Barrierefreiheit ausliefern.
Ausliefern & verifizieren (gerenderte Standbilder → MP4)
Verpackter Helfer (
scripts/): erstelle eine Kontaktseite mitscripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.pngund überprüfe dann die Kodierung mitscripts/probe-mp4.sh out.mp4 [WxH] [fps]. Siehescripts/README.md.
Untertitel werden als Remotion-Komposition (<Composition> + zod schema + defaultProps) ausgeliefert – alle Wortbewegungen werden frame-gesteuert über useCurrentFrame(), niemals Date.now() / Math.random() / Timer. Lieferbar = out/*.mp4 (eingebrannt) + Projekt + separate SRT/VTT. 9:16 vertikal (1080×1920) ist die Voreinstellung.
Verifizierungsschleife – Standbilder rendern → prüfen → kodieren. Die Wort-Zeitsteuerung ist das, was kaputtgeht; überprüfe sie bei exakten Frames, bevor du eine Kodierung startest.
# Standbilder am Start / einem ausgewählten aktiven-Wort-Frame / Ende – MIT AUSGELIEFERTEN PROPS (echte Tokens + 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
# Überprüfe jedes PNG:
# - das bei Frame 90 hervorgehobene Wort ist dasjenige, dessen [startMs,endMs] 90/fps enthält (kein Drift)
# - Einbrennung lesbar: fette serifenlose Schrift, Strich+Schatten hält, kein Beschneiden
# - 9:16 sicherer Bereich: Untertitel sitzt im mittleren Bereich, frei von oben ~12% und unten ~20-35% (Untertitel/CTA/Audio-UI) und der rechten Aktionsleiste
npx remotion render Captions out/captions.mp4 --props='{"captionsSrc":"vo.json"}' # kodieren, sobald Standbilder stimmen
npx remotion render Captions out/demo.gif --codec=gif # README-Proof-Clip
Verwende npx remotion compositions um durationInFrames/fps auszulesen und die Frames für das aktive Wort und das Ende auszuwählen.
Bevor du abschließt:
- Standbilder werden sauber bei Frame 0, einem mittleren aktiven-Wort-Frame und dem letzten Frame gerendert – keine fehlende Schrift/Audio.
- Das richtige Wort wird beim überprüften Frame hervorgehoben (frame/fps liegt innerhalb seines Tokens); keine gleichmäßig aufgeteilte Fälschung.
- Einbrennung ist lesbar (Strich+Schatten) und der Untertitel liegt bei jedem überprüften Frame vollständig im 9:16-Sicherheitsbereich.
- Nur frame-gesteuert – kein
Date.now()/Math.random()/ Timer. - Ausgelieferte Props (echte Tokens, nicht nur
defaultProps) rendern korrekt; MP4 + separate SRT/VTT ausgegeben, GIF optional.
Referenzdateien
references/word-timed-captions.md– End-to-End-Build: Whisper-Transkription und derCaption-Typ, ein vollständiger SRT-Parser, Remotion-Komponente mit Wort-Zeitsteuerung und aktiver Hervorhebung, manuelle Seiteneinteilung, ein SRT/VTT-Emittent, plattformspezifische Karten des sicheren Bereichs und ein Datenblatt für lesbare Schrift.