feat: real word timestamps from the bundled server, carried through the pipeline (#352) #499
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/352-word-timestamps-client"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #352 (client half; the server half is Rhoving/iac-repo#396, deployed and verified live).
The bundled WhisperX server already did VAD-cut transcription and forced alignment and threw the word timings away; API v2 returns them. This makes the local adapter say what it can do, consume the words as real spans, carry them through the pipeline, and use them.
What
probe_local_whisper_capabilitiesdoes a 5 sGET /health, cached process-wide per endpoint for 10 minutes (a fresh adapter is built per call, so an instance cache would probe once per VAD span). A v2 server declaresword_timestamps=True, vocabulary viainitial_prompt, and the server'sconcurrency_limit; a v1 server (or an unreachable one) declares no word timestamps andVocabularyStyle.NONE— a v1 server ignoresinitial_prompt, so the honest declaration makes #355 skip the query and the #358 panel showasr_no_vocabularywith "update the bundled server".capabilitiesis a property returning the last-known value; the probe is awaited insubmitand at the read sites that decide something. Deliberately not probed at the #356 slot decision — a queued session must not touch the provider.parse_aligned_segments: aligned wordsinterpolated=Falsewith the alignment score as confidence;null-timed words placed proportionally between their aligned neighbours and flagged;"words": []falls back to whole-segment interpolation; the parser is chosen by the declaration, never by sniffing the body. A 500 invalidates the cached probe (a replaced server); a 429 does not.words;_offset_segmentsoffsets them on the VAD path; migrationc7d8e9fa0b1c(afterb6c7d8e9fa0b, single head, up/down/up verified) adds nullable JSONBtranscript_segments.words, written with the rows. A test drives identical timings through the local v2 adapter and the stub adapter and asserts identical persisted rows — one code path for both.anchor_highlightsanchors to the first non-interpolated word of the matched phrase, else the segment start as before; and it now matches across consecutive same-track segments, becausecompact_transcriptjoins those lines with a space so a quote the model actually read can straddle a boundary while having exactly one person to credit. Cross-speaker stitches are still dropped./healthcapabilities,wordswith a null-timed word and an empty-words segment) with a v1 variant as its own case. One case was restated: a word-timestamping provider must have measured at least one word and its degraded flag must matchany(word.interpolated)in both directions — no forced-alignment server can promise every word.Live check against
10.3.0.28:8091: probe reads v2; a realsubmitwith 4 s of non-speech and a vocabulary returned{"word":"Bramblewick","start":0.031,"end":0.253,"score":0.007}— the parser handling a real body and the server honouringinitial_prompt, in one request.Rebased over #350 (kept both sides:
retry_after_seconds, the adapter'smodel=,OpenAiAsrCasebesideLocalWhisperV1Case).Verification
alembic heads→ one head. Version sync OK; no bot contract change.🤖 Generated with Claude Code
`transcribe_track` puts `words` on every segment dict — `{word, start, end, interpolated}` — and the VAD path shifts their times with the segment they belong to. They travel; nothing branches on them. The merge, the overlap marking, the line format and the coverage checks are untouched, and every provider fills the key: the #352 server with measured spans, a v1 server and the interpolating path with estimates that say so. That is what lets the two consumers that want words — highlight anchoring, and the persisted rows — ask one question instead of asking which adapter ran. `_offset_segments` is the half that cannot merely pass them through. A span cut at two hours is transcribed as its own file starting at zero, so a word left in span-relative time would be a *measured* timestamp pointing two hours from the words it belongs to — worse than the interpolated one it replaced, because nothing would mark it wrong. `confidence` is dropped on the way into the dict. Nothing reads it, and an alignment score and a managed provider's per-word confidence are on different scales, so carrying it would only guarantee that two runs of one session on two providers stored visibly different things. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>