From c724cb36b1dee1435f2c181c7c00f19abc2705d6 Mon Sep 17 00:00:00 2001 From: Nose Date: Sun, 26 Apr 2026 13:48:18 -0700 Subject: [PATCH] alldebrid plugin optimization and mpv playlist fix --- API/data/alldebrid.json | 6 +-- MPV/LUA/main.lua | 7 ++- MPV/portable_config/mpv.conf | 13 +++--- Provider/alldebrid.py | 44 ++++++++++++------- SYS/config.py | 83 ++++++++++++++++++++++++++++++++++-- cmdlet/_shared.py | 14 +++++- cmdnat/pipe.py | 11 ++++- 7 files changed, 147 insertions(+), 31 deletions(-) diff --git a/API/data/alldebrid.json b/API/data/alldebrid.json index 55e2a61..8cac978 100644 --- a/API/data/alldebrid.json +++ b/API/data/alldebrid.json @@ -375,7 +375,7 @@ "(filespace\\.com/[a-zA-Z0-9]{12})" ], "regexp": "(filespace\\.com/fd/([a-zA-Z0-9]{12}))|((filespace\\.com/[a-zA-Z0-9]{12}))", - "status": true + "status": false }, "filezip": { "name": "filezip", @@ -463,7 +463,7 @@ "isra\\.cloud/\\?op=report_file&id=([0-9a-zA-Z]{12})" ], "regexp": "((isra\\.cloud/[0-9a-zA-Z]{12}))|(isra\\.cloud/\\?op=report_file&id=([0-9a-zA-Z]{12}))", - "status": true, + "status": false, "hardRedirect": [ "isra\\.cloud/([0-9a-zA-Z]{12})" ] @@ -494,7 +494,7 @@ "mediafire\\.com/(\\?|download/|file/|download\\.php\\?)([0-9a-z]{15})" ], "regexp": "mediafire\\.com/(\\?|download/|file/|download\\.php\\?)([0-9a-z]{15})", - "status": false + "status": true }, "mixdrop": { "name": "mixdrop", diff --git a/MPV/LUA/main.lua b/MPV/LUA/main.lua index b23bd94..b31d51b 100644 --- a/MPV/LUA/main.lua +++ b/MPV/LUA/main.lua @@ -5683,13 +5683,18 @@ mp.register_event('file-loaded', function() mp.add_timeout(0.1, function() M._reset_uosc_input_state('file-loaded-web') end) + + _format_cache_poll_generation = _format_cache_poll_generation + 1 + if _extract_store_hash(url) or not _is_ytdlp_url(url) then + return + end + local ok, err = _cache_formats_from_current_playback('file-loaded') if ok then _lua_log('formats: file-loaded cache succeeded for url=' .. url) else _lua_log('formats: file-loaded cache pending reason=' .. tostring(err or 'unknown')) end - _format_cache_poll_generation = _format_cache_poll_generation + 1 _schedule_playback_format_cache_poll(url, _format_cache_poll_generation, 1) _prefetch_formats_for_url(url) end) diff --git a/MPV/portable_config/mpv.conf b/MPV/portable_config/mpv.conf index cb51b7c..3ac7bea 100644 --- a/MPV/portable_config/mpv.conf +++ b/MPV/portable_config/mpv.conf @@ -7,13 +7,14 @@ border=no cache=yes # Give HTTP store streams more room to absorb Hydrus/network jitter before # mpv restarts audio after an underrun. -cache-secs=90 +cache-secs=300 cache-pause=yes -cache-pause-wait=12 -demuxer-readahead-secs=90 -demuxer-max-bytes=512MiB -demuxer-max-back-bytes=256MiB -audio-buffer=1.0 +cache-pause-initial=yes +cache-pause-wait=30 +demuxer-readahead-secs=300 +demuxer-max-bytes=1GiB +demuxer-max-back-bytes=512MiB +audio-buffer=2.0 # Ensure uosc texture/icon fonts are discoverable by libass. osd-fonts-dir=~~/scripts/uosc/fonts diff --git a/Provider/alldebrid.py b/Provider/alldebrid.py index 1f676c1..e147995 100644 --- a/Provider/alldebrid.py +++ b/Provider/alldebrid.py @@ -1248,11 +1248,15 @@ class AllDebrid(TableProviderMixin, Provider): log(f"AllDebrid magnet {magnet_id} has no downloadable files", file=sys.stderr) return 0 - try: - if progress is not None and hasattr(progress, "begin_steps"): - progress.begin_steps(total_files) - except Exception: - pass + magnet_path_metadata: Dict[str, Any] = {} + magnet_folder_name = str( + magnet_files.get("filename") + or magnet_files.get("name") + or magnet_files.get("hash") + or f"magnet-{magnet_id}" + ).strip() + if magnet_folder_name: + magnet_path_metadata["folder"] = magnet_folder_name downloaded = 0 for file_idx, node in enumerate(file_entries, 1): @@ -1261,8 +1265,10 @@ class AllDebrid(TableProviderMixin, Provider): relpath = str(node.get("_relpath") or file_name or "").strip() try: - if progress is not None and hasattr(progress, "step"): - progress.step(f"file {file_idx}/{total_files}: {relpath or file_name or 'download'}") + if progress is not None and hasattr(progress, "set_status"): + progress.set_status( + f"downloading file {file_idx}/{total_files}: {relpath or file_name or 'download'}" + ) except Exception: pass @@ -1282,21 +1288,23 @@ class AllDebrid(TableProviderMixin, Provider): except Exception as exc: debug(f"[alldebrid] unlock_link failed: {exc}, trying locked URL") - target_path = output_dir rel_path_obj = Path(relpath) - if rel_path_obj.parent: - target_path = output_dir / rel_path_obj.parent - try: - target_path.mkdir(parents=True, exist_ok=True) - except Exception: - target_path = output_dir + target_path = adjust_output_dir_for_alldebrid( + output_dir, + {**magnet_path_metadata, "relpath": relpath}, + magnet_files, + ) + + suggested_name = sanitize_filename(rel_path_obj.name) or sanitize_filename(file_name) + if not suggested_name: + suggested_name = rel_path_obj.name or file_name or f"file-{file_idx}" try: result_obj = _download_direct_file( file_url, target_path, quiet=quiet_mode, - suggested_filename=rel_path_obj.name, + suggested_filename=suggested_name, pipeline_progress=progress, ) except Exception as exc: @@ -1315,6 +1323,12 @@ class AllDebrid(TableProviderMixin, Provider): if downloaded == 0: log(f"AllDebrid magnet {magnet_id} produced no downloads", file=sys.stderr) + try: + if progress is not None and hasattr(progress, "clear_status"): + progress.clear_status() + except Exception: + pass + return downloaded def search( diff --git a/SYS/config.py b/SYS/config.py index fad4ac3..f244246 100644 --- a/SYS/config.py +++ b/SYS/config.py @@ -6,6 +6,7 @@ import json import sqlite3 import time import os +import re import datetime import sys import tempfile @@ -30,6 +31,7 @@ _LAST_SAVED_CONFIG: Dict[str, Any] = {} _CONFIG_SAVE_MAX_RETRIES = 5 _CONFIG_SAVE_RETRY_DELAY = 0.15 _CONFIG_MISSING = object() +_PATH_ALIAS_TOKEN_RE = re.compile(r"^\$(?:\((?P[^)]+)\)|(?P[A-Za-z0-9_.-]+))$") class ConfigSaveConflict(Exception): @@ -62,6 +64,20 @@ def global_config() -> List[Dict[str, Any]]: "group": "Display", "default": "rainbow", "choices": ["plain", "bw-striped", "rainbow"], + }, + { + "key": "download_path_default", + "label": "Default Download Path", + "group": "Downloads", + "type": "string", + "default": "", + }, + { + "key": "path_aliases", + "label": "Path Aliases", + "group": "Downloads", + "type": "json", + "default": {}, } ] @@ -82,6 +98,56 @@ def get_nested_config_value(config: Dict[str, Any], *path: str) -> Any: return cur +def _normalize_path_alias_name(value: Any) -> Optional[str]: + raw = str(value or "").strip() + if not raw: + return None + + match = _PATH_ALIAS_TOKEN_RE.match(raw) + if match: + raw = str(match.group("braced") or match.group("plain") or "").strip() + + candidate = raw.strip().strip("()") + if not candidate: + return None + return candidate.lower() + + +def get_path_aliases(config: Dict[str, Any]) -> Dict[str, str]: + aliases: Dict[str, str] = {} + if not isinstance(config, dict): + return aliases + + for block_name in ("path_aliases", "download_paths"): + block = config.get(block_name) + if not isinstance(block, dict): + continue + for key, value in block.items(): + alias = _normalize_path_alias_name(key) + if not alias: + continue + if isinstance(value, str) and value.strip(): + aliases[alias] = value.strip() + + return aliases + + +def resolve_path_alias(config: Dict[str, Any], value: Any) -> Optional[Path]: + raw = str(value or "").strip() + if not raw.startswith("$"): + return None + + alias = _normalize_path_alias_name(raw) + if not alias: + return None + + target = get_path_aliases(config).get(alias) + if not target: + return None + + return expand_path(target) + + def coerce_config_value( value: Any, existing_value: Any = _CONFIG_MISSING, @@ -271,13 +337,24 @@ def resolve_output_dir(config: Dict[str, Any]) -> Path: """Resolve output directory from config with single source of truth. Priority: - 1. config["temp"] - explicitly set temp/output directory - 2. config["outfile"] - fallback to outfile setting - 3. System Temp - default fallback directory + 1. config["download_path_default"] - default download/output directory + 2. config["temp"] - explicitly set temp/output directory + 3. config["outfile"] - fallback to outfile setting + 4. System Temp - default fallback directory Returns: Path to output directory """ + default_output = config.get("download_path_default") + if default_output: + try: + aliased = resolve_path_alias(config, default_output) + path = aliased if aliased is not None else expand_path(default_output) + if path.exists() or path.parent.exists(): + return path + except Exception as exc: + logger.debug("resolve_output_dir: failed to expand download_path_default value %r: %s", default_output, exc, exc_info=True) + # First try explicit temp setting from config temp_value = config.get("temp") if temp_value: diff --git a/cmdlet/_shared.py b/cmdlet/_shared.py index 7783785..244e043 100644 --- a/cmdlet/_shared.py +++ b/cmdlet/_shared.py @@ -804,7 +804,19 @@ def resolve_target_dir( target = parsed.get("path") if target: try: - p = Path(str(target)).expanduser().resolve() + from SYS.config import resolve_path_alias + from SYS.utils import expand_path + + raw_target = str(target or "").strip() + aliased = resolve_path_alias(config, raw_target) + if raw_target.startswith("$") and aliased is None: + log( + f"Unknown path alias {raw_target}. Set it with .config path_aliases. ", + file=sys.stderr, + ) + return None + + p = aliased if aliased is not None else expand_path(raw_target) if handle_creations: p.mkdir(parents=True, exist_ok=True) return p diff --git a/cmdnat/pipe.py b/cmdnat/pipe.py index 2fe2023..c9176eb 100644 --- a/cmdnat/pipe.py +++ b/cmdnat/pipe.py @@ -1454,6 +1454,13 @@ def _queue_items( except Exception: pass + # Batched queue operations should not block on one IPC reply per item. + # On Windows named pipes, waiting for every `loadfile`/`loadlist` ack can + # make multi-select `.mpv` calls stall for several seconds. + command_wait = bool(wait) + if len(items) > 1: + command_wait = False + # Just verify cookies are configured, don't try to set via IPC _ensure_ytdl_cookies(config) @@ -1683,8 +1690,8 @@ def _queue_items( "request_id": 200 } try: - debug(f"Sending MPV {command_name}: {target_to_send} mode={mode} wait={wait}") - resp = _send_ipc_command(cmd, silent=True, wait=wait) + debug(f"Sending MPV {command_name}: {target_to_send} mode={mode} wait={command_wait}") + resp = _send_ipc_command(cmd, silent=True, wait=command_wait) debug(f"MPV {command_name} response: {resp}") except Exception as e: debug(f"Exception sending {command_name} to MPV: {e}", file=sys.stderr)