Summary
media-use/audio/references/transcribe.md:43 states the word id (w0, w1, …) "is added during normalization for stable references in caption overrides."
For the JSON transcripts the CLI itself produces, it never is. Per-word caption overrides have nothing to key on.
Environment
- hyperframes 0.8.10, installed globally via npm
- macOS 15, Apple M5 Pro
- whisper-cpp
small.en via Homebrew whisper-cli
- Independently reproduced on WSL2 / Ubuntu
Measured
hyperframes transcribe probe.wav --model small.en on 1.7 s of speech:
[
{ "text": "The", "start": 0.01, "end": 0.16 },
{ "text": "pipeline", "start": 0.16, "end": 0.6 },
{ "text": "is", "start": 0.6, "end": 0.68 },
{ "text": "proven", "start": 0.82, "end": 1.04 },
{ "text": "end-to-end.", "start": 1.04, "end": 1.74 }
]
Zero id fields across all 5 words.
Where it is and is not assigned
loadTranscript (dist/cli.js:97787) branches on file type:
| input |
id assigned? |
.srt |
yes — id: w.id ?? "w" + i |
.vtt |
yes — id: w.id ?? "w" + i |
| JSON, whisper-cpp shape |
no — parseWhisperCpp emits {text,start,end} only |
| JSON, openai shape |
no — parseOpenAI emits {text,start,end} only |
| JSON, flat array |
id: w.id ?? "" — empty string, not "w" + i |
The flat-array branch is the one that looks like the documented normalization and is not: a missing id becomes "", so every word ends up sharing the same empty key.
Since the branch is chosen by file format rather than by caller, going through the skill-driven flow instead of the CLI does not change the result — it is the same loadTranscript.
Workaround (verified, no code change)
Round-trip through SRT with --preserve-cues on both legs:
hyperframes transcribe audio.wav --model small.en # -> transcript.json, no ids
hyperframes transcribe transcript.json --to srt -o w.srt --preserve-cues
hyperframes transcribe w.srt --preserve-cues # -> transcript.json WITH ids
Result: 5 entries, word-level timings preserved, id w0…w4 present.
--preserve-cues is required on the export leg too. Without it the export groups all 5 words into a single cue, and the word granularity is gone before the id assignment ever runs — the re-import then yields one entry, id: "w0", text "The pipeline is proven end-to-end.".
The CLI help describes --preserve-cues purely in import terms ("Keep each transcript entry as its own caption cue"), which gives no hint that it is load-bearing on export.
Suggested fixes
- Assign
"w" + i in parseWhisperCpp / parseOpenAI, or in the flat-array branch of loadTranscript instead of defaulting to "".
- Correct
transcribe.md:43 — as written it promises ids on the path that never produces them.
- Document
--preserve-cues as applying to export as well as import.
Summary
media-use/audio/references/transcribe.md:43states the wordid(w0,w1, …) "is added during normalization for stable references in caption overrides."For the JSON transcripts the CLI itself produces, it never is. Per-word caption overrides have nothing to key on.
Environment
small.envia Homebrewwhisper-cliMeasured
hyperframes transcribe probe.wav --model small.enon 1.7 s of speech:[ { "text": "The", "start": 0.01, "end": 0.16 }, { "text": "pipeline", "start": 0.16, "end": 0.6 }, { "text": "is", "start": 0.6, "end": 0.68 }, { "text": "proven", "start": 0.82, "end": 1.04 }, { "text": "end-to-end.", "start": 1.04, "end": 1.74 } ]Zero
idfields across all 5 words.Where it is and is not assigned
loadTranscript(dist/cli.js:97787) branches on file type:idassigned?.srtid: w.id ?? "w" + i.vttid: w.id ?? "w" + iparseWhisperCppemits{text,start,end}onlyparseOpenAIemits{text,start,end}onlyid: w.id ?? ""— empty string, not"w" + iThe flat-array branch is the one that looks like the documented normalization and is not: a missing id becomes
"", so every word ends up sharing the same empty key.Since the branch is chosen by file format rather than by caller, going through the skill-driven flow instead of the CLI does not change the result — it is the same
loadTranscript.Workaround (verified, no code change)
Round-trip through SRT with
--preserve-cueson both legs:Result: 5 entries, word-level timings preserved,
idw0…w4present.--preserve-cuesis required on the export leg too. Without it the export groups all 5 words into a single cue, and the word granularity is gone before the id assignment ever runs — the re-import then yields one entry,id: "w0", text"The pipeline is proven end-to-end.".The CLI help describes
--preserve-cuespurely in import terms ("Keep each transcript entry as its own caption cue"), which gives no hint that it is load-bearing on export.Suggested fixes
"w" + iinparseWhisperCpp/parseOpenAI, or in the flat-array branch ofloadTranscriptinstead of defaulting to"".transcribe.md:43— as written it promises ids on the path that never produces them.--preserve-cuesas applying to export as well as import.