huge refactor of the entire codebase, with the goal of improving maintainability, readability, and extensibility. This commit includes changes to almost every file in the project, including:
This commit is contained in:
+75
-67
@@ -10,17 +10,15 @@ from datetime import datetime, timedelta
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
from pathlib import Path
|
||||
from SYS.cmdlet_spec import Cmdlet, CmdletArg, parse_cmdlet_args
|
||||
from Provider.tidal_manifest import resolve_tidal_manifest_path
|
||||
from ProviderCore.registry import get_plugin_for_url
|
||||
from SYS.logger import debug, get_thread_stream, is_debug_enabled, set_debug, set_thread_stream
|
||||
from SYS.result_table import Table
|
||||
from MPV.mpv_ipc import MPV
|
||||
from SYS import pipeline as ctx
|
||||
from SYS.models import PipeObject
|
||||
|
||||
from SYS.config import get_hydrus_access_key, get_hydrus_url
|
||||
from SYS.config import get_hydrus_access_key, get_hydrus_url, resolve_cookies_path
|
||||
|
||||
_ALLDEBRID_UNLOCK_CACHE: Dict[str,
|
||||
str] = {}
|
||||
_NOTES_PREFETCH_INFLIGHT: set[str] = set()
|
||||
_NOTES_PREFETCH_LOCK = threading.Lock()
|
||||
_PLAYLIST_STORE_CACHE: Optional[Dict[str, Any]] = None
|
||||
@@ -478,73 +476,85 @@ def _try_enable_mpv_file_logging(mpv_log_path: str, *, attempts: int = 3) -> boo
|
||||
return bool(ok)
|
||||
|
||||
|
||||
def _get_alldebrid_api_key(config: Optional[Dict[str, Any]]) -> Optional[str]:
|
||||
def _iter_plugin_targets(item: Any) -> List[str]:
|
||||
values: List[str] = []
|
||||
seen: set[str] = set()
|
||||
|
||||
def _add(candidate: Any) -> None:
|
||||
text = str(candidate or "").strip()
|
||||
if not text or text in seen:
|
||||
return
|
||||
seen.add(text)
|
||||
values.append(text)
|
||||
|
||||
try:
|
||||
if not isinstance(config, dict):
|
||||
return None
|
||||
provider_cfg = config.get("provider")
|
||||
if not isinstance(provider_cfg, dict):
|
||||
return None
|
||||
ad_cfg = provider_cfg.get("alldebrid")
|
||||
if not isinstance(ad_cfg, dict):
|
||||
return None
|
||||
key = ad_cfg.get("api_key")
|
||||
if not isinstance(key, str):
|
||||
return None
|
||||
key = key.strip()
|
||||
return key or None
|
||||
if isinstance(item, dict):
|
||||
_add(item.get("path"))
|
||||
_add(item.get("url"))
|
||||
_add(item.get("source_url"))
|
||||
_add(item.get("target"))
|
||||
metadata = item.get("full_metadata") or item.get("metadata")
|
||||
else:
|
||||
_add(getattr(item, "path", None))
|
||||
_add(getattr(item, "url", None))
|
||||
_add(getattr(item, "source_url", None))
|
||||
_add(getattr(item, "target", None))
|
||||
metadata = getattr(item, "full_metadata", None) or getattr(item, "metadata", None)
|
||||
if isinstance(metadata, dict):
|
||||
_add(metadata.get("url"))
|
||||
_add(metadata.get("webpage_url"))
|
||||
_add(metadata.get("source_url"))
|
||||
extra = item.get("extra") if isinstance(item, dict) else getattr(item, "extra", None)
|
||||
if isinstance(extra, dict):
|
||||
_add(extra.get("url"))
|
||||
_add(extra.get("source_url"))
|
||||
except Exception:
|
||||
return None
|
||||
return values
|
||||
|
||||
return values
|
||||
|
||||
|
||||
def _is_alldebrid_protected_url(url: str) -> bool:
|
||||
def _resolve_plugin_url(url: str, config: Optional[Dict[str, Any]]) -> str:
|
||||
target = str(url or "").strip()
|
||||
if not target:
|
||||
return target
|
||||
|
||||
try:
|
||||
if not isinstance(url, str):
|
||||
return False
|
||||
u = url.strip()
|
||||
if not u.startswith(("http://", "https://")):
|
||||
return False
|
||||
p = urlparse(u)
|
||||
host = (p.netloc or "").lower()
|
||||
path = p.path or ""
|
||||
# AllDebrid file page links (require auth; not directly streamable by mpv)
|
||||
return host == "alldebrid.com" and path.startswith("/f/")
|
||||
plugin = get_plugin_for_url(target, config or {})
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _maybe_unlock_alldebrid_url(url: str, config: Optional[Dict[str, Any]]) -> str:
|
||||
"""Convert AllDebrid protected file URLs into direct streamable links.
|
||||
|
||||
When AllDebrid returns `https://alldebrid.com/f/...`, that URL typically requires
|
||||
authentication. MPV cannot access it without credentials. We transparently call
|
||||
the AllDebrid API `link/unlock` (using the configured API key) to obtain a direct
|
||||
URL that MPV can stream.
|
||||
"""
|
||||
if not _is_alldebrid_protected_url(url):
|
||||
return url
|
||||
|
||||
cached = _ALLDEBRID_UNLOCK_CACHE.get(url)
|
||||
if isinstance(cached, str) and cached:
|
||||
return cached
|
||||
|
||||
api_key = _get_alldebrid_api_key(config)
|
||||
if not api_key:
|
||||
return url
|
||||
plugin = None
|
||||
if plugin is None:
|
||||
return target
|
||||
|
||||
try:
|
||||
from API.alldebrid import AllDebridClient
|
||||
resolved = plugin.resolve_url(target)
|
||||
except Exception as exc:
|
||||
debug(f"Plugin URL resolution failed for {target}: {exc}", file=sys.stderr)
|
||||
return target
|
||||
|
||||
client = AllDebridClient(api_key)
|
||||
unlocked = client.unlock_link(url)
|
||||
if isinstance(unlocked, str) and unlocked.strip():
|
||||
unlocked = unlocked.strip()
|
||||
_ALLDEBRID_UNLOCK_CACHE[url] = unlocked
|
||||
return unlocked
|
||||
except Exception as e:
|
||||
debug(f"AllDebrid unlock failed for MPV target: {e}", file=sys.stderr)
|
||||
return str(resolved or target)
|
||||
|
||||
return url
|
||||
|
||||
def _resolve_plugin_playback_path(item: Any, config: Optional[Dict[str, Any]]) -> Optional[str]:
|
||||
for candidate in _iter_plugin_targets(item):
|
||||
try:
|
||||
plugin = get_plugin_for_url(candidate, config or {})
|
||||
except Exception:
|
||||
plugin = None
|
||||
if plugin is None:
|
||||
continue
|
||||
|
||||
try:
|
||||
resolved = plugin.resolve_playback_path(item)
|
||||
except Exception as exc:
|
||||
debug(f"Plugin playback-path resolution failed for {candidate}: {exc}", file=sys.stderr)
|
||||
continue
|
||||
|
||||
text = str(resolved or "").strip()
|
||||
if text:
|
||||
return text
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _ensure_lyric_overlay(mpv: MPV) -> None:
|
||||
@@ -1078,9 +1088,7 @@ def _ensure_ytdl_cookies(config: Optional[Dict[str, Any]] = None) -> None:
|
||||
|
||||
cookies_path = None
|
||||
try:
|
||||
from tool.ytdlp import YtDlpTool
|
||||
|
||||
cookiefile = YtDlpTool(config or {}).resolve_cookiefile()
|
||||
cookiefile = resolve_cookies_path(config or {})
|
||||
if cookiefile is not None:
|
||||
cookies_path = str(cookiefile)
|
||||
except Exception:
|
||||
@@ -1326,7 +1334,7 @@ def _get_playable_path(
|
||||
"none"}:
|
||||
path = None
|
||||
|
||||
manifest_path = resolve_tidal_manifest_path(item)
|
||||
manifest_path = _resolve_plugin_playback_path(item, config)
|
||||
if manifest_path:
|
||||
path = manifest_path
|
||||
else:
|
||||
@@ -1542,7 +1550,7 @@ def _queue_items(
|
||||
# If the target is an AllDebrid protected file URL, unlock it to a direct link for MPV.
|
||||
try:
|
||||
if isinstance(target, str):
|
||||
target = _maybe_unlock_alldebrid_url(target, config)
|
||||
target = _resolve_plugin_url(target, config)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -2591,7 +2599,7 @@ def _start_mpv(
|
||||
try:
|
||||
needs_mpd_whitelist = False
|
||||
for it in items or []:
|
||||
mpd = resolve_tidal_manifest_path(it)
|
||||
mpd = _resolve_plugin_playback_path(it, config)
|
||||
candidate = mpd
|
||||
if not candidate:
|
||||
if isinstance(it, dict):
|
||||
|
||||
Reference in New Issue
Block a user