78 Commits
Author SHA1 Message Date
imSp4rky e29a523907 feat(tracks): download and mux stereoscopic 3D (MV-HEVC) video to MP4
Mark 3D from REQ-VIDEO-LAYOUT, DV profile 20 codecs or an lhvC box; mux with MP4Box, decrypt with mp4decrypt, skip FFmpeg repack, sample cut and hybrid; add {3d} and env MP4Box.
2026-11-12 04:47:18 -07:00
imSp4rky f85d7f5fa8 feat(subtitles): add --sub-font and subtitle.ass_style for converted ASS
Converted subtitles get one style on a 1080-line canvas shaped like the video; native SSA/ASS is never restyled.
2026-11-11 10:04:57 -07:00
imSp4rky 590a57113b feat(dl): add -ce/--continuous-episodes to number seasons on
Each season's episodes continue from the previous season's last episode (S02E05 after a four-episode S01); also on the REST API. A --remote get_tracks refresh no longer resets season, number and part.
2026-11-11 08:38:27 -07:00
imSp4rky d12bf31b6b feat(hybrid): generate Dolby Vision from the decoded video
--generate-dv now takes an HDR10 base and measures L1 with FFmpeg when there is no usable HDR10+ metadata. -r HDR10 or -r HDR10P with the flag runs as a hybrid of that base alone, --sample shifts the DV RPU to match the base, and the REST API accepts generate_dv with any range.
2026-11-10 05:30:03 -07:00
imSp4rky 4b6d15cc2a feat(dl)!: treat a missing -sl language as a warning
A missing -sl language now warns and continues, with or without --best-available. --require-subs counts a forced track only when selection keeps it, and a title with no track left is skipped instead of failing at mux.

BREAKING CHANGE: a missing -sl subtitle language no longer fails the run. Use --require-subs to fail on it.
2026-11-09 15:01:54 -07:00
imSp4rky d8a4950b99 fix(import): keep only exported dash content periods on a re-fetch
The export writes mediaexport content_periods per manifest; an import that re-fetches the MPD (no saved snapshot) drops the periods a service filtered.
2026-11-02 20:58:49 -07:00
imSp4rky 842ece3802 fix(manifests): skip filtered dash periods after the main content and keep them over export
A manifest snapshot module now saves and restores DASH/ISM parser state for --remote and export/import, so an import keeps the service's period filter without fetching the manifest.
2026-11-02 20:41:27 -07:00
imSp4rky eeff83b820 fix(api): cast download job options with the dl click types
cast_dl_params feeds ctx.params to dl() and dl.result() as on the CLI. A string lang value now returns 400 instead of splitting into letters, and -r accepts HDR10+.
2026-11-02 20:30:40 -07:00
imSp4rky 06f87dafa3 feat(drm): add blowfish stripe cipher and mp3 audio codec
BlowfishStripe decrypts on the client after download, so a --remote client gets clear audio.
2026-10-26 18:59:42 -06:00
imSp4rky ffeb58f808 feat(manifests): add dts-hd, mpeg-h and lcevc codecs and a manifest corpus
Fix DASH SegmentTimeline negative @r and Pattern, TTML sidecar sets with no contentType, HLS CHANNELS identifiers and Dolby channel masks; test against 408 public DASH/HLS/ISM manifests.
2026-10-26 10:30:43 -06:00
imSp4rky 96d6a4f1b2 fix(api): split global speed limit across job workers 2026-10-20 20:03:03 -06:00
imSp4rky 1a8e633fdc feat(dash): flag dv profile 8 tracks from supplementalCodecs and use hdr10+dv as the hybrid 2026-10-18 17:18:11 -06:00
imSp4rky 768e35d905 feat(hybrid): use a dv track that carries hdr10+ as the hybrid of its resolution 2026-10-18 12:11:02 -06:00
imSp4rky 811e0a19c0 feat(tracks): probe HDR10 tracks for HDR10+ SEI before selection
Relabel HEVC HDR10 tracks whose first fragment carries ST 2094-40 SEI as HDR10P, for dl, the REST API and remote sessions. Config key hdr10plus_probe (default true).
2026-10-17 18:58:48 -06:00
imSp4rky 35871f3a52 feat(hybrid): generate dolby vision from hdr10+ with --generate-dv
Builds per-scene L1 in unshackle (PQ mean, letterbox-aware) with a generate_dv_mode config (v29/v40/legacy); a failed hybrid no longer exits 0 with no video.
2026-10-17 16:40:14 -06:00
imSp4rky a4ce70a53c feat(tracks)!: match a language tag to the variant it means
A tag with a region (en-US, es-419) matches only that variant and reports a miss instead of taking another region. A bare tag takes the variant it means (pt -> pt-BR, es -> es-ES) and falls back to any variant only when that one is missing. `orig` prefers the title's own tag, `-sl orig` resolves, forced and SDH subtitles are picked from their own pools, and the track name no longer strips the region from the cached language.

BREAKING CHANGE: -l es-419 no longer selects es-ES, and language_priority, language_tags and muxing.default_language treat a regional tag as exact.
2026-10-15 12:26:39 -06:00
imSp4rky b4136e1cf5 perf(sample): download only the subtitle segments around the window
A segmented subtitle track took every segment in a --sample run. It now takes the window plus one segment on each side.
2026-10-11 19:14:14 -06:00
imSp4rky ecdfb667c9 feat(dl): add --sample and --include-sample
--sample downloads only a time window of a title (default 120-180 s) and cuts the mux to it. --include-sample cuts a 60 s sample from a finished file. Both write to a Sample folder with a SAMPLE tag on the filename
2026-10-11 14:53:50 -06:00
imSp4rky 3477d09cc2 feat(dl): add --audio-description-only and --forced-subs-only
Each keeps one kind of track inside its track type. A run of more than one title skips a title that has no such track. The REST API rejects a non-boolean value for a boolean dl param, and the DASH parser reads audio description from Role elements.
2026-10-06 12:45:52 -06:00
imSp4rky 1d2cb3a102 fix(dl): read the vaults first under --all-drm
A DRM system sends a challenge only for a KID with no content key. --cdm-only keeps one challenge per system for each set of KIDs.
2026-10-05 16:43:54 -06:00
imSp4rky ee38477b11 feat(dl): add --all-drm to license Widevine and PlayReady per track
Each DRM system sends one challenge per KID set in a run, also when a vault has the content keys. Needs a CDM for each system in the cdm config.
2026-10-05 15:22:01 -06:00
imSp4rky 9a1abd23b6 feat(remote): license with the client's device when the user picked one
A remote client that passes --cdm, has a cdm entry for the service that named the loaded device, or sets server_cdm: false now declines the server CDM at session create, and warns when the server keeps it. A service option listed in SERVER_DEVICE_OPTIONS keeps the server CDM.
2026-10-01 13:21:53 -06:00
imSp4rky c7ab04028a feat(setup): add unshackle setup command and cross-platform install scripts
setup downloads the external tools, writes a first config and imports CDM devices; install.sh and install.bat bootstrap uv and run it.
2026-10-01 12:21:24 -06:00
imSp4rky 37e2b59bce feat(dl): add --latest-episodes N to download the N newest episodes
Split episodes count once. Exposed as latest_episodes on the REST API. Closes #159.
2026-09-28 09:01:40 -06:00
imSp4rky c06b00ec63 fix(import): read an HLS export that names no title language or master key
Probe each media playlist for its KIDs and refuse a manifest unshackle cannot read.
2026-09-23 16:20:49 -06:00
imSp4rky d014c69d00 feat(export): separate export file name parts with dots
Titles, scope and quality parts use dots and never follow output_template.
2026-09-23 15:26:46 -06:00
imSp4rky a19c3e3879 fix(export): name the file from the first write 2026-09-23 15:11:15 -06:00
imSp4rky 347668eafa docs(export): describe the export file name and its messages 2026-09-23 15:11:15 -06:00
imSp4rky 80088fe4f6 feat(import): build tracks from direct-file export rows
Imports unidl json_manifest exports made of whole files.
2026-09-23 15:04:22 -06:00
imSp4rky 06866eb12e feat(export): record each track's KIDs
An import reads them first, so a track gets its own keys without a probe.
2026-09-23 14:35:58 -06:00
imSp4rky 84bb07f589 fix(import): stop clearly when no export or vault key exists
The bad-key retry now requests each dropped KID.
2026-09-23 14:33:34 -06:00
imSp4rkyandAntCardFr 783c8ae1c2 feat(proxies): add Control D proxy provider
Runs a loopback forwarder per region-named profile (unshackle-<region>), so
--proxy and --proxy-download can exit in different countries

Thanks to Orni_ for the original code and the test account.

Co-authored-by: AntCardFr <24798811+AntCardFR@users.noreply.github.com>
2026-09-23 13:23:50 -06:00
imSp4rky 30ea001cb3 Merge branch 'feat/new-export' into dev 2026-09-23 11:12:19 -06:00
imSp4rky a254830121 fix(export): import side-loaded subtitles and keep conflicting keys apart
The importer builds subtitle tracks from track rows that carry a url, which the format promised and no reader did. The writer keeps a conflicting KID instead of overwriting it, identifies a manifest by its whole URL, writes subtitle rows as srt or vtt rather than enum names, and survives an export file it cannot read.
2026-09-20 10:39:13 -06:00
imSp4rky 746385101b feat(export): write mediaexport files and read legacy exports through the package
--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.
2026-09-20 00:07:04 -06:00
imSp4rky cf2906f120 fix(cli): make the click param types the one source of codec spellings
--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.
2026-09-17 19:22:19 -06:00
imSp4rky a0515f8d57 feat(dl): add --proxy-download for a separate download proxy
Manifest, licence and auth stay on --proxy; track, subtitle and attachment downloads use the second proxy. Also exposed as proxy_download on the REST download job with the same server_proxy gate. The download session now ignores HTTP(S)_PROXY and copies rnet cookies set by name.
2026-09-13 14:41:01 -06:00
imSp4rky a7d6791136 feat(remote): verify server-CDM vault keys on the client and report bad pairs
The licence response lists KIDs a server vault supplied, the client proves them with the decode check, and a bad pair goes to the new session keys/bad route so the server flags its own row. Client vault keys go first over --remote, and the dashboard health warns when no SQLite vault can store flags.
2026-09-12 15:13:08 -06:00
imSp4rky d36797641d feat(dl): add --no-postscript to skip post-scripts for one run 2026-09-06 21:02:58 -06:00
imSp4rky 85503fd87c test(downloader): cover probe judging and the submission window
Assert that a probe is judged on the trailing tick, that a backoff is temporary and climbs back to the cap, and that a worker always finds its next segment staged. Documents adaptive-workers behaviour in the downloading guide.
2026-09-06 20:03:46 -06:00
imSp4rky dffb72e41d fix(audio): accept immersive channel layouts like 5.1.4
parse_channels rejected a three-figure layout, so a remote download crashed rebuilding the track from JSON. Add channel_total so --channels can compare either form.
2026-09-04 19:27:39 -06:00
imSp4rky 235839abc8 feat(audio): support DTS:X Profile 2 with MP4 muxing
Route titles with a dtsx/dtsy sample entry to MP4 via MP4Box, since Matroska has no CodecID for DTS-UHD. Add per-rendition HLS audio codec selection, fix merge_segments progress and its cleanup path.
2026-09-04 14:19:29 -06:00
imSp4rky 4ef577c1d1 fix(tracks): match resolutions per range and codec
An exact height match in one range denied the 16:9 canvas fallback to the others, so a cropped HDR10 or SDR track was dropped whenever an uncropped DV track matched the requested height. Also sweep the hybrid L5 temp files so a killed run cannot leave a stale RPU behind.
2026-08-27 14:32:36 -06:00
imSp4rky b94f338619 docs(post-scripts): use post-script as the one term for hooks
Add the post-script row to the ste-writing terminology contract and
replace hook/postscript/post-download script across docs and docstrings.
2026-08-25 19:57:41 -06:00
imSp4rky e804d09f0a feat(dl): add --cdm to pin one CDM device for a run
Overrides the cdm config mapping (and its quality/DRM sub-entries) for the whole run; the REST API cdm field now flows through the same param.
2026-08-24 16:37:48 -06:00
imSp4rky 8c43250c9b feat(dl): add --no-attachments to skip cover art and subtitle fonts
Drops service attachments at track selection and skips the font attach pass; exposed as no_attachments in the REST dl params and documented.
2026-08-24 16:21:52 -06:00
imSp4rky d8739b3561 feat(dl): add --require-audio/--require-video and --warn-only alias
Gate a title on audio or video languages that must exist, independent of what -l/-al/-vl select. --require-subs joins the same gate: its --s-lang conflict and download-all side effect are gone, so -sl now decides what to keep. Requiring a language that is also excluded fails at parse time.
2026-08-23 12:24:45 -06:00
imSp4rky 8b10b793e2 docs: phase 2 of the prose style across docs and docstrings 2026-08-19 20:59:09 -06:00
imSp4rky 66971230c3 docs: apply simplified technical english across docs and docstrings. 2026-08-19 16:31:06 -06:00
imSp4rky 68c7d95173 refactor(music)!: make Song an ordinary Title on the generic path
Delete the dead grouped-download block, the planner, and the music service hooks; Song now flows through the one download loop, gets tagged via tag_file, and answers -w with {disc}x{track} keys.

BREAKING CHANGE: GROUP_AUDIO_DOWNLOADS, get_music_track_options and get_music_collection_label are gone. A music service returns one Audio track per quality and lets the framework choose.
2026-08-18 15:05:30 -06:00