@@ -401,8 +401,9 @@ def min_score(n: int) -> float:
401401
402402 # cursor: whisper-word lower-bound for the tight search.
403403 # Never goes backwards on normal matches; back-fill uses explicit lo/hi.
404- cursor = 0
405- consec_misses = 0 # consecutive misses on ≥MIN_WORDS segments
404+ cursor = 0
405+ consec_misses = 0 # consecutive misses on ≥MIN_WORDS segments
406+ last_matched_abs_end = 0 # abs index past end of last confirmed match
406407
407408 seg_raw_start = [None ] * len (transcript_segs )
408409 seg_raw_end = [None ] * len (transcript_segs )
@@ -454,6 +455,7 @@ def min_score(n: int) -> float:
454455 offset = new_offset
455456 new_cursor_t = whisper_words [abs_end - 1 ]["end" ] - OVERLAP_SEC
456457 cursor = max (cursor , bisect .bisect_left (w_starts , new_cursor_t ))
458+ last_matched_abs_end = max (last_matched_abs_end , abs_end )
457459 consec_misses = 0
458460
459461 # ── Queue for back-fill ───────────────────────────────────────────────
@@ -468,12 +470,24 @@ def min_score(n: int) -> float:
468470 continue
469471
470472 # ── Successful match ──────────────────────────────────────────────────
473+ # Enforce strict order: abs_start must be >= last_matched_abs_end.
474+ # The cursor already prevents the search window from rewinding, but
475+ # a repeated phrase can still be found at an earlier whisper position
476+ # than where the previous anchor ended (e.g. 'Ich glaube,' appears 50×).
477+ if abs_start < last_matched_abs_end :
478+ pending_miss .append (si )
479+ n_missed += 1
480+ consec_misses += 1
481+ si += 1
482+ continue
483+
471484 offset = whisper_words [abs_start ]["start" ] - t_start
472485 consec_misses = 0
473486
474487 seg_raw_start [si ] = whisper_words [abs_start ]["start" ]
475488 seg_raw_end [si ] = whisper_words [abs_end - 1 ]["end" ]
476489 n_matched += 1
490+ last_matched_abs_end = abs_end
477491
478492 # Advance cursor to 2s before end of this match
479493 new_cursor_t = whisper_words [abs_end - 1 ]["end" ] - OVERLAP_SEC
@@ -488,9 +502,15 @@ def min_score(n: int) -> float:
488502 print (f" W: { wt [:70 ]!r} " , flush = True )
489503
490504 # ── Back-fill pending missed segments using this anchor ───────────────
491- # Walk in reverse order (closest to anchor first).
492- # Each pending seg gets a ±TIGHT_WIN window anchored by current offset,
493- # capped at abs_start so segment order is preserved.
505+ # Walk REVERSED (closest to anchor → farthest from anchor).
506+ # upper_bound starts at abs_start and moves LEFT as we assign.
507+ # Each back-filled segment must end strictly before upper_bound,
508+ # and upper_bound is updated to pa_s so the next (earlier) segment
509+ # is constrained to be placed before the one just assigned.
510+ # This guarantees the back-filled assignments are in transcript order.
511+ upper_bound = abs_start
512+ bf_results : dict = {} # psi → (pa_s, pa_e)
513+
494514 for psi in reversed (pending_miss ):
495515 pseg = transcript_segs [psi ]
496516 pt_n = norm_words (pseg ["text" ])
@@ -500,7 +520,7 @@ def min_score(n: int) -> float:
500520 continue
501521 p_expected = pt_s + offset
502522 p_lo = bisect .bisect_left (w_starts , p_expected - TIGHT_WIN )
503- p_hi = min (abs_start ,
523+ p_hi = min (upper_bound ,
504524 bisect .bisect_right (w_starts , p_expected + TIGHT_WIN ))
505525 if p_lo >= p_hi :
506526 if DEBUG :
@@ -511,19 +531,23 @@ def min_score(n: int) -> float:
511531 pa_s , pa_e , p_score = search_segment (
512532 pt_n , whisper_words , w_starts ,
513533 p_lo , p_center , p_half_win , min_score (pt_len ))
514- if pa_s is not None and pa_e <= abs_start :
515- seg_raw_start [psi ] = whisper_words [pa_s ]["start" ]
516- seg_raw_end [psi ] = whisper_words [pa_e - 1 ]["end" ]
517- n_matched += 1
518- n_missed -= 1
534+ if pa_s is not None and pa_e <= upper_bound :
535+ bf_results [psi ] = (pa_s , pa_e )
536+ upper_bound = pa_s # earlier segs must be placed before this
519537 if DEBUG :
520538 print (f" BACKFILL MATCH psi={ psi :4d} "
521- f"t={ pt_s :.1f} s raw={ seg_raw_start [ psi ]:.1f} s "
539+ f"t={ pt_s :.1f} s raw={ whisper_words [ pa_s ][ 'start' ]:.1f} s "
522540 f"score={ p_score :.2f} " , flush = True )
523541 else :
524542 if DEBUG :
525543 print (f" BACKFILL MISS psi={ psi :4d} t={ pt_s :.1f} s" ,
526544 flush = True )
545+
546+ for psi , (pa_s , pa_e ) in bf_results .items ():
547+ seg_raw_start [psi ] = whisper_words [pa_s ]["start" ]
548+ seg_raw_end [psi ] = whisper_words [pa_e - 1 ]["end" ]
549+ n_matched += 1
550+ n_missed -= 1
527551 pending_miss .clear ()
528552
529553 si += 1
0 commit comments