nn
This commit is contained in:
@@ -10,7 +10,7 @@ This keeps format selection logic in ytdlp and leaves add-file plug-and-play.
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from typing import Any, Dict, Iterable, List, Optional
|
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
||||||
|
|
||||||
from ProviderCore.base import Provider, SearchResult
|
from ProviderCore.base import Provider, SearchResult
|
||||||
from SYS.provider_helpers import TableProviderMixin
|
from SYS.provider_helpers import TableProviderMixin
|
||||||
@@ -65,31 +65,31 @@ class ytdlp(TableProviderMixin, Provider):
|
|||||||
|
|
||||||
# Dynamically load URL domains from yt-dlp's extractors
|
# Dynamically load URL domains from yt-dlp's extractors
|
||||||
# This enables provider auto-discovery for format selection routing
|
# This enables provider auto-discovery for format selection routing
|
||||||
@property
|
@classmethod
|
||||||
def URL(self) -> List[str]:
|
def url_patterns(cls) -> Tuple[str, ...]:
|
||||||
"""Get list of supported domains from yt-dlp extractors."""
|
"""Return supported domains from yt-dlp extractors."""
|
||||||
try:
|
try:
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
# Build a comprehensive list from known extractors and fallback domains
|
# Build a comprehensive list from known extractors and fallback domains
|
||||||
domains = set(self._fallback_domains)
|
domains = set(cls._fallback_domains)
|
||||||
|
|
||||||
# Try to get extractors and extract domain info
|
# Try to get extractors and extract domain info
|
||||||
try:
|
try:
|
||||||
extractors = yt_dlp.gen_extractors()
|
extractors = yt_dlp.gen_extractors()
|
||||||
for extractor_class in extractors:
|
for extractor_class in extractors:
|
||||||
# Get extractor name and try to convert to domain
|
# Get extractor name and try to convert to domain
|
||||||
name = getattr(extractor_class, 'IE_NAME', '')
|
name = getattr(extractor_class, "IE_NAME", "")
|
||||||
if name and name not in ('generic', 'http'):
|
if name and name not in ("generic", "http"):
|
||||||
# Convert extractor name to domain (e.g., 'YouTube' -> 'youtube.com')
|
# Convert extractor name to domain (e.g., 'YouTube' -> 'youtube.com')
|
||||||
name_lower = name.lower().replace('ie', '').strip()
|
name_lower = name.lower().replace("ie", "").strip()
|
||||||
if name_lower and len(name_lower) > 2:
|
if name_lower and len(name_lower) > 2:
|
||||||
domains.add(f"{name_lower}.com")
|
domains.add(f"{name_lower}.com")
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return list(domains) if domains else self._fallback_domains
|
return tuple(domains) if domains else tuple(cls._fallback_domains)
|
||||||
except Exception:
|
except Exception:
|
||||||
return self._fallback_domains
|
return tuple(cls._fallback_domains)
|
||||||
|
|
||||||
# Fallback common domains in case extraction fails
|
# Fallback common domains in case extraction fails
|
||||||
_fallback_domains = [
|
_fallback_domains = [
|
||||||
|
|||||||
@@ -674,6 +674,35 @@ class Download_File(Cmdlet):
|
|||||||
unsupported = list(set(raw_urls or []) - set(supported or []))
|
unsupported = list(set(raw_urls or []) - set(supported or []))
|
||||||
return supported, unsupported
|
return supported, unsupported
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _match_provider_urls(
|
||||||
|
raw_urls: Sequence[str],
|
||||||
|
registry: Dict[str, Any],
|
||||||
|
) -> Dict[str, str]:
|
||||||
|
matches: Dict[str, str] = {}
|
||||||
|
if not raw_urls:
|
||||||
|
return matches
|
||||||
|
|
||||||
|
match_provider_name_for_url = registry.get("match_provider_name_for_url")
|
||||||
|
if not callable(match_provider_name_for_url):
|
||||||
|
return matches
|
||||||
|
|
||||||
|
for url in raw_urls:
|
||||||
|
try:
|
||||||
|
url_str = str(url or "").strip()
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if not url_str:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
provider_name = match_provider_name_for_url(url_str)
|
||||||
|
except Exception:
|
||||||
|
provider_name = None
|
||||||
|
if provider_name:
|
||||||
|
matches[url_str] = str(provider_name).strip().lower()
|
||||||
|
|
||||||
|
return matches
|
||||||
|
|
||||||
def _parse_query_keyed_spec(self, query_spec: Optional[str]) -> Dict[str, List[str]]:
|
def _parse_query_keyed_spec(self, query_spec: Optional[str]) -> Dict[str, List[str]]:
|
||||||
if not query_spec:
|
if not query_spec:
|
||||||
return {}
|
return {}
|
||||||
@@ -2500,10 +2529,12 @@ class Download_File(Cmdlet):
|
|||||||
|
|
||||||
original_skip_preflight = None
|
original_skip_preflight = None
|
||||||
original_timeout = None
|
original_timeout = None
|
||||||
|
original_skip_direct = None
|
||||||
try:
|
try:
|
||||||
if isinstance(config, dict):
|
if isinstance(config, dict):
|
||||||
original_skip_preflight = config.get("_skip_url_preflight")
|
original_skip_preflight = config.get("_skip_url_preflight")
|
||||||
original_timeout = config.get("_pipeobject_timeout_seconds")
|
original_timeout = config.get("_pipeobject_timeout_seconds")
|
||||||
|
original_skip_direct = config.get("_skip_direct_on_streaming_failure")
|
||||||
except Exception:
|
except Exception:
|
||||||
original_skip_preflight = None
|
original_skip_preflight = None
|
||||||
original_timeout = None
|
original_timeout = None
|
||||||
@@ -2521,6 +2552,7 @@ class Download_File(Cmdlet):
|
|||||||
return 0
|
return 0
|
||||||
if isinstance(config, dict):
|
if isinstance(config, dict):
|
||||||
config["_skip_url_preflight"] = True
|
config["_skip_url_preflight"] = True
|
||||||
|
config["_skip_direct_on_streaming_failure"] = True
|
||||||
|
|
||||||
if isinstance(config, dict) and config.get("_pipeobject_timeout_seconds") is None:
|
if isinstance(config, dict) and config.get("_pipeobject_timeout_seconds") is None:
|
||||||
config["_pipeobject_timeout_seconds"] = 60
|
config["_pipeobject_timeout_seconds"] = 60
|
||||||
@@ -2554,6 +2586,10 @@ class Download_File(Cmdlet):
|
|||||||
config.pop("_pipeobject_timeout_seconds", None)
|
config.pop("_pipeobject_timeout_seconds", None)
|
||||||
else:
|
else:
|
||||||
config["_pipeobject_timeout_seconds"] = original_timeout
|
config["_pipeobject_timeout_seconds"] = original_timeout
|
||||||
|
if original_skip_direct is None:
|
||||||
|
config.pop("_skip_direct_on_streaming_failure", None)
|
||||||
|
else:
|
||||||
|
config["_skip_direct_on_streaming_failure"] = original_skip_direct
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -2605,12 +2641,18 @@ class Download_File(Cmdlet):
|
|||||||
if picker_result is not None:
|
if picker_result is not None:
|
||||||
return int(picker_result)
|
return int(picker_result)
|
||||||
|
|
||||||
streaming_candidates = list(raw_url)
|
provider_url_matches = self._match_provider_urls(raw_url, registry)
|
||||||
|
streaming_candidates = [
|
||||||
|
url for url in raw_url
|
||||||
|
if provider_url_matches.get(str(url).strip()) == "ytdlp"
|
||||||
|
]
|
||||||
supported_streaming, unsupported_streaming = self._filter_supported_urls(streaming_candidates)
|
supported_streaming, unsupported_streaming = self._filter_supported_urls(streaming_candidates)
|
||||||
|
matched_ytdlp = bool(streaming_candidates)
|
||||||
|
|
||||||
streaming_exit_code: Optional[int] = None
|
streaming_exit_code: Optional[int] = None
|
||||||
streaming_downloaded = 0
|
streaming_downloaded = 0
|
||||||
if supported_streaming:
|
if supported_streaming:
|
||||||
|
debug(f"[download-file] Using ytdlp provider for {len(supported_streaming)} URL(s)")
|
||||||
streaming_exit_code = self._run_streaming_urls(
|
streaming_exit_code = self._run_streaming_urls(
|
||||||
streaming_urls=supported_streaming,
|
streaming_urls=supported_streaming,
|
||||||
args=args,
|
args=args,
|
||||||
@@ -2626,6 +2668,17 @@ class Download_File(Cmdlet):
|
|||||||
|
|
||||||
if not raw_url and not piped_items:
|
if not raw_url and not piped_items:
|
||||||
return int(streaming_exit_code or 0)
|
return int(streaming_exit_code or 0)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
skip_direct = bool(config.get("_skip_direct_on_streaming_failure")) if isinstance(config, dict) else False
|
||||||
|
except Exception:
|
||||||
|
skip_direct = False
|
||||||
|
if matched_ytdlp:
|
||||||
|
skip_direct = True
|
||||||
|
if skip_direct:
|
||||||
|
raw_url = [u for u in raw_url if u not in supported_streaming]
|
||||||
|
if not raw_url and not piped_items:
|
||||||
|
return int(streaming_exit_code or 1)
|
||||||
|
|
||||||
# Re-check picker if partial processing occurred
|
# Re-check picker if partial processing occurred
|
||||||
picker_result = self._maybe_show_provider_picker(
|
picker_result = self._maybe_show_provider_picker(
|
||||||
|
|||||||
Reference in New Issue
Block a user