diff --git a/unshackle/commands/dl.py b/unshackle/commands/dl.py index 27d571f..7bc7fda 100644 --- a/unshackle/commands/dl.py +++ b/unshackle/commands/dl.py @@ -718,7 +718,6 @@ class dl: for name, binary in [ ("shaka_packager", binaries.ShakaPackager), ("mp4decrypt", binaries.Mp4decrypt), - ("n_m3u8dl_re", binaries.N_m3u8DL_RE), ("mkvmerge", binaries.MKVToolNix), ("ffmpeg", binaries.FFMPEG), ("ffprobe", binaries.FFProbe), @@ -744,11 +743,6 @@ class dl: output = (r.stdout or "") + (r.stderr or "") lines = [line.strip() for line in output.split("\n") if line.strip()] version = " | ".join(lines[:2]) if lines else None - elif name == "n_m3u8dl_re": - r = subprocess.run( - [str(binary), "--version"], capture_output=True, text=True, timeout=5 - ) - version = (r.stdout or r.stderr or "").strip().split("\n")[0] except Exception: version = "" binary_versions[name] = {"path": str(binary), "version": version} diff --git a/unshackle/commands/env.py b/unshackle/commands/env.py index adc48d4..dced7c0 100644 --- a/unshackle/commands/env.py +++ b/unshackle/commands/env.py @@ -68,15 +68,6 @@ def check() -> None: "desc": "HDR10+ metadata", "cat": "HDR", }, - # Downloaders - {"name": "aria2c", "binary": binaries.Aria2, "required": False, "desc": "Multi-thread DL", "cat": "Download"}, - { - "name": "N_m3u8DL-RE", - "binary": binaries.N_m3u8DL_RE, - "required": False, - "desc": "HLS/DASH/ISM", - "cat": "Download", - }, # Subtitle Tools { "name": "SubtitleEdit", diff --git a/unshackle/core/api/handlers.py b/unshackle/core/api/handlers.py index e2e8999..731f2aa 100644 --- a/unshackle/core/api/handlers.py +++ b/unshackle/core/api/handlers.py @@ -413,7 +413,7 @@ def serialize_video_track(track: Video, include_url: bool = False) -> Dict[str, codec_name = track.codec.name if hasattr(track.codec, "name") else str(track.codec) range_name = track.range.name if hasattr(track.range, "name") else str(track.range) - # Get descriptor for N_m3u8DL-RE compatibility (HLS, DASH, URL, etc.) + # Serialize the manifest descriptor (HLS, DASH, URL, etc.) descriptor_name = None if hasattr(track, "descriptor") and track.descriptor: descriptor_name = track.descriptor.name if hasattr(track.descriptor, "name") else str(track.descriptor) @@ -442,7 +442,7 @@ def serialize_audio_track(track: Audio, include_url: bool = False) -> Dict[str, """Convert audio track to JSON-serializable dict.""" codec_name = track.codec.name if hasattr(track.codec, "name") else str(track.codec) - # Get descriptor for N_m3u8DL-RE compatibility + # Serialize the manifest descriptor (HLS, DASH, URL, etc.) descriptor_name = None if hasattr(track, "descriptor") and track.descriptor: descriptor_name = track.descriptor.name if hasattr(track.descriptor, "name") else str(track.descriptor) diff --git a/unshackle/core/binaries.py b/unshackle/core/binaries.py index 598387c..dd0a114 100644 --- a/unshackle/core/binaries.py +++ b/unshackle/core/binaries.py @@ -45,12 +45,10 @@ ShakaPackager = find( f"packager-{__shaka_platform}-arm64", f"packager-{__shaka_platform}-x64", ) -Aria2 = find("aria2c", "aria2") CCExtractor = find("ccextractor", "ccextractorwin", "ccextractorwinfull") HolaProxy = find("hola-proxy") MPV = find("mpv") Caddy = find("caddy") -N_m3u8DL_RE = find("N_m3u8DL-RE", "n-m3u8dl-re") MKVToolNix = find("mkvmerge") Mkvpropedit = find("mkvpropedit") DoviTool = find("dovi_tool") @@ -66,12 +64,10 @@ __all__ = ( "FFPlay", "SubtitleEdit", "ShakaPackager", - "Aria2", "CCExtractor", "HolaProxy", "MPV", "Caddy", - "N_m3u8DL_RE", "MKVToolNix", "Mkvpropedit", "DoviTool", diff --git a/unshackle/core/manifests/hls.py b/unshackle/core/manifests/hls.py index a953659..3f2e957 100644 --- a/unshackle/core/manifests/hls.py +++ b/unshackle/core/manifests/hls.py @@ -490,39 +490,6 @@ class HLS: return None - @staticmethod - def _finalize_n_m3u8dl_re_output(*, track: AnyTrack, save_dir: Path, save_path: Path) -> Path: - """ - Finalize output from N_m3u8DL-RE. - - We call N_m3u8DL-RE with `--save-name track.id`, so the final file should be `{track.id}.*` under `save_dir`. - This moves that output to `save_path` (preserving the real suffix) and, for subtitles, updates `track.codec` - to match the produced file extension. - """ - matches = [p for p in save_dir.rglob(f"{track.id}.*") if p.is_file()] - if not matches: - raise FileNotFoundError(f"No output files produced by N_m3u8DL-RE for save-name={track.id} in: {save_dir}") - - primary = max(matches, key=lambda p: p.stat().st_size) - - final_save_path = save_path.with_suffix(primary.suffix) if primary.suffix else save_path - - final_save_path.parent.mkdir(parents=True, exist_ok=True) - if primary.absolute() != final_save_path.absolute(): - final_save_path.unlink(missing_ok=True) - shutil.move(str(primary), str(final_save_path)) - - if isinstance(track, Subtitle): - ext = final_save_path.suffix.lower().lstrip(".") - try: - track.codec = Subtitle.Codec.from_mime(ext) - except ValueError: - pass - - shutil.rmtree(save_dir, ignore_errors=True) - - return final_save_path - @staticmethod def download_track( track: AnyTrack, diff --git a/unshackle/unshackle-example.yaml b/unshackle/unshackle-example.yaml index 4bc7dcb..6e63ae8 100644 --- a/unshackle/unshackle-example.yaml +++ b/unshackle/unshackle-example.yaml @@ -395,29 +395,13 @@ key_vaults: # no_push: false # Default behavior - vault both provides and receives keys # Choose what software to use to download data -downloader: aria2c -# Options: requests | aria2c | curl_impersonate | n_m3u8dl_re -# Can also be a mapping: -# downloader: -# NF: requests -# AMZN: n_m3u8dl_re -# DSNP: n_m3u8dl_re -# default: requests +downloader: requests +# Options: requests +# Downloading now uses the unified in-process requests/rnet downloader; the legacy +# aria2c, curl_impersonate, and n_m3u8dl_re backends have been removed. -# aria2c downloader configuration -aria2c: - max_concurrent_downloads: 4 - max_connection_per_server: 3 - split: 5 - file_allocation: falloc # none | prealloc | falloc | trunc - -# N_m3u8DL-RE downloader configuration -n_m3u8dl_re: - thread_count: 16 - ad_keyword: "advertisement" - use_proxy: true - -# curl_impersonate downloader configuration +# rnet TLS impersonation preset (not a downloader). Selects the browser +# fingerprint the HTTP session impersonates. curl_impersonate: browser: chrome120 @@ -555,9 +539,8 @@ remote_services: # Per-service overrides for remote services # Override downloader, decryption tool, or CDM settings per service on the client services: - # Example: Use n_m3u8dl_re for HLS services, mp4decrypt for specific services + # Example: Override the decryption tool for specific services # EXAMPLE_SERVICE: - # downloader: n_m3u8dl_re # Override client downloader (requests, aria2c, n_m3u8dl_re, curl_impersonate) # decryption: mp4decrypt # Override decryption tool (shaka, mp4decrypt) # Example: Multiple servers @@ -567,7 +550,6 @@ remote_services: # server_cdm: true # services: # EXAMPLE: - # downloader: n_m3u8dl_re # decryption: mp4decrypt # eu-server: # url: "https://eu.example.com:8786" @@ -581,7 +563,7 @@ services: # You can override ANY global configuration option on a per-service basis # This allows fine-tuned control for services with special requirements - # Supported overrides: dl, aria2c, n_m3u8dl_re, curl_impersonate, subtitle, muxing, headers, etc. + # Supported overrides: dl, curl_impersonate, subtitle, muxing, headers, etc. # Example: Comprehensive service configuration showing all features EXAMPLE: @@ -627,20 +609,6 @@ services: lang: ["en", "es-419"] # Different language priority for this service sub_format: srt # Force SRT subtitle format - # Override n_m3u8dl_re downloader settings - n_m3u8dl_re: - thread_count: 8 # Lower thread count for rate-limited service (global default: 16) - use_proxy: true # Force proxy usage for this service - retry_count: 10 # More retries for unstable connections - ad_keyword: "advertisement" # Service-specific ad filtering - - # Override aria2c downloader settings - aria2c: - max_concurrent_downloads: 2 # Limit concurrent downloads (global default: 4) - max_connection_per_server: 1 # Single connection per server - split: 3 # Fewer splits (global default: 5) - file_allocation: none # Faster allocation for this service - # Override subtitle processing for this service subtitle: conversion_method: pycaption # Use specific subtitle converter @@ -675,18 +643,12 @@ services: dl: downloads: 2 # Limit concurrent downloads workers: 4 # Reduce workers to avoid rate limits - n_m3u8dl_re: - thread_count: 4 # Very low thread count - retry_count: 20 # More retries for flaky service - aria2c: - max_concurrent_downloads: 1 # Download tracks one at a time - max_connection_per_server: 1 # Single connection only # Notes on service-specific overrides: # - Overrides are merged with global config, not replaced # - Only specified keys are overridden, others use global defaults # - Reserved keys (profiles, api_key, certificate, etc.) are NOT treated as overrides - # - Any dict-type config option can be overridden (dl, aria2c, n_m3u8dl_re, subtitle, etc.) + # - Any dict-type config option can be overridden (dl, subtitle, muxing, headers, etc.) # - CLI arguments always take priority over service-specific config # External proxy provider services