Compare commits

...
3 changed files with 48 additions and 9 deletions
+1
View File
@@ -28,6 +28,7 @@ the frozen-backend fallback mirror it for their toolchains.
### Fixed
- Dubbing jobs can now reuse every source-language code produced by automatic ASR detection without a 400 error on the next upload (#1737)
- Incomplete Sherpa-ONNX model snapshots now self-repair before recognizer startup instead of failing on a missing ONNX file (#1733)
- OmniVoice subprocess startup now allows slow packaged Windows Python runtimes to signal readiness before termination (#1711)
- SRT files selected during source analysis now wait for speaker cloning, then replace transcript text without losing voices (#1709)
+28 -9
View File
@@ -518,9 +518,12 @@ _ingest_gen = dub_pipeline.ingest_pipeline
#: container so a mislabelled video can't slip past the video-skipping branch.
_AUDIO_EXTS = {".wav", ".mp3", ".m4a", ".aac", ".flac", ".ogg", ".opus", ".wma"}
# Source-language choices exposed by the first-party dub UI. Keeping this an
# allow-list rejects language names and private-use BCP-47 tags before they are
# persisted as ASR overrides. Values are normalized to lowercase below.
# Source-language choices exposed by the first-party dub UI, plus every
# language code Whisper can write back after auto-detection. A restored job
# may reuse that detected value as the next upload's override, so rejecting our
# own persisted codes strands otherwise valid dubbing sessions (#1737).
# Keeping this an allow-list still rejects language names and private-use
# BCP-47 tags. Values are normalized to lowercase below.
_DUB_SOURCE_LANG_CODES = frozenset({
"af", "sq", "am", "ar", "hy", "az", "eu", "be", "bn", "bs", "bg",
"my", "ca", "cmn-hans", "cmn-hant", "hr", "cs", "da", "nl", "en",
@@ -531,6 +534,8 @@ _DUB_SOURCE_LANG_CODES = frozenset({
"ru", "sm", "gd", "sr", "sn", "sd", "si", "sk", "sl", "so", "es",
"su", "sw", "sv", "tg", "ta", "te", "th", "tr", "uk", "ur", "uz",
"vi", "cy", "xh", "yi", "yo", "zu",
"as", "ba", "bo", "br", "fo", "lb", "ln", "mg", "nn", "oc", "sa",
"tk", "tl", "tt", "yue", "zh",
})
@@ -544,6 +549,20 @@ def _source_lang_override(value: str | None) -> str | None:
return code
def _detected_source_lang(value: str | None) -> str:
"""Normalize an ASR language without truncating valid three-letter codes."""
code = (value or "en").split("_", 1)[0].strip().lower()
if code in _DUB_SOURCE_LANG_CODES:
return code
short = code[:2]
return short if short in _DUB_SOURCE_LANG_CODES else "en"
def _resolved_source_lang(override: str | None, detected: str | None) -> str:
"""Prefer an explicit source while preserving a valid ASR language code."""
return override or _detected_source_lang(detected)
@router.post("/dub/upload")
async def dub_upload(
video: UploadFile = File(...),
@@ -1809,9 +1828,9 @@ async def dub_transcribe_stream(
except Exception as e:
logger.warning("speaker_clone extraction skipped: %s", e)
job["source_lang"] = job.get("source_lang_override") or (
(detected_lang or "en").split("_")[0][:2] or "en"
).lower()
job["source_lang"] = _resolved_source_lang(
job.get("source_lang_override"), detected_lang
)
job["full_transcript"] = " ".join(s.get("text", "") for s in final_segs)
_save_job(job_id, job)
@@ -2008,9 +2027,9 @@ async def dub_transcribe(job_id: str, num_speakers: Optional[int] = None):
except Exception as e:
logger.warning("Failed to unload ASR backend: %s", e)
job["source_lang"] = job.get("source_lang_override") or (
(detected_lang or "en").split("_")[0][:2] or "en"
).lower()
job["source_lang"] = _resolved_source_lang(
job.get("source_lang_override"), detected_lang
)
scene_cuts = job.get("scene_cuts") or []
segments = segment_transcript(result, duration=job.get("duration", 0.0), scene_cuts=scene_cuts)
+19
View File
@@ -339,3 +339,22 @@ class TestAudioOnlyDubbing:
assert response.status_code == 202
assert queued[0][5]["source_lang"] == "fr"
def test_asr_detected_source_languages_can_be_reused_as_overrides(self, app_client):
_client, dc, _dx, _tmp = app_client
detected_codes = {
"as", "ba", "bo", "br", "fo", "lb", "ln", "mg", "nn", "oc",
"sa", "tk", "tl", "tt", "yue", "zh",
}
for code in detected_codes:
assert dc._source_lang_override(code) == code
def test_asr_detected_cantonese_code_is_not_truncated(self, app_client):
_client, dc, _dx, _tmp = app_client
assert dc._detected_source_lang("yue") == "yue"
assert dc._detected_source_lang("es_ES") == "es"
assert dc._detected_source_lang("unknown-language") == "en"
assert dc._resolved_source_lang(None, "yue") == "yue"
assert dc._resolved_source_lang("fr", "yue") == "fr"