Documentation

Cue algorithms

Transcription hands back words and timestamps. Deciding where a cue begins, where its line breaks, and how long it stays on screen is a separate problem — and the one that decides whether subtitles are readable. subkit ships two answers.

Choosing one

Select with --subtitle-algorithm deepgram|netflix on subtitle, generate, cues and render. Here is the same audio, same transcript, both ways:

--subtitle-algorithm deepgram
1
00:00:12,040 --> 00:00:14,180
[speaker 0]
Nobody tells you that the hard part of

2
00:00:14,180 --> 00:00:17,640
subtitling isn't the words. It's deciding where to

3
00:00:17,640 --> 00:00:18,880
cut them.
--subtitle-algorithm netflix
1
00:00:12,040 --> 00:00:15,900
Nobody tells you that the hard part
of subtitling isn't the words.

2
00:00:16,400 --> 00:00:18,880
It's deciding where to cut them.
PickWhen
deepgramYou want cues that mirror the provider's own captioning output exactly, keep speaker labels, and never invent timing. Good for review, search and diffing against Deepgram's captions.
netflixAnyone is going to read these. Reading speed, line length and event duration are constrained, and events are cut on sentence boundaries.

deepgram

The default. A port of deepgram-go-captions, kept faithful to it:

  • Utterances — or the flat word list, when utterances are off — are chunked into fixed-size groups of 8 words (--subtitle-max-words).
  • On the flat word path, cues also split on speaker changes when diarization is on.
  • Cue timing is exactly the first word's start to the last word's end. Nothing is padded, extended or rounded.
  • Speaker labels are emitted when the speaker changes between cues — [speaker 0] on its own line in SRT, <v Speaker 0> inline in WebVTT.
Fixed-size chunking is why the example above breaks after “the hard part of”. It is mechanically correct and it matches the reference implementation; it is not trying to be readable.

netflix

Follows the Netflix Timed Text Style Guide — the general requirements, the subtitle timing guidelines and the subtitle template guides:

RuleValue
Max chars per line42, for Latin scripts
Max lines2, favouring bottom-heavy splits
Min duration5⁄6 second per event
Max duration7 seconds per event
Reading speed17 characters per second (adult templates)
Event gapsat least 2 frames at 24fps; gaps under 0.5s are closed to 2 frames
Out-timesextended roughly 0.5s past the audio when the next event allows
Speaker labelsnone — the style guide identifies speakers with dialogue punctuation, not labels

Segmentation

Events are segmented sentence-first:

  • A cue never spans a speaker change, or a silence longer than --subtitle-max-gap (default 1 second).
  • Whole sentences are packed into an event while they still fit the character, line and duration budget.
  • A sentence too large for one event is split at the best linguistic break point: after punctuation, before conjunctions and prepositions, and never immediately after an article.

That is the difference visible in the comparison above — the netflix column cuts between two sentences, not between “part” and “of”.

Line breaks

Breaks inside a cue use the same scoring as segmentation, with function-word lists for English and Portuguese. Other languages fall back to punctuation and line balance, which is weaker but never wrong in a way that changes meaning.

bash
subkit subtitle movie.mp4 --subtitle-algorithm netflix --language pt-PT

Overrides

Every numeric subtitle flag defaults to 0, which means use the selected algorithm's own default. Set only what you want to change:

bash
# netflix rules, but a slower reading speed and shorter lines
subkit subtitle movie.mp4 \
  --subtitle-algorithm netflix \
  --subtitle-reading-speed 13 \
  --subtitle-max-chars 37

On generate and render, individual subtitle outputs can pick their own algorithm — useful when you want a readable SRT for viewers and a faithful VTT for tooling in the same run:

bash
subkit generate movie.mp4 \
  --output subtitle:srt:algorithm=netflix \
  --output subtitle:vtt:algorithm=deepgram
Changing a subtitle flag only invalidates the cue and render stages. The transcription is reused, so re-cutting cues costs no provider call — see caching.

Out of scope

Frame-based rules assume 24fps. Shot-change alignment is deliberately out of scope: cue generation works from the transcript and never inspects video frames, so it cannot know where a cut lands. If you need cues snapped to shot changes, that is a step after subkit, not inside it.