Untertitel-Animation
Verwandle ein Transkript oder Voiceover in wortgenaue, scroll-stoppende Untertitel: Jedes Wort erscheint im Takt, das aktive Wort wird hervorgehoben, und der Text bleibt fest mit der Erzählung verbunden. Erstelle die Art von Untertiteln, die die Sehzeit auf TikTok, Reels und Shorts steigern – lesbar, im Takt und innerhalb des Sicherheitsbereichs.
Wann verwenden
- Wort-für-Wort- oder Karaoke-Untertitel in kurzen vertikalen Videos (9:16).
- Einbrennen von offenen Untertiteln auf einer Voiceover-/Erzählspur aus einem Transkript.
- Umwandlung eines Whisper/SRT-Transkripts in animierten Text in Remotion oder im Web.
- Neugestaltung automatisch generierter Untertitel in eine markenbezogene „Hormozi-Stil“-Einblendung.
Der Workflow
| Schritt | Aufgabe | Werkzeug |
|---|---|---|
| Transkribieren | Audio → Wort-Zeitstempel | Whisper (@remotion/install-whisper-cpp), AssemblyAI |
| Normalisieren | → { text, startMs, endMs }[] Token | @remotion/captions Caption-Typ |
| Seitieren | Wörter in 1–4-Wort-„Seiten“ gruppieren | createTikTokStyleCaptions() |
| Animieren | Einblendung pro Wort + Hervorhebung des aktiven Wortes | Remotion spring() / CSS |
| Platzieren | Sicherer Bereich, lesbare Schriftart | Layout-Regeln unten |
| Exportieren | Einbrennen (MP4) und/oder Sidecar SRT/VTT | Remotion-Rendering / Datei-Export |
Zwei unverhandelbare Regeln
- Wortgenaue Zeitsteuerung, nicht auf Zeilenebene. Karaoke wirkt nur dann magisch, wenn jedes Wort auf die Silbe trifft. Transkribiere immer mit Wortzeitstempeln; teile niemals eine Zeile gleichmäßig über ihre Dauer auf – eine Abweichung ist sofort sichtbar.
- Lesbarkeit vor Stil. Untertitel werden auf einem Telefon, bei Sonnenlicht und stummgeschaltet gelesen. Kräftige serifenlose Schrift, starker Rand oder Schlagschatten, hoher Kontrast zuerst; Dekoration kommt danach. Ein Untertitel, den niemand lesen kann, ist Dekoration, kein Untertitel.
Wortzeitsteuerung aus einem Transkript
Whisper liefert direkt Wortzeitstempel. Normalisiere jede Quelle in eine einheitliche Token-Form, damit der Renderer nie wissen muss, woher die Wörter kommen:
// Untertitel-Token – die eine Form, die alles stromabwärts konsumiert
type Token = { text: string; startMs: number; endMs: number };
// Einen SRT-Cue-Block "00:00:01,200 --> 00:00:01,640" in ms parsen
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 Cues auf Satzebene existieren, transkribiere neu für Wortzeitsteuerung – interpoliere nicht. Whisper medium.en ist ~2x schneller als large und genau genug für saubere Sprachaufnahmen.
Einblendung pro Wort (Remotion)
Treibe Skalierung und Deckkraft jedes Wortes mit einer Feder, die an seinem eigenen startMs verankert ist. Das kleine Überschwingen lässt es „lebendig“ wirken.
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 Einblend-Frame des Wortes
const p = spring({ frame: frame - enter, fps, config: { damping: 12, mass: 0.6 } });
const scale = interpolate(p, [0, 1], [0.6, 1]); // Einblendung von 60% → 100% (überschwingt)
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 durch Testen von frame gegen die [startMs, endMs] jedes Tokens. Behalte die Hervorhebung als eine einzelne Akzentfarbe oder eine gefüllte „Box“ hinter dem aktuellen Wort bei – animiere niemals die Farbe jedes Wortes gleichzeitig.
Seiten, keine Textwände
Zeige 1–4 Wörter gleichzeitig an. Verwende Remotions createTikTokStyleCaptions() (oder gruppiere manuell) und stimme combineTokensWithinMilliseconds ab: ~200–500ms für echte Wort-für-Wort-Darstellung, ~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-Einblendung (ohne Remotion)
Für einen DOM/Live-Player gib jedem Wort seine 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 Schriftart und Platzierung im sicheren Bereich (9:16, 1080×1920)
| Eigenschaft | Wert | Grund |
|---|---|---|
| Schriftart | Fett/ExtraFett serifenlos (Montserrat, Inter, Helvetica) | Lesbar auf kleinen, überladenen Bildschirmen |
| Größe | 56–80px (≈8% der Bildhöhe), mindestens 45px | Lesbar bei Stummschaltung auf einem Telefon |
| Rand | 2–6px durchgezogener schwarzer Umriss | Übersteht jeden Hintergrund |
| Schlagschatten | Weicher Schlagschatten als Alternative zum Rand | Abhebung auf hellen Bildern |
| Groß-/Kleinschreibung | Großbuchstaben oder Satz; hoher Kontrast in der Füllung | Wirkung + Lesbarkeit |
| Vertikale Position | Mittlerer Bereich, ~62–70% von oben | Oberhalb der UI, unterhalb der Handlung |
| Unterer Sicherheitsabstand | Freihalten von den unteren ~280px / 15% | Vermeidung von Untertitel-/CTA/Audio-UI |
| Seitlicher Sicherheitsabstand | Innerhalb der mittleren 80% Breite | Vermeidung von rechten Symbolleisten |
Platziere Untertitel im mittleren Bereich – nicht ganz unten, wo die Plattform-UI (Benutzername, Audio, Buttons) ist. Begrenze den Text auf maximal ~2 Zeilen.
Synchronisation mit einem Voiceover / einer Erzählung
Das Transkript trägt bereits die Zeitsteuerung des exakten Audios. Lege dasselbe Audio unter die Komposition und behalte die Zeitstempel in der Zeitbasis des Audios – Untertitel und Stimme bleiben ohne manuelles Nachjustieren synchron. Wenn der Voiceover neu aufgenommen wird, transkribiere neu; verschiebe niemals Offsets von Hand.
Einbrennen vs. Sidecar SRT/VTT
| Einbrennen (offen) | Sidecar SRT/VTT (geschlossen) | |
|---|---|---|
| Wo | Pixel im MP4 | Separate .srt/.vtt-Datei |
| Social Media (TikTok/Reels) | Erforderlich – garantiert, stylbar | Wird von der Plattform oft ignoriert |
| Barrierefreiheit (ADA/WCAG) | Erfüllt die Anforderungen allein nicht | Erforderlich (umschaltbar) |
| Best Practice | Animierte Untertitel für Social Media einbrennen | Auch eine Sidecar-Datei für Web/SEO ausliefern |
Gib beides aus: Brenne die animierten Untertitel für Social Media ein und schreibe eine einfache SRT/VTT-Datei aus denselben Token für barrierefreie/SEO-Wiedergabe.
Checkliste für die Ausgabe
- Wortzeitstempel (echt, aus der Transkription) – keine gleichmäßig aufgeteilte Fälschung.
- Einblendung pro Wort mit leichtem Überschwingen; eine Akzentfarbe für das aktive Wort.
- 1–4 Wörter pro Seite; niemals eine Textwand.
- Fette serifenlose Schrift, 56–80px, 2–6px Rand + Schatten, hoher Kontrast.
- Mittlerer Bereich, freigehalten von unteren ~280px und seitlichen Rändern.
- Audio unter der Komposition angelegt; Zeitstempel in der Audio-Zeitbasis.
- Einbrennen für Social Media und eine Sidecar-SRT/VTT für Barrierefreiheit ausliefern.
Ausliefern und überprüfen (gerenderte Standbilder → MP4)
Verpacktes Hilfsskript (
scripts/): Ordne deine Standbilder mitscripts/contact-sheet.sh sheet.png f-hook.png f-mid.png f-end.pngan und ü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 framegetrieben durch useCurrentFrame(), niemals Date.now() / Math.random() / Timer. Liefergegenstand = out/*.mp4 (eingebrannt) + das Projekt + die Sidecar-SRT/VTT. 9:16 vertikal (1080×1920) ist der Standard.
Überprüfungsschleife – Standbilder rendern → prüfen → kodieren. Die Wort-Zeitsteuerung ist das, was kaputtgeht; überprüfe sie an genauen Frames, bevor du eine Kodierung startest.
# Standbilder am Anfang / einem Beispiel-Frame mit aktivem Wort / Ende – MIT AUSGELIEFERTEN PROPS (echte Token + 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
# Jedes PNG überprüfen:
# - Das bei Frame 90 hervorgehobene Wort ist das Wort, dessen [startMs,endMs] 90/fps enthält (keine Abweichung)
# - Eingebrannt lesbar: fette serifenlose Schrift, Rand+Schatten hält, kein Beschneiden
# - 9:16-Sicherheitsbereich: Untertitel sitzt im mittleren Bereich, frei von den oberen ~12 % und den unteren ~20–35 % (Untertitel/CTA/Audio-UI) und der rechten Aktionsleiste
npx remotion render Captions out/captions.mp4 --props='{"captionsSrc":"vo.json"}' # kodieren, sobald die Standbilder stimmen
npx remotion render Captions out/demo.gif --codec=gif # README-Nachweis-Clip
Verwende npx remotion compositions, um durationInFrames/fps auszulesen und die Frames mit aktivem Wort sowie den Endframe auszuwählen.
Bevor du abschließt:
- Standbilder werden sauber bei Frame 0, einem mittleren Frame mit aktivem Wort und dem letzten Frame gerendert – keine fehlende Schriftart/fehlendes Audio.
- Das richtige Wort wird beim abgetasteten Frame hervorgehoben (Frame/fps liegt innerhalb seines Tokens); keine gleichmäßig aufgeteilte Fälschung.
- Die Einbrennung ist lesbar (Rand+Schatten) und der Untertitel befindet sich bei jedem überprüften Frame vollständig im 9:16-Sicherheitsbereich.
- Nur framegetrieben – kein
Date.now()/Math.random()/ Timer. - Ausgelieferte Props (echte Token, nicht nur
defaultProps) werden korrekt gerendert; MP4 + Sidecar-SRT/VTT ausgegeben, GIF optional.
Referenzdateien
references/word-timed-captions.md– End-to-End-Aufbau: Whisper-Transkription und derCaption-Typ, ein vollständiger SRT-Parser, Remotion-Komponente mit Wortzeitsteuerung und aktiver Hervorhebung, manuelle Seitierung, ein SRT/VTT-Export, plattformspezifische Safe-Area-Karten und ein Spezifikationsblatt für lesbare Schriftarten.