fix(downloads): tighten SourceAdapter resolution + neutral nfo default (review)

- resolve_adapter: identify from the yt-dlp extractor_key only, not the full
  webpage_url (a sibling source's name in a URL path no longer misresolves);
  add youtube-nocookie.com to the YouTube host match so nocookie embeds keep
  the POT/player-client extraction strategy.
- _download: re-resolve the adapter with the post-extraction info dict so the
  .nfo uniqueid type and uploader_url agree on one source (dedups the inline
  resolve too).
- MediaMeta.uniqueid_type defaults to source-neutral "generic" instead of
  "youtube", so an un-updated caller can never falsely label a non-YouTube
  download as youtube.
- Tests: lock extractor-key-only resolution + nocookie host; default nfo type.
This commit is contained in:
2026-08-13 01:09:38 +02:00
parent d4dca46d9f
commit 883e8515b9
5 changed files with 33 additions and 9 deletions
+10 -5
View File
@@ -122,12 +122,17 @@ def resolve_adapter(
"""Pick the adapter for a job. Matches on `source_kind` first, then the yt-dlp `extractor_key`
from `info` (post-extraction), then the URL host in `source_ref` (pre-extraction). Falls back to
the generic adapter — so behaviour is unchanged for any source without a dedicated one."""
tag = ""
if info:
extractor = info.get("extractor_key") or info.get("extractor") or ""
tag = f"{extractor} {info.get('webpage_url') or ''}".lower()
# Identify from the yt-dlp extractor key alone — NOT the webpage_url, whose path could contain a
# sibling source's name (e.g. a reddit post slug '.../youtube_is_dead') and cause a misresolve.
tag = (info.get("extractor_key") or info.get("extractor") or "").lower() if info else ""
host = urlparse(source_ref).netloc.lower() if source_ref else ""
if source_kind == "youtube" or "youtube" in tag or "youtu.be" in host or "youtube.com" in host:
if (
source_kind == "youtube"
or "youtube" in tag
or "youtu.be" in host
or "youtube.com" in host
or "youtube-nocookie.com" in host
):
return _YOUTUBE
if "facebook" in tag or "facebook.com" in host or "fb.watch" in host:
return _FACEBOOK
+3 -2
View File
@@ -60,8 +60,9 @@ class MediaMeta:
uploader: str
upload_date: date | None
# The .nfo <uniqueid type=...> — the source's scraper type (youtube / facebook / …), set from
# the SourceAdapter. Defaults to "youtube" only as a safety net for un-updated callers.
uniqueid_type: str = "youtube"
# the SourceAdapter. Defaults to the source-neutral "generic" so an un-updated caller is at worst
# uninformative, never actively mislabelling a non-YouTube download as "youtube".
uniqueid_type: str = "generic"
duration_s: int | None = None
description: str | None = None
thumbnail_url: str | None = None
+4 -1
View File
@@ -791,6 +791,9 @@ def _download(job_id: int, asset_id: int, source_kind: str, source_ref: str, spe
adapter = adapters.resolve_adapter(source_kind, source_ref)
url = service.source_url(source_kind, source_ref)
info = _media_info(_extract_with_fallback(job_id, asset_id, url, spec, staging, adapter))
# Re-resolve now that yt-dlp's info reveals the true source (a bare/redirected url only
# identifies post-extraction) so the .nfo uniqueid type + uploader_url agree on one source.
adapter = adapters.resolve_adapter(source_kind, source_ref, info)
produced = _produced_file(info, staging)
produced = _ensure_browser_playable(job_id, produced, spec, info)
@@ -834,7 +837,7 @@ def _download(job_id: int, asset_id: int, source_kind: str, source_ref: str, spe
asset.acodec = info.get("acodec") if info.get("acodec") not in (None, "none") else None
asset.title = meta.title
asset.uploader = meta.uploader
asset.uploader_url = adapters.resolve_adapter(source_kind, source_ref, info).uploader_url(info)
asset.uploader_url = adapter.uploader_url(info)
asset.upload_date = meta.upload_date
asset.thumbnail_url = meta.thumbnail_url
# Record a self-hosted poster for every download: it's the reliable link-preview