Commit Graph

548 Commits

Author SHA1 Message Date
Andy
99be88dc08 feat(session): replace curl_cffi with rnet for TLS-fingerprinted HTTP
Replace CurlSession (curl_cffi) with RnetSession powered by rnet (Rust/BoringSSL). Benchmarks show 3.5x faster segmented downloads (1.06 GB/s vs 304 MB/s) and 16% faster single-file downloads with near-zero TLS fingerprinting overhead.

- Add RnetSession wrapper with requests-compatible API (headers, cookies, proxies, retry logic, prepared requests)
- Add RnetResponse wrapper normalizing rnet quirks (status_code as int, text as property, bytes-to-str headers, iter_content re-chunking)
- Replace CurlSession isinstance checks across manifests, tracks, DRM
- Update downloader with rnet native streaming path and byte-based progress tracking for accurate Rich progress bars
- Add speed display column to Rich progress bar (DASH/HLS/URL prefix)
- Add rnet dependency, services use exact preset names (e.g. OkHttp4_12)
2026-03-24 10:08:17 -06:00
Andy
6840944738 perf(downloader): optimize hot loop and threading efficiency
Replace list.pop(0) with deque.popleft() for O(1) speed tracker eviction, skip urllib3 decode chain with decode_content=False on raw reads, use running total instead of sum() for progress reporting, add explicit stream.close() on CurlSession path, replace busy-poll loop with concurrent.futures.wait(FIRST_COMPLETED), skip ThreadPoolExecutor for single-URL downloads, and DRY up duplicated raw/iter_content progress logic into a unified chunk iterator.
2026-03-23 18:17:12 -06:00
Andy
006d080416 feat(downloader): optimize download throughput with Queue-based threading and raw reads
Fix critical bug where ThreadPoolExecutor was not actually parallelizing downloads (generator functions returned instantly, I/O ran on main thread).

Performance improvements:
Queue-based event dispatch: workers consume generators in threads, push events to a thread-safe Queue for truly parallel segment downloads
Raw socket reads (resp.raw.read) for requests.Session — 30-35% faster than iter_content, with iter_content fallback for CurlSession
File pre-allocation via truncate when Content-Length is known
Hot loop caching: time.time, f.write, stream.raw.read cached as locals
HTTPAdapter connection pooling mounted on passed sessions for reuse
2026-03-23 17:20:26 -06:00
Andy
732709d3a9 feat(remote): interactive auth handshake, server CDM, cache round-trip, and serve remote-only mode
Add InputBridge for interactive client-server authentication (OTP, device codes, PINs) with async auth via asyncio.to_thread, prompt polling endpoints, and cancellation support.
Server CDM mode detects CDM type from config.cdm per-service, resolves keys server-side, and returns DRM type to client for correct display. Cache files round-trip between client and server on session create/delete. Vault loading fixed for server-side key caching.
HLS/ISM/DASH DRM extraction in license handler. Serve --remote-only mode exposes only session endpoints. Clean connection error handling for unreachable servers.
2026-03-22 22:44:36 -06:00
Andy
1ad226fbcf feat(remote): server vault lookups, service CDM mapping, key display, and service param forwarding 2026-03-20 21:13:56 -06:00
Andy
51d6921eaf fix(drm): include external KID in PSSH when it differs from existing KIDs
When the DASH manifest provides a cenc:default_KID that differs from the PSSH's embedded KIDs, the external KID must be added to the PSSH so the license server returns keys for both. Previously, the PSSH was only modified when all existing KIDs were placeholders, causing a CEKNotFound error when the track's actual encryption KID wasn't in the license response.
2026-03-20 12:48:49 -06:00
Andy
dc197af29e feat(dash): refactor segment extraction and add content period validation 2026-03-20 12:47:49 -06:00
Andy
561a609040 fix(audio): support 'xheaac' profile 2026-03-20 10:34:49 -06:00
Andy
a21c32df5d feat(dl): add --vbitrate-range and --abitrate-range options for bitrate range selection
Allow users to specify a bitrate range (e.g., --abitrate-range 300-400) to filter tracks within that range, with downstream selection picking the highest per language. Mutually exclusive with the existing --vbitrate/--abitrate exact match options.
2026-03-20 10:34:26 -06:00
Andy
2f721266f0 Merge branch 'main' into dev 2026-03-19 20:36:55 -06:00
Andy
faaaf08bd5 fix(drm): add zero-KID fallback for mp4decrypt and clear HLS track.drm after download
mp4decrypt silently copies files unchanged when the tenc box default KID is all zeros, since none of the real KID:KEY pairs match. Add zero-KID fallback entries to both Widevine and PlayReady mp4decrypt methods, matching what Shaka Packager already does.

Also clear track.drm after HLS download when decryption was performed, preventing unnecessary double-decryption. DASH and URL descriptors already did this.
2026-03-19 18:43:43 -06:00
Andy
c323db9481 feat(downloader): consolidate into unified requests-based downloader
Replace 4 separate downloaders (requests, curl_impersonate, aria2c, n_m3u8dl_re) with a single optimized requests downloader with adaptive chunk sizing and session passthrough for TLS fingerprinting support.

- Adaptive chunk sizing (512KB-4MB) based on content length, up from fixed 1KB
- Buffered writes (1MB buffer) for improved I/O throughput
- Session passthrough: accepts both requests.Session and CurlSession
- Per-call speed tracking with rolling window (fixes cross-track speed bleed)
- Worker count default capped at 16
- Removed all downloader.__name__ special-casing from manifest parsers
- Removed aria2c/curl_impersonate/n_m3u8dl_re downloader modules
- Deprecated downloader config key in unshackle.yaml
2026-03-19 18:13:43 -06:00
Andy
4c55f7af5b docs: update API and configuration documentation with example service tags 2026-03-19 12:55:39 -06:00
Andy
e9dbe3f0ac Merge branch 'dev' of https://github.com/unshackle-dl/unshackle into dev 2026-03-19 12:38:40 -06:00
Andy
b3b67b0c96 feat(session): add IP validation for session access and enhance session management 2026-03-19 12:38:33 -06:00
Sp5rky
313efe5b03 Rename remote-services-flow.md to REMOTE-SERVICES-FLOW.md 2026-03-19 12:13:45 -06:00
Andy
9d21d8a246 feat(remote): fetch service list and CLI options from server for --remote help
When using --remote -h, fetch available services from the server's /api/services endpoint instead of listing local services.
2026-03-19 00:06:36 -06:00
Andy
61ee5cb10a feat(remote): add zlib compression for API payloads and gzip transport
Compress manifest XML, cache files, and cookies with zlib before base64 encoding, reducing /tracks payload by 83-95%. Add configurable compression_level in serve config (default: 1, fast).

Enable aiohttp gzip middleware for transport-level compression on all JSON responses. Set User-Agent to unshackle/<version> for remote client requests. Add remote-aware status messages for auth, titles, and tracks.
2026-03-18 23:47:06 -06:00
Andy
b420e2bd08 refactor(remote): deduplicate CDM loading, proxy resolution, and license handling 2026-03-18 22:54:11 -06:00
Andy
b1a5f8f37f feat(remote): add server-CDM mode, manifest transfer, and region-aware proxy
Server-side:
- Add server_cdm mode: server handles full CDM licensing using its own devices, returns KID:KEY pairs instead of raw license bytes
- Support batch license resolution for multiple tracks in one request
- Extract DRM from manifest ContentProtection when track.drm is empty
- Serialize DASH/ISM manifest XML as base64 in /tracks response
- Include session cookies/headers and server_cdm_type in /tracks response
- Detect server CDM type from actual track DRM + configured devices
- Check server region against client_region to skip unnecessary proxy
- Support decrypt_labs and custom_api remote CDMs for both WV and PR

Client-side:
- Re-parse DASH/ISM manifests locally from base64 to populate track.data
- Match remote tracks to re-parsed tracks by ID with attribute fallback
- Copy DRM objects from re-parsed manifests to remote tracks
- Pre-fetch keys via resolve_server_keys() before downloads start
- Fallback per-track licensing via _proxy_license during download
- Apply session cookies/headers from server for CDN access
- Apply downloader/decryption config directly for remote services
- Preserve pre-injected content_keys during DASH DRM override
- Skip redundant CDM calls when all KIDs already have keys

Docs:
- Add comprehensive remote-services-flow.md with Mermaid diagrams covering proxy mode, server-CDM mode, manifest transfer, and config
2026-03-18 19:30:59 -06:00
Andy
7fafdf024c fix(drm): preserve original PSSH for content_id-based Widevine manifests 2026-03-18 11:06:04 -06:00
Andy
96911cb626 Merge branch 'dev' of https://github.com/unshackle-dl/unshackle into dev 2026-03-17 16:54:35 -06:00
Sp5rky
e994c483c5 Merge pull request #92 from unshackle-dl/feat/remote-services
feat(dl): add --remote flag for remote server downloads
2026-03-17 16:53:59 -06:00
Andy
f86d4e7937 docs(dl): add comprehensive list of available dl keys and their descriptions 2026-03-17 09:27:52 -06:00
Andy
d4bc095f96 fix: update actions/checkout to v5 in release workflow 2026-03-17 09:16:46 -06:00
Andy
79e8184474 ci: enable manual triggering of release workflow 4.0.0 2026-03-17 09:10:50 -06:00
Andy
178eed9236 ci: add GitHub Actions release workflow for major/minor versions 2026-03-17 09:08:20 -06:00
Andy
63d2ba60c4 chore(changelog): tag v4.0.0 release 2026-03-17 08:57:34 -06:00
Andy
f46aa9d8c8 chore(changelog): update changelog for upcoming release and reorganize sections 2026-03-17 08:55:14 -06:00
Andy
bfab2ad5c9 fix(serve): use X-Secret-Key header for REST API auth to match pywidevine
Standardize on X-Secret-Key across all endpoints so RemoteClient, pywidevine CDM, and api-only mode all use the same auth header. Adds lightweight middleware for --api-only mode without pywidevine dep.
2026-03-16 19:08:03 -06:00
Andy
b5325c9c47 feat(dl): add --remote flag for downloading via remote unshackle server
Add RemoteService adapter that proxies auth, titles, tracks, and DRM licensing to a remote serve instance while running the full dl pipeline locally. Includes per-session cache isolation, PSSH forwarding for PlayReady/Widevine licensing, and session cleanup on completion.
2026-03-16 18:18:43 -06:00
Andy
b1447eb14b fix(dl): filter CC subtitle languages with --s-lang and extract all manifest CCs
Fixes issues introduced in 15acaea where CC extraction only used the first manifest entry and ignored --s-lang filtering entirely. Now all CC languages from the HLS manifest are iterated and filtered against --s-lang using the same match logic as regular subtitle selection.
2026-03-16 14:09:05 -06:00
Andy
e02aa66843 feat(dl): add --worst flag and SHIELD OkHttp fingerprint preset
Add --worst CLI flag to select the lowest bitrate video track within a specified resolution (e.g. --worst -q 720). Requires -q/--quality.
Add shield_okhttp TLS fingerprint preset for NVIDIA SHIELD Android TV with OkHttp 4.11 JA3 signature.
2026-03-11 13:59:07 -06:00
Sp5rky
c82bb5fe34 Merge pull request #88 from CodeName393/fix-aria2c-progress-bar
fix(aria2c): Correct progress bar tracking for HLS downloads
2026-03-07 20:21:25 -07:00
Andy
ec2ecfe7b4 fix(ism): prevent duplicate track IDs for audio tracks with same lang/codec/bitrate
Include StreamIndex Name and Url attributes in the track ID hash to disambiguate tracks that share the same codec, language, bitrate, and QualityLevel index.
2026-03-07 13:01:36 -07:00
Andy
15acaea208 feat(dl): extract closed captions from HLS manifests and improve CC extraction
- Parse CLOSED-CAPTIONS entries from HLS manifests and attach CC metadata (language, name, instream_id) to video tracks
- Move CC extraction to run after decryption instead of before, fixing extraction failures on encrypted streams
- Extract CCs even when other subtitle tracks exist, using manifest CC language info instead of guessing
- Try ccextractor on the original file before repacking to preserve container-level CC data (e.g. c608 boxes) that ffmpeg remux strips
- Display deduplicated closed captions in --list output and download progress, positioned after subtitles
- Add closed_captions field to Video track class
2026-03-05 15:57:29 -07:00
CodeName393
def18a4c44 fix(aria2c): Correct progress bar tracking for HLS downloads
Modified the download generator in aria2c to track progress by the number of completed segments (len(completed)) when downloading multiple files. Single-file downloads remain byte-based.
2026-03-05 14:43:24 +09:00
Sp5rky
7dd6323be5 Merge pull request #87 from CodeName393/add-HDR-Vivid-TAG
fix(title): Add HDR Vivid Format HDR Tag
2026-03-04 15:38:03 -07:00
CodeName393
d68bb28a66 fix(title): Add HDR Vivid Format HDR Tag
The existing HDR Vivid format HDR tag processing is missing due to the feature of the title map.
2026-03-04 23:17:18 +09:00
Andy
d1e6d0812c fix(dash): pass period_filter to n_m3u8dl_re via filtered MPD file
The period_filter in DASH.to_tracks() only affected track listing but had no effect on n_m3u8dl_re downloads, which re-parsed the raw MPD and downloaded all periods including ads/pre-rolls. This caused DRM decryption failures and corrupted video output.
When periods are filtered during to_tracks(), write a filtered MPD (with rejected periods removed) to a temp file and pass it to n_m3u8dl_re via track.from_file.

Closes #51
2026-03-01 13:18:27 -07:00
Andy
2f7a3d6d1d feat(dl): add skip messages for --no-audio and --no-chapters flags 2026-02-28 14:19:06 -07:00
Andy
572a894620 feat(dl): add --animeapi and --enrich options for anime metadata and tagging
Add AnimeAPI integration to resolve anime database IDs (MAL, AniList, Kitsu, etc.) to TMDB/IMDB/TVDB for MKV tagging. The --enrich flag overrides show title and fills in year when missing from the service.

- Add animeapi-py dependency for cross-platform anime ID resolution
- Add --animeapi option (e.g. mal:12345, anilist:98765, defaults to MAL)
- Add --enrich flag to override title/year from external sources
- Remove --tmdb-name and --tmdb-year in favor of unified --enrich
- Update REST API params and docs to match
2026-02-28 12:51:14 -07:00
Andy
5bd03c67cf fix(api): resolve Sentinel serialization, missing params, and add search endpoint (#80)
Fix multiple issues with the REST API that caused downloads to fail:
- Filter Click Sentinel.UNSET enum values from service parameter defaults that caused "Object of type Sentinel is not JSON serializable" errors
- Add missing select_titles and no_video args to dl.result() call
- Fix wanted param unpacking for list-tracks SeasonRange.parse_tokens()
- Add enum conversion for vcodec, range, sub_format, and export params that were passed as strings but expected as enums by dl.result()
- Add missing dl command params: split_audio, repack, imdb_id, output_dir, no_cache, reset_cache to DEFAULT_DOWNLOAD_PARAMS and download worker
- Expand vcodec/acodec/sub_format validation to cover all supported values
- Add POST /api/search endpoint for searching services by query
- Update Swagger docs with all new params and correct type definitions
- Add comprehensive REST API documentation (docs/API.md)
- Update ADVANCED_CONFIG.md with serve CLI options and API reference
2026-02-27 19:17:15 -07:00
Andy
d8a362c853 fix(drm): update PlayReady KID extraction for pyplayready 0.8.3 compatibility
Replace removed `WrmHeader.read_attributes()` with `key_ids` property and add missing WRMHEADER v4.1-v4.3 XML paths (`DATA/PROTECTINFO/...`) to the base64 PSSH parser fallback.
2026-02-27 16:54:23 -07:00
Andy
08269bef60 feat: update unshackle version to 4.0.0 2026-02-26 22:39:25 -07:00
Andy
65ceb4ae51 chore(changelog): update changelog 2026-02-26 21:18:59 -07:00
Andy
c8883a5404 feat(templates): add configurable language tagging rule engine (#58) 2026-02-26 21:17:46 -07:00
Andy
13d7636d86 chore(changelog): update changelog for upcoming release and reorganize sections 2026-02-26 18:28:07 -07:00
Andy
6ce7b6c4d3 feat(templates)!: add customizable output filename templates (#12)
BREAKING CHANGE: The 'scene_naming' config option has been removed.
Users must configure 'output_template' in unshackle.yaml with movies, series, and songs templates. See unshackle-example.yaml for examples.
2026-02-26 18:23:18 -07:00
Andy
798ce95042 fix(dependencies): update pyplayready version to 0.8.3 and adjust dependencies 2026-02-26 14:12:38 -07:00