Skip to content

Commit 050e560

Browse files
nodeeeeeeclaude
andcommitted
Fix language misdetection in OpenAI Whisper API transcription
Previously, chunk 0's auto-detected language was locked for all subsequent chunks. If chunk 0 misdetected (e.g. "cy"/Welsh for an English lecture), the entire transcript was forced into that language. Now each chunk auto-detects independently, and majority voting across all chunks determines the final language label. This prevents one bad chunk from poisoning the whole file. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 79d0642 commit 050e560

1 file changed

Lines changed: 18 additions & 14 deletions

File tree

extract_caption.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ def transcribe_api(video_path: Path, caption_path: Path) -> bool:
275275
f"≤{_API_CHUNK_MINUTES} min (total {total_dur:.0f}s)")
276276

277277
all_segments: list = []
278-
detected_lang: str | None = None # set after first chunk; locked for all subsequent chunks
278+
chunk_langs: list[str] = [] # per-chunk detected language for majority vote
279279
lang_prob = 1.0
280280
total_dropped = 0
281281

@@ -289,9 +289,10 @@ def transcribe_api(video_path: Path, caption_path: Path) -> bool:
289289
_extract_audio(audio_path, chunk_file, start=start, duration=dur)
290290
chunk_mb = chunk_file.stat().st_size / (1024 ** 2)
291291

292-
# Chunk 1: auto-detect (or use explicit setting).
293-
# Chunk 2+: lock to the language detected from chunk 1 to prevent drift.
294-
chunk_lang = WHISPER_LANGUAGE if i == 0 else (detected_lang or WHISPER_LANGUAGE)
292+
# When language is set explicitly, use it for every chunk.
293+
# When auto-detecting (None), let each chunk detect independently
294+
# so one bad first-chunk detection doesn't poison the whole file.
295+
chunk_lang = WHISPER_LANGUAGE
295296
print(f" {chunk_mb:.1f} MB lang={chunk_lang or 'auto'} "
296297
f"sending to API...", end="", flush=True)
297298

@@ -306,10 +307,10 @@ def transcribe_api(video_path: Path, caption_path: Path) -> bool:
306307
)
307308
elapsed = _time.monotonic() - t0
308309

309-
# First chunk determines language for all subsequent chunks
310-
if i == 0:
311-
lang_full = getattr(response, "language", "english") or "english"
312-
detected_lang = _LANG_NAMES.get(lang_full.lower(), lang_full[:2].lower())
310+
# Collect per-chunk language for majority vote
311+
lang_full = getattr(response, "language", "english") or "english"
312+
chunk_detect = _LANG_NAMES.get(lang_full.lower(), lang_full[:2].lower())
313+
chunk_langs.append(chunk_detect)
313314

314315
resp_dict = response.model_dump()
315316
api_segs = resp_dict.get("segments", [])
@@ -337,16 +338,19 @@ def transcribe_api(video_path: Path, caption_path: Path) -> bool:
337338
all_segments.extend(segs)
338339

339340
drop_note = f" ({n_dropped} dropped)" if n_dropped else ""
340-
lang_note = (f" lang={detected_lang}" if i == 0 else "")
341+
lang_note = f" lang={chunk_detect}"
341342
print(f" done in {elapsed:.0f}s {len(segs)} segs{drop_note}{lang_note}")
342343

343-
if detected_lang and i == 0: # only one chunk — print lang info here
344-
pass # already printed above
345-
elif detected_lang:
346-
print(f" Language locked to '{detected_lang}' from first chunk")
344+
# Majority vote across chunks for the final language label
345+
from collections import Counter
346+
detected_lang = Counter(chunk_langs).most_common(1)[0][0] if chunk_langs else "en"
347+
if len(set(chunk_langs)) > 1:
348+
print(f" Language votes: {dict(Counter(chunk_langs))} → '{detected_lang}'")
349+
else:
350+
print(f" Detected language: '{detected_lang}'")
347351

348352
result = {
349-
"language": detected_lang or "en",
353+
"language": detected_lang,
350354
"language_probability": lang_prob,
351355
"duration": round(total_dur, 3),
352356
"segments": all_segments,

0 commit comments

Comments
 (0)