cuenorm
Any subtitle format in, one normalised cue stream out.
Part of catena.
What it does
Reads a subtitle file — SubRip, WebVTT or SubStation Alpha — and writes one
cue record per line: a start, an
end, and what was said.
The format is worked out from the content, not from the name, because a subtitle file is named for the format somebody expected it to be in.
When you would use it
- At the front of anything that reads recordings. Everything downstream works on cues and never has to know which of the three formats a recording arrived with.
- Cleaning up automatic captioning. It repeats each line across overlapping windows, so the same sentence arrives three or four times over; indexed as they come, every one is a separate hit for the same words at almost the same timestamp, which buries everything else in a result list.
- Getting markup and sound cues out of the way.
<v Speaker>tags, styling,[Music],[Applause]— none of it is speech, and a search that matches[Music]matches every recording with a theme tune.
Install
cargo install --git https://git.pumpin.online/funkpower/catena cuenorm
Example
A WebVTT file with a speaker tag, a sound cue, and a repeated line:
$ cat episode.vtt
WEBVTT
00:00:01.000 --> 00:00:04.500
<v Host>The mill stood at the northern end of the valley,
00:00:04.500 --> 00:00:08.000
where the fall of the river was steepest and the ice came latest.
00:00:08.000 --> 00:00:11.000
[Music]
00:00:11.000 --> 00:00:15.500
Its wheel was undershot, which was wasteful,
00:00:15.000 --> 00:00:19.000
Its wheel was undershot, which was wasteful,
00:00:19.000 --> 00:00:24.000
and nobody replaced it for two hundred years.
$ cuenorm episode.vtt
{"v":1,"start":1.0,"end":4.5,"text":"The mill stood at the northern end of the valley,"}
{"v":1,"start":4.5,"end":8.0,"text":"where the fall of the river was steepest and the ice came latest."}
{"v":1,"start":11.0,"end":19.0,"text":"Its wheel was undershot, which was wasteful,"}
{"v":1,"start":19.0,"end":24.0,"text":"and nobody replaced it for two hundred years."}
The speaker tag is gone, [Music] left no cue at all, and the repeated line
became one cue spanning both — 11.0 to 19.0, the earliest start and the
latest end.
In a pipeline:
cuenorm episode.vtt | cuepack --seconds 75 > passages.ndjson
cuenorm episode.srt | cueseg --duration 7245 > chapters.ndjson
Options
| Option | Effect |
|---|---|
--keep-repeats |
Keep consecutive repeats instead of merging them |
--man |
Write this page as a manual page, in roff |
Exit codes
| Code | Meaning |
|---|---|
0 |
Done |
64 |
The command line was wrong |
74 |
The file could not be read |
Bytes that are not UTF-8 are replaced rather than refused. A subtitle file downloaded from anywhere is as likely to be Latin-1, and a cue with one wrong character still has its timing and most of its words.
The formats
| Format | Recognised by |
|---|---|
| WebVTT | The WEBVTT line at the front |
| SubStation Alpha | A [Script Info] or [Events] section |
| SubRip | Neither of the above |
SubStation Alpha events are rows under a Format: line that names the
columns, so the column order is read rather than assumed. Override blocks
({\an8}), line breaks (\N) and drawing commands are handled: a drawing
command is a shape, not speech, and reads as a string of coordinates if it is
kept.
A WebVTT cue line carries positioning settings after the end time, which are ignored — that is also what lets an old SubRip file with screen coordinates after its timestamps read at all.
As a library
let cues = cuekit::merge_repeats(&cuekit::parse(&content));
for cue in &cues {
println!("{:.1}–{:.1} {}", cue.start, cue.end, cue.text);
}
See cuekit for the parsers one at a time.