fix(stt): re-anchor whisper's word boundaries on the audio, so transcript trims cut where the words are#172
Merged
EtienneLescot merged 2 commits intoJul 27, 2026
Conversation
…ript trims cut where the words are
Deleting words in the transcript editor produced a trim whose edges were
audibly wrong every time: the attack of the first removed word stayed in, and
the cut bit into the following kept word.
The editor is not at fault - it turns a selection into exactly
`[firstWord.startSec, lastWord.endSec]`. The word times are. whisper.cpp's
`t_dtw` marks where the decoder *emitted* a token, not where the speaker
started saying it, and since `word.end` is the next word's `t_dtw`, every
boundary in the transcript is late by the same amount. The whole thing is
dragged right by roughly a syllable.
Measured on a real recording (33.6 s of French, `ggml-small-q8_0`) by
comparing the stored word times against the signal's own RMS envelope: the
lag is 80-150 ms, mean 83 ms. Two independent post-pause onsets confirm it
- "Alors" reported at 11.96 while the audio resumes at 11.78, "Voilà" at
25.04 against 24.85 - and phrase-*ends* are already correct (11.20 against
11.23), so a constant offset would have traded one artifact for another.
So measure instead of guessing: each boundary moves back to the quietest
10 ms frame within the preceding 150 ms of the same samples whisper was
given. That is self-limiting - on a decaying tail the quietest frame IS the
reported one, so the boundaries whisper already got right do not move. The
correction is applied in the server adapter, upstream of everything, so
trims, caption cues and the transcript pane's playhead highlight are all
fixed in one place.
On the trim that prompted the report ("vraiment il est bien"), 21.94-22.68
becomes 21.80-22.55; the audio has the phrase at 21.86-22.56. The post-pause
onsets land at 11.82 and 24.89 against 11.78 and 24.85, and the two
phrase-ends stay put.
One real bug came out of the existing whisperServer tests while writing
this: clamping an out-of-range boundary into the envelope dragged it onto
the end of the buffer. A boundary past the decoded audio now stays untouched
- there is nothing to measure it against.
Existing transcripts keep their old times; re-run transcription on a project
to pick the correction up. Phrase segments (the fallback whisper only
returns when it reports no words at all) are deliberately left alone.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Deleting words in the transcript editor produced a trim whose edges were audibly wrong every time: the attack of the first removed word stayed in, and the cut bit into the following kept word.
The editor is not at fault — it turns a selection into exactly
[firstWord.startSec, lastWord.endSec](RightPanes.tsx:704). The word times are. whisper.cpp'st_dtwmarks where the decoder emitted a token, not where the speaker started saying it, and sinceword.endis the next word'st_dtw, every boundary is late by the same amount — the whole transcript is dragged right by roughly a syllable.Fix: each boundary moves back to the quietest 10 ms frame within the preceding 150 ms of the same samples whisper was given. Applied in the server adapter, upstream of everything, so trims, caption cues and the transcript pane's playhead highlight are all corrected in one place.
It is self-limiting: on a decaying tail — a word ending a phrase, where whisper is already right — the quietest frame is the reported one, so nothing moves. That is why a constant offset was rejected: it would have fixed the onsets and broken the phrase-ends.
Related issue
Type of change
Release impact
Desktop impact
Measurements
Diagnosed and verified against a real 33.6 s French recording (
ggml-small-q8_0) by comparing the stored word times to the signal's own RMS envelope. Measured lag: 80–150 ms, mean 83 ms.vraiment)bien)Alors)Je)Voilà)profiter.)là.)The two phrase-ends are the control: whisper already had them right, and the correction leaves them alone.
Testing
npm run test— 1130/1130 pass (6 new inelectron/stt/snapWordBoundaries.test.ts)tsc --noEmitclean,biome checkclean,scripts/check-docs.mjsOKOne real bug surfaced from the existing
whisperServertests while writing this: clamping an out-of-range boundary into the envelope dragged it onto the end of the buffer. A boundary past the decoded audio now stays untouched — there is nothing to measure it against.Notes for the reviewer
LOOKBACK_SECinsnapWordBoundaries.tsis the calibration knob, sized to the measured lag. Widen it and boundaries inside continuous speech start snapping onto the previous syllable's trough.locateSourcePosition's epsilon,virtual-preview.ts:144) — negligible next to the 150 ms just corrected, and touching it risks the clip-chaining logic.