Fix: overlapping whisper timestamps cause subtitle flicker - #3
Conversation
When Whisper produces overlapping word timestamps (word B starts before word A ends), the old fallback of seg_start + 0.05s created a visible flash. Now uses the word's own whisper-reported end time, with a 0.3s minimum floor. Non-overlapping words are unaffected.
louisedesadeleer
left a comment
There was a problem hiding this comment.
Thanks for this — the diagnosis is exactly right (the 0.05s fallback window is a ~1.5-frame flash), but I tested the fix and it trades that flash for a more visible artifact.
Repro. I ran build_ass.py on a whisper JSON where the third word overlaps the second (there 1.4–1.9, overlapping starts at 1.3):
Before (main):
Dialogue: 0,0:00:01.40,0:00:01.45,...,hello {\c..}there{\c..} overlapping
Dialogue: 0,0:00:01.30,0:00:02.40,...,hello there {\c..}overlapping{\c..}
With this PR:
Dialogue: 0,0:00:01.40,0:00:01.90,...,hello {\c..}there{\c..} overlapping
Dialogue: 0,0:00:01.30,0:00:02.40,...,hello there {\c..}overlapping{\c..}
The extended event now overlaps the next event for 0.5s instead of 0.05s. Since both events carry the full chunk text, libass collision handling stacks them — the viewer sees two copies of the caption line at once (different word highlighted in each) for half a second. The old flash was ugly but brief; this is longer and more noticeable.
Root cause is that overlapping timestamps are never normalized, so any fix at event-build time is patching a symptom. My suggestion: sanitize the word list right after it is built (around line 32), so starts are monotonic with a minimum word duration:
MIN_WORD = 0.15
for k in range(1, len(words)):
if words[k]["start"] < words[k-1]["start"] + MIN_WORD:
words[k]["start"] = words[k-1]["start"] + MIN_WORDThen every event gets ≥0.15s (~4–5 frames), no event ever overlaps the next, and the fallback branch on line 57 becomes nearly unreachable (can keep it as a belt-and-braces floor). Only glitched words get shifted; normal timestamps never enter the condition.
Happy to merge if you rework it along those lines — and thanks for the clear before/after table in the description.
When Whisper produces overlapping word timestamps (word B starts before word A ends), the fallback on line 62 created a visible flash. Instead of patching the fallback, sanitize the word list right after it is built so starts are monotonic with a minimum 0.15s word duration. - Only glitched words get shifted; normal timestamps are unaffected - Fallback kept as belt-and-braces (nearly unreachable now) - Fixes the stacking artifact from the previous approach
|
Updated — applied your timestamp normalization suggestion. Word starts are now monotonic with a 0.15s minimum floor right after the word list is built. Fallback on line 62 kept as belt-and-braces. Ready for re-review. |
Problem
When Whisper produces overlapping word timestamps (i.e. the next word starts before the current word ends), line 57 falls back to
seg_end = seg_start + 0.05. A 0.05s display window is ~1.5 frames at 30fps — the word barely renders, causing a visible white flash.Fix
Changed the fallback to
max(w["end"], seg_start + 0.3). This uses the word's own whisper-reported end time (more accurate), with a 0.3s minimum floor (~9 frames) if even that is malformed. Non-overlapping words are completely unaffected — they never enter this branch.Before vs After