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:
2026-04-19 00:41:09 -07:00
parent d9e736172a
commit bafd37fdfb
50 changed files with 3258 additions and 4177 deletions
+75 -67
View File
@@ -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):