Skip to content

Commit 37b564f

Browse files
author
Zenflow
committed
fix bootstrap: constrain 4-gram scan to first 180s of whisper output
Prevents false match deep in audio (e.g. offset=+2034s) when the same 4-gram happens to appear mid-episode before it appears in the intro region. Also add --skip-step3 flag to run only steps 1+2 (alignment) without the stripped-audio re-transcription, which takes ~70 min for large-v3.
1 parent 8e56628 commit 37b564f

1 file changed

Lines changed: 27 additions & 11 deletions

File tree

scripts/podcast_to_moshi_dataset.py

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
)
6363
WHISPER_MODEL = os.path.join(
6464
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
65-
"bin", "models", "ggml-large-v3-turbo-q5_0.bin",
65+
"bin", "models", "ggml-large-v3.bin",
6666
)
6767

6868
WHISPER_THREADS = 8
@@ -256,16 +256,24 @@ def search_segment(t_norm: list, whisper_words: list, w_starts: list,
256256

257257

258258
def _bootstrap_offset(whisper_words: list, transcript_segs: list,
259-
max_segs: int = 40) -> float | None:
259+
max_segs: int = 40,
260+
max_raw_t: float = 180.0) -> float | None:
260261
"""
261262
Estimate the initial raw_time − transcript_time offset by finding the
262-
first 4-word exact run shared between the transcript and whisper words.
263+
first 4-word exact run shared between the transcript (first max_segs
264+
segments) and the whisper output (first max_raw_t seconds only).
265+
266+
Constraining to the first max_raw_t seconds of whisper prevents a rare
267+
false match deep in the audio from producing a wildly wrong initial offset.
263268
264269
Returns offset (float) or None if nothing found.
265270
"""
266271
w_norm = [((norm_words(w["word"]) or [""])[0]) for w in whisper_words]
267272
w_starts = [w["start"] for w in whisper_words]
268273

274+
# Only search within the first max_raw_t seconds of whisper output
275+
wi_limit = bisect.bisect_right(w_starts, max_raw_t)
276+
269277
for seg in transcript_segs[:max_segs]:
270278
tn = norm_words(seg["text"])
271279
t_start = float(seg["start"])
@@ -274,12 +282,12 @@ def _bootstrap_offset(whisper_words: list, transcript_segs: list,
274282
continue
275283
for gi in range(len(tn) - 3):
276284
gram = tn[gi:gi + 4]
277-
for wi in range(len(w_norm) - 3):
285+
for wi in range(min(wi_limit, len(w_norm) - 3)):
278286
if w_norm[wi:wi + 4] == gram:
279-
# gram starts at gi/len(tn) through the segment
280287
word_frac = gi / len(tn)
281288
approx_gram_t = t_start + word_frac * seg_dur
282-
return w_starts[wi] - approx_gram_t
289+
offset = w_starts[wi] - approx_gram_t
290+
return offset
283291
return None
284292

285293

@@ -783,7 +791,8 @@ def parse_episode_filter(spec: str) -> set:
783791
return result
784792

785793

786-
def process_episode(ep_num: int, transcript_path: str, mp3_path: str):
794+
def process_episode(ep_num: int, transcript_path: str, mp3_path: str,
795+
skip_step3: bool = False):
787796
print(f"\n{'═'*60}", flush=True)
788797
print(f" Episode {ep_num}{os.path.basename(mp3_path)}", flush=True)
789798
print(f"{'─'*60}", flush=True)
@@ -810,8 +819,11 @@ def process_episode(ep_num: int, transcript_path: str, mp3_path: str):
810819
print_alignment_report(aligned, transcript_segs)
811820

812821
# ── Step 3 ────────────────────────────────────────────────────────────────
813-
print(f"\n [Step 3] Rerun whisper on stripped audio + compare", flush=True)
814-
step3_rerun_compare(ep_num, mp3_path, aligned)
822+
if skip_step3:
823+
print(f"\n [Step 3] skipped (--skip-step3)", flush=True)
824+
else:
825+
print(f"\n [Step 3] Rerun whisper on stripped audio + compare", flush=True)
826+
step3_rerun_compare(ep_num, mp3_path, aligned)
815827

816828

817829
def main():
@@ -821,6 +833,8 @@ def main():
821833
)
822834
parser.add_argument("--episodes", default=None,
823835
help='e.g. "152" or "150-152" or "150,152"')
836+
parser.add_argument("--skip-step3", action="store_true",
837+
help="skip the stripped-audio re-transcription step")
824838
args = parser.parse_args()
825839

826840
if not os.path.isfile(WHISPER_CLI):
@@ -834,12 +848,14 @@ def main():
834848
if not episodes:
835849
sys.exit("No matching episodes found.")
836850

837-
print(f"Found {len(episodes)} episode(s). Running all 3 steps.")
851+
steps = "steps 1+2" if args.skip_step3 else "all 3 steps"
852+
print(f"Found {len(episodes)} episode(s). Running {steps}.")
838853

839854
errors = 0
840855
for ep_num, transcript_path, mp3_path in episodes:
841856
try:
842-
process_episode(ep_num, transcript_path, mp3_path)
857+
process_episode(ep_num, transcript_path, mp3_path,
858+
skip_step3=args.skip_step3)
843859
except Exception as exc:
844860
import traceback
845861
print(f"\n ✗ ep{ep_num} FAILED: {exc}", flush=True)

0 commit comments

Comments
 (0)