Communitygithub.com

vidmoat/caption-styling

Stile, Zeitsteuerung und Platzierung von Untertiteln für Lesbarkeit auf dem Smartphone bei ausgeschaltetem Ton. Setzt Zeilenlängen, Lesegeschwindigkeit, Timing, Größe, Kontrast und sichere Bereiche für Plattformen und prüft SRT- oder VTT-Dateien darauf.

Was ist caption-styling?

Diese Fähigkeit optimiert Untertitel und Bildunterschriften für die Anzeige auf Mobilgeräten ohne Ton. Sie wendet bewährte Standards an: maximal zwei Zeilen pro Einblendung, eine Lesegeschwindigkeit von höchstens 17 Zeichen pro Sekunde (entspricht 160–180 Wörtern pro Minute) und eine minimale Anzeigedauer von einer Sekunde. Die Positionierung erfolgt im unteren, mittleren, sicheren Bereich (oberhalb von 420 px auf 1080x1920), um Überschneidungen mit App-Buttons von TikTok oder Reels zu vermeiden. Die Schriftgröße wird je nach Format (Hoch- oder Querformat) und Stil (Satzuntertitel vs. kurze, poppige Einblendungen) angepasst. Ein integriertes Prüfskript (`scripts/check_captions.py`) überprüft automatisch Zeilenanzahl, -länge, Lesegeschwindigkeit, Dauer und Überlappungen. Die Fähigkeit unterstützt die Arbeit in Editoren oder per ffmpeg und erzeugt sowohl eingebrannte Versionen (für soziale Medien) als auch Sidecar-Dateien im SRT- oder VTT-Format (für Barrierefreiheit und Plattform-Upload). Besonderes Augenmerk liegt auf realen Kontrollen mittels gerenderter Frames, da Breitenschätzung bei schmalen Schriftarten oft ungenau ist.

Funktioniert mit~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/vidmoat/skills/tree/HEAD/skills/core/caption-styling

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

Caption styling

A caption is read in about two seconds, on a phone, with the sound off, over moving footage, under app buttons. Every default below serves that reader. Work in the user's editor if they have one; the numbers are the same everywhere. When there is no editor, use the ffmpeg recipes in references/ffmpeg-burn-in.md.

Defaults

Use these unless the user or their style guide says otherwise.

SettingDefaultWhy
Lines per cue2 at mostA third line covers the subject and is not read in time.
Characters per line42 on landscape 16:9; on vertical 9:16, whatever fits the safe width at your size: about 32 at 64 px, 28 at 72 px (mixed case)Longer lines force eye travel, and on vertical they run off the frame.
Reading speed17 characters per second at most (about 160 to 180 words a minute)Broadcast practice; WCAG sets no speed, so this is the working standard.
Cue duration1 s minimum, 6 s maximumUnder 1 s is a flicker; over 6 s viewers re-read.
Gap between cues0 or at least 2 framesA 1-frame gap reads as a flash.
PositionBottom centre, inside the safe zone; on 1080x1920, text stays above the bottom 420 px (22% of height)That clears both TikTok and Reels. Move to the top when the lower third holds a face, burned-in text or app UI.
Size, sentence subtitlesLandscape: 4.5 to 5% of frame height (49 to 54 px at 1080p). Vertical: 64 to 72 px on 1080x1920Readable at arm's length, and the line lengths above still fit the 900 px safe width.
Size, punchy short-form captions (1 to 3 words)About 140 to 150 px on 1080x1920, at most about 14 characters per line in mixed case, 11 in capitalsThese are graphic elements, not subtitles.

| Colour | Off-white #F2F2F2 on a 70 to 80% opaque dark plate, or a 3 to 5 px dark outline plus soft shadow | Contrast against moving footage cannot be measured frame by frame; a plate guarantees WCAG 4.5:1. | | Fonts | One heavy sans-serif (bold or semibold); never more than two families in a video | Thin weights vanish after compression. | | Word highlight (karaoke) | One accent colour on the active word only, and it must clear 4.5:1 too | A second colour for "emphasis" makes viewers hunt for meaning. |

Fit check before rendering. A bold Arial-class sans in mixed case averages 0.44 of the font size per character (measured, spaces included); all caps averages 0.55; condensed faces about 0.35. So a line is roughly characters x factor x font_px wide, and it must fit the safe width (900 px on 1080x1920, 90% of the width on landscape), not the frame. At 96 px, a 27-character mixed-case line is about 27 x 0.44 x 96 = 1,140 px: wider than the whole 1080 px frame. The factors are averages for the faces they were measured on (see Gotchas), so confirm with a rendered frame.

Platform pixel safe zones and how to derive the safe box for any canvas are in references/platform-safe-zones.md. Read it before placing captions on vertical video.

Workflow

  1. Start from real timings. Captions need a transcript with segment or word times. With no transcript there are no captions to make: say so, or transcribe first. Never invent wording.

  2. Rebase times to the edit. Transcripts are timed against the source file. After trimming, cutting or changing speed, shift and scale each cue to the timeline (see Gotchas).

  3. Segment into cues. Break at clause boundaries: after punctuation, before conjunctions and prepositions. Never split an article from its noun, a first name from a surname, or a number from its unit. Of two possible breaks, take the one that makes the two lines closest in length.

  4. Enforce reading speed. A cue over 17 characters per second gets split or extended into the silence after it. Never shrink the type to fit.

  5. Mark speakers and sound. On a speaker change start the line with - (or name them in brackets the first time). Put meaningful non-speech in square brackets: [door slams], [tense music]. Do not caption filler words that were cut from the audio.

  6. Style and place using the defaults and the safe box for this canvas.

  7. Check the file, then fix every error it reports:

    python scripts/check_captions.py captions.srt --fps 30 --max-chars 32
    

    It checks line count, line length, characters per second, duration, gaps and overlaps, and exits non-zero on errors. Add --json for a machine report. Its default line limit is 32 characters, the vertical and accessibility figure; pass --max-chars 42 for landscape subtitles.

  8. Look at rendered frames, not settings: the first cue, the longest cue, and a cue over the busiest background. Confirm nothing is clipped, nothing sits under app UI, and the text is readable at phone size.

  9. Deliver both a burned-in version for social and a sidecar .srt or .vtt for accessibility and platform upload, unless the user asked for one.

Gotchas

  • Width estimates lie on condensed faces. Average-glyph width estimates have been off by more than 3x: one overflow check reported a line running 127 px off each side when it actually rendered 270 px wide in a 1080 px frame. Shrinking type to satisfy that estimate produced 76 px captions on 1080x1920 where the punchy style called for about 150 px, far too small to read on a phone. Render a frame and measure.
  • Transcript times are source-relative. If a clip starts 12.4 s into its source, subtract 12.4 s from every cue and add the clip's timeline start. At 2x speed, durations halve, so cues can fall under 1 s; re-segment instead of keeping the old cue boundaries.
  • ffmpeg burns SRT on a 288-line grid, not in pixels. With subtitles=file.srt:force_style=..., FontSize and MarginV are measured against a 384x288 script, so FontSize=24 renders as a 90 px font on 1080-line video and 160 px on 1920-line video. Convert with FontSize = px * 288 / frame_height, or write an ASS file whose PlayResX and PlayResY equal the video size so units are pixels (the recipe does).
  • A plate in ASS takes OutlineColour. With BorderStyle=3 the box colour comes from OutlineColour, not BackColour, and ASS colours are &HAABBGGRR where alpha 00 is opaque. &H40000000 is black at 75% opacity.
  • Inline colour changes break an ASS plate. A {\c...} highlight inside a BorderStyle=3 line draws a separate box per colour run, and the half-transparent boxes overlap into dark seams. Draw the plate as its own layer with invisible text and put the visible text on a layer above (the recipe shows how).
  • Missing fonts fail silently. libass falls back to another font without an error. Check the fontselect: line in the ffmpeg log, or pass fontsdir=.
  • Windows paths break the subtitles filter. Escape the drive colon: subtitles='C\:/clips/subs.srt'.
  • Sung lyrics are phrase-timed. Speech recognition under music drops words (one test lost 4 of 17) and returns phrase times, not word times. Do not build word-by-word karaoke from it without checking against the audio.
  • A preset can colour the active word in more than one place. One editor's neon preset drew the highlight from both a highlight colour and a glow, so overriding only a field that does not exist changed nothing, with no error. Override every field that carries the colour, by its real name, and confirm on a rendered frame.

Individual skills in this repo

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

Verwandte Skills