Fix the bugs the type errors exposed (RnetSession.send body, HLS proxy session, title cache fallback, mux progress) and gate mypy in pre-commit, warn-only in CI.
Test servers shut down without the 0.5s poll wait, unit tests refuse non-loopback lookups, and three load-sensitive tests are steadied. Pushes to dev test Python 3.11 only; the docs job builds with uv.
--export writes the mediaexport format with DRM, content keys and manifests at title level, and unshackle's own track dicts under x-unshackle. unshackle import reads mediaexport, unidl v1 and unshackle v2 through the shared reader, applies the export's request headers to the import session, and rebuilds DRM from the title-level pool. mediaexport is a git dependency on unshackle-dl/mediaexport.
--acodec now lists its codecs in --help, --vcodec drops the duplicate entries, and the REST API derives its allowlists from the same types, so AC-4, DDP, VORB and sub_format "original" stop being rejected and H264 stops being silently dropped. Adds shell completion for --acodec and repairs MultipleChoice completion. Raises the click floor to 8.2.
Render all click help through rich-click, themed by a new `theme` config key (9 palettes + aliases, previewable via `unshackle env theme`), and show service docstrings in local and remote `dl <TAG> -h`.
--anilist supersedes --animeapi (REST: anilist_id); the keyless AniList
GraphQL provider joins the registry with title search, mal: ID lookup,
an ANILIST mkv tag, and the anilist_title_language config option.
Services mark anime via Service.ANIME / Title.anime for anilist-first
metadata resolution with fall-through.
Move the supported interpreter range to >=3.11,<3.15, dropping the EOL 3.10 floor and unblocking installs on 3.13/3.14.
- pyproject: requires-python ">=3.10,<3.13" -> ">=3.11,<3.15"
- update_checker: replace deprecated asyncio.get_event_loop() with asyncio.to_thread() (warns on 3.12+, slated for removal)
- test_compression: simplify _run() to plain asyncio.run()
- README: bump documented Python range to 3.11 - 3.14
- uv.lock: regenerate for the new interpreter floor
Group selected videos by (resolution, range, codec) and mux each group into one MKV; only language is collapsed, so ranges/codecs/resolutions stay in separate files. Adds --merge-video flag + muxing.merge_video config (global and per-service), docs, and tests.
* Add native Music core workflow
* feat(music): add shared music core helpers, album folder template, and UX cleanup
Consolidate duplicated music-service logic into core and extend the native music workflow.
- add core/music/extract.py: shared stateless helpers (first_text, first_number, year/duration/name formatting, classify_release_kind, dedupe_track_options, build_music_from_songs) so services stop carrying their own copies
- add core/music/display.py: shared rich rendering (render_track_panel, render_album_header, render_artwork_preview) with TrackRow / MusicHeaderInfo data holders
- export the new helpers from core/music/__init__.py
- add dedicated `albums` folder template kind (output_template.folder.albums) resolving albums -> songs -> "{artist} - {album} ({year})"; whitelist music template variables (album_artist, track_total, disc_total, release_type, genre, explicit, isrc, upc, label)
- fix song filename crash: config.get_output_template(...) did not exist; use config.output_template.get("songs") with a sane default
- strip emojis from music output (renderer, dl.py music branch) to match unshackle UX; remove dead MusicSongPlan import and music_icon logic
- document the albums folder key in unshackle-example.yaml
- add tests for extract, display, and the folder template
---------
Co-authored-by: MrMovies-Dev <MrMovies-Dev@users.noreply.github.com>
Missing requested subtitle and audio languages now warn and continue when --best-available is set instead of hard-exiting. Without the flag, missing languages still produce an error and exit, matching the prior strict behavior. Audio missing-lang detection is now symmetric with subtitles.
- add find_missing_langs helper in core/utilities for reuse between s_lang and a_lang paths (skips all/best/orig sentinels)
- refactor dl.py s_lang/a_lang checks to share the helper
- add tests/lang_selection covering match primitives, helper output, and tricky langcodes corners (zh-Hans/zh-Hant/zh-CN/zh-TW/zh-HK, cmn/yue, fil/tl/tgl)
- clean up unused-var ruff F841 in tests/remote/unit/
Video.Range.from_cicp() crashed with ValueError on any CICP primaries, transfer, or matrix value outside the small subset previously enumerated (e.g. TransferCharacteristics=4 / BT.470M gamma 2.2 seen in SCTE-stitched live MPDs). Extend the three inner enums to cover all documented ITU-T H.273 / ISO/IEC 23001-8 values and fall back to Unspecified on unknown codes so SDR content no longer fails to parse.
Source: https://raw.githubusercontent.com/FFmpeg/FFmpeg/master/libavutil/pixfmt.h (authoritative implementation of ITU-T H.273 / ISO/IEC 23001-8 tables). ITU spec itself: https://www.itu.int/rec/T-REC-H.273
The REST API download endpoint was broken after recent dl command changes.
- Add missing vbitrate_range, abitrate_range, and worst parameters to the API call and DEFAULT_DOWNLOAD_PARAMS
- Convert wanted episode strings (S01E01) to internal SxE format via SeasonRange so episode filtering works correctly
- Track completed output files via dl.completed_files instead of returning an empty list
Closes#98
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.
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
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
- Add MonaLisaCDM class wrapping wasmtime for key extraction
- Add MonaLisa DRM class with decrypt_segment() for per-segment decryption
- Display Content ID and keys in download output (matching Widevine/PlayReady)
- Add wasmtime dependency for WASM module execution
* chore(deps): update subby to 0.3.27
* fix(subs): add WVTT (WebVTT in MP4) subtitle converter for subby
- Use WVTTConverter if self.codec == Subtitle.Codec.fVTT
- Read or save files as Paths instead of strings to avoid AttributeErrors
- Add CommonIssuesFixer when stripping SDH
- Set Subtitle.Codec.fVTT to use subby if conversion_method == "auto"
- Silence the underlying srt library logging used by subby to avoid info messages being printed to console