How word-accurate captions actually work

· 8 min read · ReadAnyText

Three ways to time a subtitle, why only one of them is exact, and the millisecond bug that made our own files technically malformed.

Every subtitle file is a list of guesses about when words were said. Usually. There is one case where the timings are not guesses at all, and it is worth understanding because it changes the order you do the work in.

Three ways to time a subtitle

1. Estimate from the text

Count the words, assume a reading speed, divide. This is what most free subtitle generators do when you hand them a script with no audio.

It drifts immediately. Real speech is not evenly paced: a sentence with a date in it takes far longer to say than its character count suggests, and a list of short words goes faster. Within a couple of paragraphs the captions are visibly ahead or behind, and someone has to fix every cue by hand.

2. Transcribe the audio afterwards

Record first, then run a speech recogniser over the result and take its word boundaries. This is what most paid tools do, and it works well.

It has two costs. It needs a second processing pass over the whole file, and it inherits every mistake the recogniser makes. The words it is most likely to get wrong are proper nouns, numbers and technical terms — which are exactly the words a viewer most needs to read correctly, because those are the ones they could not catch by ear either.

3. Ask the thing that is speaking

If the audio is being synthesized rather than recorded, there is a third option that neither of the others can match: the speech engine knows when it started each word, because it is the thing saying them.

Nothing is estimated. Nothing is recognised. The engine emits a word boundary event as it goes, and those events are the timings. A name it pronounces oddly is still timed perfectly, because timing and pronunciation are separate problems.

ApproachWhat you actually get
Estimated from textDrifts within a few sentences. Every cue needs hand-correcting.
Transcribed from audioGood, but a second pass, and it inherits the recogniser's errors on exactly the hard words.
Reported by the synthesizerExact, free, and available the moment the audio exists.

What that looks like in practice

Here is the opening of one of our published examples, with the caption file generated alongside it in the same render:

1
00:00:00,100 --> 00:00:04,725
Every word in this recording has a timestamp,
captured by the speech engine at the

2
00:00:04,725 --> 00:00:05,925
moment it was spoken.

Those numbers came out of the engine, not out of an estimate. You can download that exact MP3 and its SRT from the examples page and check them against each other.

Grouping words into cues is the part that needs opinions

Exact word timings are raw material, not subtitles. A caption that changed on every word would be unreadable. The rules that turn one into the other are conventional broadcast ones, and they are worth stating because most tools hide them:

RuleValue
Maximum line lengthAbout 42 characters
Maximum lines per cueTwo
Maximum time on screenSix seconds
Minimum time on screen1.2 seconds, stretched if there is room
Forced breakAt the end of a sentence, or after a silence of 0.7 seconds or more

The sentence-end rule matters more than the length ones. A cue that breaks mid-clause and finishes in the next card reads badly even when every timing is perfect.

The millisecond that made our files malformed

While preparing the examples page, we looked at a generated SRT closely enough to notice this:

1
00:00:00,100 --> 00:00:04,726   <- ends here
2
00:00:04,725 --> 00:00:05,925   <- starts one millisecond earlier

Cue one ended after cue two began. One millisecond of overlap, invisible to a viewer, and technically malformed: strict SRT parsers reject overlapping cues outright, and players that honour both timestamps can flicker between them.

The cause was the thing that makes these timings good in the first place. A cue ends when its last word ends, and the engine’s reported word durations can run a millisecond or two past the start of the next word — real speech overlaps slightly, and the engine reports it faithfully. Faithful reporting plus a naive cue boundary equals an overlap.

The fix is one clamp, applied after the cues are built: no cue may end later than the next one starts. It is three lines, and it had been affecting every subtitle file the product exported.

We mention it because it is the kind of defect that never shows up in review. The captions looked right, played right, and passed every by-eye check — the file was simply, quietly, invalid.

A workflow that saves an editing pass

If your narration has not been recorded yet, the usual order is backwards. Instead:

  1. Write the script first, with pauses where you want the visuals to breathe.
  2. Generate once. You get the audio, the SRT, the VTT and a plain transcript together.
  3. Cut to the audio. The narration is already timed, so the edit follows it rather than the other way round.
  4. Drop the SRT on last. No re-syncing, because the captions and the audio came out of the same render.

One deliberate exception

Pronunciation fixes do not change the subtitles. If you respell a name so the voice says it correctly, the caption still shows the real spelling — the viewer reads the word as written and hears it said properly. Letting a phonetic respelling leak into the caption would be an easy bug to ship and a strange thing for a reader to encounter.

You can try the whole thing at text to SRT, or hear the example above on the examples page.