Was macht Untertitel einbrennen?
Warum dieser Skill existiert
Es hat mehrere korrigierte Durchläufe an einem echten Projekt gebraucht, damit Untertitel richtig aussehen – jeder Durchlauf war es wert, nicht wieder von vorne anfangen zu müssen:
- Das systemeigene Homebrew-
ffmpeghat überhaupt keinendrawtext-/subtitles-Filter (nicht mit libass/freetype gebaut) – das Einbrennen echter gestalteter Untertitel erfordert einen anderen ffmpeg-Build, keine Notlösung. - Das Umbrechen von Untertitelzeilen nach Zeichenanzahl ergibt Unsinn – ein Zeilenumbruch muss dort erfolgen, wo eine Person beim Sprechen des Satzes natürlicherweise pausieren würde, niemals mitten in einer Phrase.
- Zeilen, die nahe am linken/rechten Bildrand liegen, können nach dem Posten durch Instagrams eigenes Zuschneiden abgeschnitten werden – echte Ränder sind erforderlich, nicht nur „passt ins Bild“.
- Schriftgröße und vertikale Position sind projektspezifisch und sollten bestätigt werden, aber vor einem vollständigen Rendering visuell überprüft werden – eine Einzelbildprüfung erkennt Überlauf/Beschnitt in Sekunden, ein vollständiges erneutes Rendern nicht.
- Untertitel dürfen sich nicht zwischen den Bildschirmen bewegen. Die erste Version verankerte sie am UNTEREN Rand (Alignment 2, MarginV 500), sodass ein 3-zeiliger Untertitel höher begann als ein 2- oder 1-zeiliger, und Untertitel, die sich mit dem vorherigen um einige Hundertstelsekunden überschnitten, wurden von libass' Kollisionsvermeidung für ihre gesamte Dauer nach oben verschoben – der Benutzer sah Titel „auf und ab wandern“. Feste Regel: Verankerung am OBENRAND (Alignment 8, MarginV = y der ersten Zeile; 1215 bei 1080x1920 hält die unterste Zeile eines 3-zeiligen Untertitels dort, wo der alte untere Anker sie hatte), sodass die erste Zeile jedes Untertitels an derselben Stelle sitzt und zusätzliche Zeilen nach unten wachsen. Zudem dürfen sich zwei Dialogue-Ereignisse zeitlich niemals überschneiden. Lasse außerdem einen kurzen LEEREN Moment zwischen aufeinanderfolgenden Untertiteln: Beende jedes Ereignis 0,12s (3 Bilder bei 25fps) bevor das nächste beginnt. Der Benutzer hat dies explizit gewünscht – die kurze Leerstelle signalisiert dem Gehirn, dass sich der Untertitel geändert hat; nahtlose Untertitel wirken wie flackernder Text. Verkürze das Ende des früheren Untertitels, verzögere niemals den Start des nächsten (dieser ist mit der Sprache synchronisiert).
- Der Untertiteltext muss keine wörtliche Abschrift sein – korrigiere Grammatik-/Wortwahlfehler (ein falsch verstandenes Wort, eine unbeholfene Konstruktion) und füge Wörter hinzu oder entferne sie, wie vom Benutzer gewünscht, genauso wie du es beim Korrekturlesen von Text tun würdest.
Schritt 1: Stelle sicher, dass ffmpeg Untertitel einbrennen kann
ffmpeg -hide_banner -filters 2>&1 | grep -E "subtitles|^\s*\S*\s+ass\b"
Wenn nichts gefunden wird, kann das reguläre ffmpeg dies nicht. Installiere den vollständigen Build:
brew install ffmpeg-full
ffmpeg-full ist keg-only (nicht in PATH verlinkt) – verwende es explizit für den Schritt zum Einbrennen von Untertiteln: /opt/homebrew/opt/ffmpeg-full/bin/ffmpeg.
Tatsächlich passierte Falle: Die Installation von ffmpeg-full zog ein neueres gemeinsames x265 und zerstörte das reguläre ffmpeg/ffprobe im PATH (dyld-Fehler „Library not loaded: libx265.*.dylib“), weil sie mit der älteren Version gelinkt waren. Führe unmittelbar nach der Installation von ffmpeg-full Folgendes aus:
ffmpeg -version && ffprobe -version
Wenn einer der beiden einen Fehler meldet, behebe ihn mit brew reinstall ffmpeg (das reguläre Formel, nicht -full) und überprüfe beide Befehle erneut, bevor du fortfährst – gehe nicht davon aus, dass das reguläre ffmpeg die Installation unbeschadet überstanden hat.
Schritt 2: Erfasse den genauen Untertiteltext und Timing
Wenn das Video aus dem basic-Skill-Schneideworkflow stammt, kennst du bereits den exakten beibehaltenen Text pro Segment und seine ursprünglichen Quellzeitstempel – aber sobald Segmente geschnitten und verkettet sind, setzt sich das Timing auf die eigene Zeitleiste des neuen Videos zurück. Berechne die Position jedes Segments im ENDGÜLTIGEN Video, indem du die tatsächlichen gerenderten Clip-Dauern in der Reihenfolge summierst:
ffprobe -v error -show_entries format=duration -of csv=p=0 clip_01.mp4
... und akkumuliere. Innerhalb des Fensters jedes Segments setze leicht vom eigenen Start/Ende des Clips zurück, um zu passen, wo die Sprache tatsächlich beginnt/endet (wenn die Clips Vor-/Nachbearbeitung aus dem Schnittschritt haben, subtrahiere sie wieder heraus), damit Untertitel nicht in toter Luft erscheinen/verschwinden.
Schritt 3: Schreibe die ASS-Datei
Verwende diese als Ausgangsvorlage – es ist der genaue Stil, der an einem echten Projekt bestätigt wurde (Inter Bold, weißer Text, deckend schwarze Box, untere Mitte, großzügige Seitenränder):
[Script Info]
Title: <Video-Name> - titlovi
ScriptType: v4.00+
WrapStyle: 2
ScaledBorderAndShadow: yes
PlayResX: 1080
PlayResY: 1920
[V4+ Styles]
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
Style: Default,Inter,70,&H00FFFFFF,&H000000FF,&H00000000,&H00000000,-1,0,0,0,100,100,0,0,3,16,0,8,120,120,1215,1
[Events]
Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
Dialogue: 0,0:00:00.15,0:00:05.93,Default,,0,0,0,,Zamisli univerzum koji postoji\Ntoliko dugo da su sve zvezde\Nodavno ugašene
Lasse vor dem Einbrennen jedes Ereignis 0,12s vor dem nächsten enden (setze das Ende des früheren Ereignisses auf den nächsten Start - 0,12; verschiebe niemals einen Start) – siehe Lektion 5.
Passe PlayResX/PlayResY an die tatsächliche Videoauflösung an (dieses Beispiel ist ein vertikales 1080x1920). WrapStyle: 2 bedeutet, dass libass Text NICHT automatisch umbrechen wird – jeder Zeilenumbruch muss ein explizites \N im Textfeld sein. Dies ist beabsichtigt: Es zwingt dich, jeden Umbruch bewusst zu wählen (siehe Schritt 4), anstatt den Renderer raten zu lassen, und es fängt auch eine überlaufende Zeile als abgeschnittenen Rand auf, anstatt als stillschweigend hinzugefügte zusätzliche Zeile.
Schriftart/Größe/Farbe/Position sind alles Standardwerte, um zu STARTEN, keine festen Regeln – bestätige mit dem Benutzer, wenn dies ein neues Projekt ist, oder verwende seine zuletzt bestätigten Werte, wenn du sie bereits für diesen Benutzer kennst.
An einem echten Projekt bestätigte Zeichensetzungskonvention
Nachgestellte Punkte UND nachgestellte Kommas am Ende der LETZTEN Zeile eines Untertitelbildschirms werden weggelassen (Untertitel benötigen keinen Punkt, wie es Prosa tut – es wirkt bei dieser Größe als visuelles Rauschen). Dies gilt auch, wenn der Satz auf dem nächsten Untertitelbildschirm fortgesetzt wird: Der Bildschirmwechsel selbst wirkt bereits als Pause, daher wird „svoje znanje kroz praksu,“ als „svoje znanje kroz praksu“ angezeigt. Der Benutzer hat dies explizit gewünscht (2026-11-05, „isto kao za tačku“). Fragezeichen und Ausrufezeichen bleiben erhalten. Kommas INNERHALB eines Bildschirms (in der Mitte einer Zeile oder am Ende einer Zeile, die nicht die letzte Zeile des Bildschirms ist) bleiben erhalten. Wende dies standardmäßig auf jedes Video an, einschließlich der zweisprachigen Zeile in zweisprachigen Untertiteln und auf Untertiteltext, den der Benutzer im Plan-Editor bearbeitet.
Schritt 4: Brich jede Zeile dort um, wo eine Person tatsächlich beim Sprechen pausieren würde
Dies ist der Teil, der am lohnendsten richtig zu machen und am einfachsten fälschlich nachlässig zu erledigen ist. Lies den Satz, als würdest du ihn laut aussprechen, und brich an den natürlichen Atem-/Phrasengrenzen um – Satzgrenzen, nach einem vollständigen Gedanken, an Kommas, die eine echte Sprechpause darstellen. Brich niemals um, weil eine Zeile „kürzer sein muss“, wenn der natürliche Bruchpunkt woanders liegt – finde stattdessen einen KÜRZEREN natürlichen Bruch (es gibt fast immer mehr als einen gültigen Pausenpunkt in einem längeren Satz), erzwinge keinen unnatürlichen, nur um ein Breitenziel zu erreichen.
Konkretes Beispiel aus einer echten Korrektur – falsch (ausgeglichen nach Zeichenanzahl, bricht mitten in der Phrase):
Zamisli univerzum koji postoji toliko
dugo da su sve zvezde odavno ugasene
Richtig (bricht an natürlichen Sprechpausen):
Zamisli univerzum koji postoji
toliko dugo da su sve zvezde
odavno ugašene
1, 2 oder 3 Zeilen sind alle in Ordnung – es gibt keine festgelegte maximale Anzahl. Wichtig ist, dass jeder Bruch eine Stelle ist, an der ein Sprecher tatsächlich pausieren würde, und dass jede Zeile innerhalb der Ränder passt (nächster Schritt).
Schritt 5: Überprüfe die Breite an den TATSÄCHLICHEN Seitenrändern, nicht an der rohen Bildbreite
MarginL/MarginR im Stil (jeweils 120px bei einem 1080 breiten Bild in der obigen Vorlage – etwa 11% pro Seite) existieren, weil die Plattform, auf die das Video geht (Instagram usw.), nach dem Hochladen ihre eigene leichte Beschneidung anwendet, und Text, der zu nah am rohen Rand ist, dadurch abgeschnitten werden kann, nicht nur durch die Bildbegrenzung. Behandle PlayResX - MarginL - MarginR als die tatsächlich nutzbare Breite, wenn du beurteilst, ob eine Zeile zu lang ist, nicht die gesamte Bildbreite.
Da WrapStyle: 2 den automatischen Umbruch deaktiviert, wird eine tatsächlich zu breite Zeile nicht umbrechen – sie wird über den Rand hinauslaufen, was als abgeschnittener/abgeschnittener Text wirkt. Fange dies in Schritt 6, bevor es in einem vollständigen Rendering landet.
Schritt 6: Rendere Testbilder, bevor das gesamte Video gebrannt wird
Wähle einen Zeitstempel in jedem der 2-3 längsten/riskantesten Untertitel und rendere nur dieses Bild:
/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg -nostdin -y -i video.mp4 -ss <t> -update 1 \
-vf "subtitles=captions.ass" -frames:v 1 check.png -loglevel error
Überprüfe jedes auf: Text, der über die Ränder hinausläuft, mehr Zeilen als beabsichtigt (ein Zeichen dafür, dass eine Zeile breiter als erwartet ist und etwas upstream neu formuliert/neu gebrochen werden muss), und ob die vertikale Position tatsächlich als „untere Mitte“ und nicht zu nah am unteren Rand oder hoch genug, um das Gesicht zu bedecken, wirkt. Korrigiere und überprüfe erneut, bevor du fortfährst – diese Schleife ist schnell (einige Sekunden pro Bild) und fängt genau die Fehler, die ein vollständiges Rendering sonst Minuten zum Reproduzieren verschwenden würde.
Schritt 7: Brenne das endgültige Video
/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg -nostdin -y -i video.mp4 \
-vf "subtitles=captions.ass" -c:v libx264 -preset medium -crf 18 -pix_fmt yuv420p \
-c:a copy output_titlovi.mp4 -loglevel error
Der Ton bleibt unberührt (-c:a copy), da Untertitel nur Video sind. Wenn die Ausgabe durch ein Tool mit Dateigrößenbeschränkung muss, erstelle eine sekundäre komprimierte Kopie, anstatt das Master zu komprimieren – dasselbe Muster wie bei den basic- und zoom-Skills:
ffmpeg -nostdin -y -i output_titlovi.mp4 -c:v libx264 -preset slow -crf 24 -pix_fmt yuv420p -c:a aac -b:a 128k output_titlovi_share.mp4 -loglevel error
Iteration auf Feedback
Untertitelarbeit durchläuft tendenziell mehrere Runden kleiner, spezifischer Korrekturen (Schriftgröße, Position, ein falsches Wort, Zeichensetzung). Jede Runde sollte nur das berühren, was sich geändert hat – bearbeite die .ass-Datei direkt (es ist einfacher Text), führe die schnelle Bildprüfung aus Schritt 6 auf dem erneut aus, was betroffen war, und brenne dann mit Schritt 7 neu. Es besteht keine Notwendigkeit, die Schritte 1-2 zu wiederholen, es sei denn, das zugrunde liegende Video selbst hat sich geändert.
Korrektur falscher Wörter über das interaktive Tool
Der Benutzer zieht es vor, Untertiteltext selbst im selben Plan-Editor-Tool (plan-editor Skill – der Standardeditor des Benutzers) zu korrigieren: Mit dem Umschalter „Izmeni tekst“ wird jeder Satz direkt bearbeitbar. Immer wenn Untertitel vorhanden sind, zeige dieses Tool mit --ass captions.ass --durations ... (siehe plan-editor Skill) anstatt um Korrekturen in Prosa zu bitten – auch wenn nur die Untertiler überprüft werden müssen und kein Zoom geplant ist (dann starte einfach jeden Clip weit und lasse den Zoomplan unberührt).
Die Antwort enthält CAPTIONS_JSON: {"<Dialogue-Index>": "Zeile 1\nZeile 2"} mit nur den geänderten Bildschirmen. Wende es an:
python3 scripts/apply_caption_edits.py captions.ass caption_edits.json captions_fixed.ass
Es ändert nur den Text (Timing und Stil bleiben), indem es jede Zeile in einen expliziten \N-Umbruch verwandelt. Führe dann die Bildprüfung aus Schritt 6 auf den bearbeiteten Bildschirmen erneut aus (ein längeres Wort kann die Ränder überlaufen lassen) und brenne mit Schritt 7 neu.