BREAKING CHANGE: PlayReady users without explicit playready_devices no longer get access to all devices by default.
Key changes:
- feat(drm): add MonaLisa DRM support to core infrastructure
- feat(cdm): add remote PlayReady CDM support via pyplayready RemoteCdm
- feat(serve): add PlayReady CDM support alongside Widevine
- feat(gluetun): Gluetun VPN integration and Windscribe support
- feat(audio): codec lists and split muxing
- feat(tracks): prioritize Atmos audio tracks over higher bitrate non-Atmos
- feat(video): detect interlaced scan type from MPD manifests
- feat(cdm): normalize CDM detection for local and remote implementations
- fix(serve)!: make PlayReady users config consistently a mapping
- 50+ additional bug fixes across HLS/DASH, proxies, subtitles, and more
Remove unreachable fallback to all devices; if a user has no explicit playready_devices configured, the PlayReady subapp receives an empty list (secure-by-default).
Add unshackle.core.cdm.detect helpers to classify CDMs consistently across local and remote backends.
- Add is_playready_cdm/is_widevine_cdm for DRM selection across pyplayready, pywidevine, and wrappers
- Add is_remote_cdm/is_local_cdm/cdm_location so services can branch on CDM execution location
- Switch core DASH/HLS parsing, track DRM selection, and dl CDM switching away from brittle isinstance/DecryptLabs-only checks
- Make unshackle.core.cdm import-light via lazy __getattr__ so optional CDM deps are only imported when needed
Proxy URIs may contain embedded userinfo (username/password). Add a small sanitizer helper and use it for proxy mapping and proxy selection logs to avoid leaking credentials.
Title filenames now include resolution/service/WEB-DL/codecs/HDR tokens in both modes; scene_naming only changes the spacer ('.' vs ' ').
Also avoid overwriting muxed outputs by disambiguating on collision (append codec suffix when needed, then a numeric suffix).
Ensure dynamic-range tokens use safe fallback when not in DYNAMIC_RANGE_MAP and append exactly one space-separated token without trailing/double spaces.
- Parse init section byterange offset as int to avoid string arithmetic bugs
- Wrap MonaLisa licensing in the same progress + error handling flow as Widevine/PlayReady
When --a-lang is specific (not best/all) and multiple codecs are requested via -a/--acodec, select only the best-bitrate track per codec per language (plus descriptive if --audio-description).
Blame: regression introduced by 939ca25 (fix(dl): keep descriptive and standard audio for requested langs).
- Pass ML-Worker key via env/stdin instead of argv to reduce exposure in process listings/logs.
- Add a hard timeout to the ML-Worker subprocess call and convert timeouts into DecryptionFailed errors.
- Make ticket bytes decoding defensive: try UTF-8, fall back to ASCII (base64), otherwise raise a descriptive ValueError.
- Switch docker run to use a temporary --env-file instead of per-var -e flags\n- Ensure temp env file is always removed (best-effort overwrite + unlink)\n- Tighten _is_container_running to exact-name matching via anchored docker filter\n- Close requests.Session used for IP verification to release connections\n- Redact more secret-like env keys in debug logs\n
Ensure playready_config['users'] and API-only config always use a dict, even under --no-key, to avoid type mismatches.
Also stop implicitly granting PlayReady access by defaulting per-user 'playready_devices' to all devices; missing 'playready_devices' now defaults to an empty list and logs a warning including the user key.
BREAKING CHANGE: users without an explicit 'playready_devices' list no longer get access to all PlayReady devices by default.
When ensure_started() is called while aria2c is already running, it now compares the requested proxy/max_workers against the values the process was started with and logs a warning if they differ (since the running process cannot be reconfigured in-place). Startup no longer uses a fixed sleep; instead it probes the JSON-RPC endpoint with a bounded retry loop (aria2.getVersion) and only proceeds once RPC is responsive, terminating the subprocess and raising on timeout.
- Add a small helper to move N_m3u8DL-RE final outputs into the expected temp path (preserve actual suffix) and keep subtitle codec consistent with the produced file.
- Skip generic HLS segment merging when N_m3u8DL-RE is in use to avoid mixing in sidecar files and reduce Windows file-lock issues.
- Harden segmented WebVTT merging to avoid IndexError when caption segment indexes exceed the provided duration list.
When --audio-description is set, keep standard selections and include descriptive tracks for requested languages, including --a-lang with orig and best selection paths.
Fixes#72
Set DOWNLOAD_LICENCE_ONLY earlier in the download command so services build tracks in license-only mode.
Update Attachment URL handling to avoid eager downloads in license-only mode while keeping metadata, stable IDs, and safe cleanup behavior.