cuepack
Cues into passages worth embedding, keeping the times they were said at.
Part of catena.
What it does
Packs consecutive cues into passages of about a minute of continuous speech,
and writes each one as a chunk
record carrying the start of its
first cue and the end of its last.
When you would use it
- Before embedding a transcript. A cue is a few seconds of speech and often half a sentence; an embedding of one says almost nothing, and a search over them returns fragments.
- When a result has to point somewhere. Because the passage keeps the first cue's start and the last cue's end, a hit can be cited to the second and a player can jump to it.
- To index recordings and books together. The record is the same one
chunkmdwrites, so one index answers across both and returns each with a location: a page number for one, a timestamp for the other.
Install
cargo install --git https://git.pumpin.online/funkpower/catena cuepack
Example
$ cuenorm episode.vtt | cuepack --seconds 10
{"v":1,"idx":0,"text":"The mill stood at the northern end of the valley, where the fall of the river was steepest and the ice came latest.","tokens":27,"page":null,"chapter":null,"role":"body","start_time":1.0,"end_time":8.0,"flags":[]}
{"v":1,"idx":1,"text":"Its wheel was undershot, which was wasteful,","tokens":11,"page":null,"chapter":null,"role":"body","start_time":11.0,"end_time":19.0,"flags":[]}
{"v":1,"idx":2,"text":"and nobody replaced it for two hundred years.","tokens":9,"page":null,"chapter":null,"role":"body","start_time":19.0,"end_time":24.0,"flags":[]}
Straight to an embedder:
cuenorm episode.vtt | cuepack | jq -r .text | emb --model bge-m3 --dim 1024
It also reads a subtitle file directly, so the middle step can be skipped when nothing else needs the cues:
cuepack episode.srt > passages.ndjson
Options
| Option | Default | Effect |
|---|---|---|
--seconds <SECONDS> |
75 |
Flush a passage once it covers this much audio |
--max-tokens <TOKENS> |
512 |
Never let a passage exceed this many tokens |
--man |
Write this page as a manual page, in roff |
Seventy-five seconds is about a paragraph of speech: long enough to hold an argument, short enough that a result points at a place in the recording rather than at a region of it.
Exit codes
| Code | Meaning |
|---|---|
0 |
Done |
64 |
The command line was wrong |
65 |
The cue stream could not be read |
74 |
The file could not be read |
How it packs
A passage is flushed when the window from its first cue's start to the current cue's end reaches the seconds budget, or when adding the current cue would take it past the token budget. The cue that trips the budget starts the next passage rather than finishing this one.
Immediately repeated lines are dropped rather than packed. Automatic captioning repeats each line across overlapping windows, and a passage padded with its own echo embeds as something nobody said.
Input
Either the cue stream cuenorm writes, or a subtitle file. A
cue stream begins with a JSON object and a subtitle file does not, which is
enough to tell them apart.
As a library
use cuepack::Budgets;
for passage in cuepack::pack(&cues, &Budgets::default()) {
println!("{:?}–{:?}: {}", passage.start_time, passage.end_time, passage.text);
}