fix(captions): add language variant fallback + error on no files
Two fixes for captions downloads: 1. Language variant fallback: When user requests 'en' but the video only has 'en-GB' (or other regional variants), yt-dlp silently skips subtitle extraction because it uses exact language matching. Now the subtitleslangs list includes common regional variants (e.g. en-US, en-GB, en-AU, etc.) so yt-dlp can match the first available variant. 2. Error on no files: When yt-dlp completes successfully (ret=0) but produces no subtitle files (e.g. no matching language at all), the download was incorrectly shown as 'completed'. Now it's marked as 'error' with a descriptive message like 'No subtitles found for language "en"'. Same treatment for thumbnail downloads.
This commit is contained in:
parent
e639caa21d
commit
36b397ae93
2 changed files with 72 additions and 6 deletions
|
|
@ -10,6 +10,39 @@ CODEC_FILTER_MAP = {
|
|||
'vp9': "[vcodec~='^vp0?9']",
|
||||
}
|
||||
|
||||
# Common language variant suffixes that yt-dlp may encounter on YouTube and
|
||||
# other platforms. When a user requests "en" we also try "en-GB", "en-US",
|
||||
# etc. so that a request isn't silently skipped when only a regional variant
|
||||
# exists. yt-dlp processes the list in order and picks the first match.
|
||||
_LANGUAGE_REGION_VARIANTS = {
|
||||
"en": ("US", "GB", "AU", "CA", "IN", "NZ", "IE", "ZA"),
|
||||
"zh": ("CN", "TW", "HK", "SG"),
|
||||
"pt": ("BR", "PT"),
|
||||
"es": ("ES", "MX", "AR", "CO", "CL", "PE", "VE"),
|
||||
"fr": ("FR", "CA", "BE", "CH"),
|
||||
"de": ("DE", "AT", "CH"),
|
||||
"ar": ("SA", "AE", "EG", "MA", "DZ"),
|
||||
"nl": ("NL", "BE"),
|
||||
"ro": ("RO", "MD"),
|
||||
"ms": ("MY", "BN", "SG"),
|
||||
}
|
||||
|
||||
|
||||
def _subtitle_lang_list(language: str, include_orig: bool = False) -> list[str]:
|
||||
"""Build a prioritised list of subtitle language codes for yt-dlp.
|
||||
|
||||
Starts with the exact language, then appends common regional variants
|
||||
(e.g. ``en`` → ``en-US, en-GB, en-AU, …``). ``include_orig`` adds the
|
||||
``<lang>-orig`` tag that YouTube uses for original-language auto-captions.
|
||||
"""
|
||||
langs = [language]
|
||||
variants = _LANGUAGE_REGION_VARIANTS.get(language)
|
||||
if variants:
|
||||
langs.extend(f"{language}-{region}" for region in variants)
|
||||
if include_orig:
|
||||
langs.append(f"{language}-orig")
|
||||
return langs
|
||||
|
||||
|
||||
def _normalize_caption_mode(mode: str) -> str:
|
||||
mode = (mode or "").strip()
|
||||
|
|
@ -141,21 +174,20 @@ def get_opts(
|
|||
if mode == "manual_only":
|
||||
opts["writesubtitles"] = True
|
||||
opts["writeautomaticsub"] = False
|
||||
opts["subtitleslangs"] = [language]
|
||||
opts["subtitleslangs"] = _subtitle_lang_list(language)
|
||||
elif mode == "auto_only":
|
||||
opts["writesubtitles"] = False
|
||||
opts["writeautomaticsub"] = True
|
||||
# `-orig` captures common YouTube auto-sub tags. The plain language
|
||||
# fallback keeps behavior useful across other extractors.
|
||||
opts["subtitleslangs"] = [f"{language}-orig", language]
|
||||
opts["subtitleslangs"] = _subtitle_lang_list(language, include_orig=True)
|
||||
elif mode == "prefer_auto":
|
||||
opts["writesubtitles"] = True
|
||||
opts["writeautomaticsub"] = True
|
||||
opts["subtitleslangs"] = [f"{language}-orig", language]
|
||||
opts["subtitleslangs"] = _subtitle_lang_list(language, include_orig=True)
|
||||
else:
|
||||
# prefer_manual (default)
|
||||
opts["writesubtitles"] = True
|
||||
opts["writeautomaticsub"] = True
|
||||
opts["subtitleslangs"] = [language, f"{language}-orig"]
|
||||
opts["subtitleslangs"] = _subtitle_lang_list(language, include_orig=True)
|
||||
|
||||
opts["postprocessors"] = postprocessors + (
|
||||
opts["postprocessors"] if "postprocessors" in opts else []
|
||||
|
|
|
|||
34
app/ytdl.py
34
app/ytdl.py
|
|
@ -704,6 +704,7 @@ class Download:
|
|||
status = await self.loop.run_in_executor(None, self.status_queue.get)
|
||||
if status is None:
|
||||
log.info(f"Status update finished for: {self.info.title}")
|
||||
|
||||
return
|
||||
if self.paused:
|
||||
self.info.status = 'paused'
|
||||
|
|
@ -775,6 +776,7 @@ class Download:
|
|||
str(getattr(self.info, 'format', '')).lower() == 'txt'
|
||||
):
|
||||
self.info.filename = rel_path
|
||||
continue
|
||||
|
||||
if 'thumbnail_file' in status:
|
||||
thumbnail_file = status.get('thumbnail_file')
|
||||
|
|
@ -784,6 +786,11 @@ class Download:
|
|||
self.info.size = os.path.getsize(thumbnail_file)
|
||||
continue
|
||||
|
||||
# All remaining messages must have a 'status' key
|
||||
if 'status' not in status:
|
||||
log.warning(f"Skipping status update without 'status' key for {self.info.title}: {list(status.keys())}")
|
||||
continue
|
||||
|
||||
self.info.status = status['status']
|
||||
self.info.msg = status.get('msg')
|
||||
if 'download_phase' in status:
|
||||
|
|
@ -1006,6 +1013,33 @@ class DownloadQueue:
|
|||
except OSError:
|
||||
pass
|
||||
download.info.status = 'error'
|
||||
else:
|
||||
# Captions-only downloads that produced no subtitle files should be
|
||||
# reported as an error rather than a silent "success". This can
|
||||
# happen when the requested language (e.g. "en") is unavailable and
|
||||
# yt-dlp silently skips subtitle extraction without raising an
|
||||
# error.
|
||||
if getattr(download.info, 'download_type', '') == 'captions' and not download.info.filename:
|
||||
subtitle_files = getattr(download.info, 'subtitle_files', [])
|
||||
if not subtitle_files:
|
||||
log.warning(
|
||||
f"Captions download for \"{download.info.title}\" produced no files. "
|
||||
f"Requested language: {getattr(download.info, 'subtitle_language', 'en')}, "
|
||||
f"mode: {getattr(download.info, 'subtitle_mode', 'prefer_manual')}"
|
||||
)
|
||||
download.info.status = 'error'
|
||||
download.info.error = (
|
||||
f"No subtitles found for language \"{getattr(download.info, 'subtitle_language', 'en')}\". "
|
||||
f"The video may not have subtitles in the requested language."
|
||||
)
|
||||
# Thumbnail-only downloads that produced no image file should also
|
||||
# be reported as an error for the same reason.
|
||||
elif getattr(download.info, 'download_type', '') == 'thumbnail' and not download.info.filename:
|
||||
log.warning(
|
||||
f"Thumbnail download for \"{download.info.title}\" produced no file."
|
||||
)
|
||||
download.info.status = 'error'
|
||||
download.info.error = "No thumbnail found for this video."
|
||||
download.close()
|
||||
if self.queue.exists(key):
|
||||
self.queue.delete(key)
|
||||
|
|
|
|||
Loading…
Reference in a new issue