Commit Graph

565 Commits

Author SHA1 Message Date
imSp4rky
82ab996777 fix(dl): prefer Atmos in -l best/all language selection
The per-language picker used max() keyed on bitrate only, so a higher-bitrate non-Atmos track was selected over a lower-bitrate Atmos track. Switch the key to (bool(x.atmos), x.bitrate) so Atmos wins with bitrate as tiebreaker, matching Tracks.sort_audio.
2026-05-14 10:00:25 -06:00
imSp4rky
c0929bf217 fix(template_formatter): preserve dash separator around empty conditional
When an empty conditional sits between a dot and a dash (e.g. `.{atmos?}-{tag}`),
the left-side dot was kept and the dash before the tag was dropped, producing
`...DDP5.1.TAG` instead of `...DDP5.1-TAG`. Prefer the dash when it is the
right-side separator.

Closes #107
2026-05-12 08:24:31 -06:00
imSp4rky
20c15f761b fix(proxies/nordvpn): use *.proxy.nordvpn.com for HTTPS proxy
NordVPN's HTTPS proxy endpoints now serve a certificate valid only for *.proxy.nordvpn.com. Connecting to the legacy <server>.nordvpn.com:89 hostname fails with SSLCertVerificationError (hostname mismatch).

Rewrite direct queries, server_map entries, and API-returned hostnames to the proxy subdomain so cert validation succeeds.
2026-05-11 09:55:48 -06:00
imSp4rky
9e4fdcdcd8 fix(dl): re-pick DV/HDR10 when HYBRID falls back under best_available
When HYBRID is requested alongside other ranges with best_available and no HDR10 base layer exists, the pre-validation hybrid selection had already locked in the lowest-resolution DV track. Snapshot the pre-hybrid pool and redo Cartesian range/quality/codec/lang selection over surviving ranges so DV (or HDR10-only) honors --worst and default best-pick semantics.
2026-05-10 11:04:29 -06:00
imSp4rky
7fb88e9a97 docs: update docs to match current codebase 2026-05-08 17:54:45 -06:00
imSp4rky
4c981e2ffd fix(tracks): honor --worst in hybrid range selection
select_hybrid always picked max-bitrate HDR10 base layer, ignoring the --worst flag. Thread worst through to use min bitrate when requested.
2026-05-08 15:37:09 -06:00
imSp4rky
5984eefcbe fix(sanitize): preserve parentheses, strip unidecode bracket artifacts (#105)
Commit 10cca7d re-added () to the stripped character set, which broke output_template patterns like ({year?}). The original reason for stripping parens was that unidecode maps 【】 to "[(" and ")]", leaving artifacts like [(SERIES NAME)] in filenames.

Allow parens in filenames so templates render correctly, and collapse the unidecode "[(" / ")]" sequences immediately after transliteration so unicode brackets still come out as [SERIES NAME].
2026-05-05 08:39:38 -06:00
imSp4rky
08c0862691 fix(manifests): clean stale .!dev resume markers before merge
HLS/DASH/ISM iterdir included leftover .!dev control files from aborted runs, crashing HLS merge_discontinuity and silently corrupting DASH/ISM merged output.
2026-05-04 22:51:45 -06:00
imSp4rky
db313a8ee2 refactor(routes, subtitle, track): improve code readability by formatting list structures 2026-05-04 22:21:03 -06:00
imSp4rky
a7af898617 feat(ip-info): consolidate IP lookup, add ipinfo.io token support
Replace get_ip_info + get_cached_ip_info pair with a single unshackle.core.utils.ip_info module providing a normalized return shape across providers. Adds optional ipinfo_api_key config for the ipinfo.io Lite endpoint (higher rate limits, ASN/org/continent data), swaps the ipapi.co fallback for ip-api.in, and migrates all callers (service, gluetun, remote_service, api/handlers, DSNP, YT) to the new import path. Auth token is sent per-request and never attached to the shared session headers.
2026-05-04 22:20:43 -06:00
imSp4rky
8f4cac6c7b chore(pre-commit): update hook versions in .pre-commit-config.yaml 2026-05-04 22:20:02 -06:00
imSp4rky
fd6aafc068 chore(release): bump version to 5.0.0
Update CHANGELOG.md for the 5.0.0 release, including the unified downloader, remote API, DRM, manifest, and download pipeline changes since 4.0.0.
5.0.0
2026-05-04 16:12:53 -06:00
imSp4rky
7b7255dec0 feat(docs): add AGENTS.md to .gitignore 2026-05-04 15:49:53 -06:00
imSp4rky
d5237371e5 feat(dl): add --no-proxy-download flag
Bypass proxy for segment downloads only. Manifest, license, and auth still use proxy. Faster when CDN is unrestricted but manifest is region-locked.
2026-05-03 13:18:53 -06:00
imSp4rky
21754ad37e feat(config): per-title-type folder templates
Allow output_template.folder to be a dict with movies/series/songs keys so music libraries can use artist/album folder layouts while movies and series keep their own scheme. Legacy string form still applies to all title types.
2026-04-30 10:51:32 -06:00
imSp4rky
605b46f723 perf(downloader): parallel byte-range fetch for single-URL tracks
Single-URL tracks (no DASH/HLS/ISM manifest) previously streamed sequentially over one TCP connection, capping throughput at per-flow CDN shaping limits. Probe ranges via a 1-byte GET; if supported and total size >= 64MB, split the byte range across N workers (capped by --workers) writing to a pre-allocated file at offsets. Each worker delegates to download() in part mode for shared retry/Range-resume semantics. ~2-3x speedup observed on shaped CDN edges.
2026-04-29 23:34:56 -06:00
imSp4rky
b3a8a531e6 feat(kv): add --local-only flag to copy/sync
Filters service tables in source vaults against the locally installed services (config.directories.services), so users don't pull keys for services they don't have. Mutually exclusive with --service.
2026-04-29 17:43:11 -06:00
imSp4rky
07881d78c2 feat(kv): add search subcommand to look up KID across vaults
Adds 'kv search <KID>' with optional -s/--service and -v/--vault
filters. Iterates configured vaults, short-circuits on first hit, and
renders results in the same Rich tree style used by the DRM key
display. Remote vaults that cannot enumerate services without a
service tag are skipped with a clear hint to re-run with --service.
2026-04-28 15:02:19 -06:00
imSp4rky
2f7a189c9c fix(hls): carry DRM keys forward across EXT-X-KEY rotation
When the active EXT-X-KEY changes but no segments precede the new key (e.g. rotation at the first segment), no separate decrypt batch is flushed for the previous DRM and its content keys are lost. The merged file still contains samples encrypted under those keys, so the final mp4decrypt/shaka call decrypts them as garbage.

Carry the previous DRM's content keys into the new DRM via setdefault so every key needed across the merged segments is present at decrypt time. Existing zero-KID fallback handling (PlayReady, Widevine) remains the disambiguator for tracks whose tenc default_KID is all-zero.
2026-04-28 09:28:21 -06:00
imSp4rky
ffd67f15d8 fix(drm): pass per-segment PSSH to Widevine license callback
Mirrors the PlayReady fix (fbc4aa2) for Widevine. HLS manifests with per-segment EXT-X-KEY changes generate distinct PSSH per segment, so service callbacks building the license URI from cached track-level PSSH can mismatch the challenge KID and trigger CEKNotFound. Forward pssh from the active DRM and fall back to the legacy single-arg call when a service hasn't adopted the kwarg.
2026-04-27 20:04:07 -06:00
imSp4rky
8fff3dc422 Merge branch 'dev' of https://github.com/unshackle-dl/unshackle into dev 2026-04-26 16:33:26 -06:00
imSp4rky
fbc4aa2c4d fix(drm): pass per-segment PSSH to PlayReady license callback
HLS manifests with per-segment EXT-X-KEY changes generate distinct WRMHEADERs per segment. Service license callbacks that build the license URI from cached track-level PSSH state can mismatch the challenge KID, causing the license server to omit it and triggering CEKNotFound. Forward pssh_b64 from the active DRM and fall back to the legacy single-arg call when a service hasn't adopted the kwarg.
2026-04-26 16:33:16 -06:00
sp4rk.y
0538f85ff7 Merge pull request #102 from CodeName393/Fix-Rnet-dict-type-error
Fix(session): header handling in session request method
2026-04-26 12:48:24 -06:00
CodeName393
bddb305c5d feat(session): Optimize header handling in session requests
Removed redundant conversion of headers to dict for requests.
2026-04-25 14:53:07 +09:00
sp4rk.y
a7f67c8b77 Merge pull request #103 from CodeName393/Add-base58
Add Base58 Utils
2026-04-24 11:52:48 -06:00
imSp4rky
b1158099d1 fix(dash): expand CICP enums to full H.273 range
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
2026-04-24 11:50:27 -06:00
CodeName393
831981a56e Add(Base58): encoding and decoding functions
Implement Base58 and Base58Check encoding and decoding.
2026-04-24 23:57:24 +09:00
CodeName393
9dbdf9804f Fix(session): header handling in session request method
Ensure headers are converted to a dictionary if provided.
2026-04-24 23:54:24 +09:00
imSp4rky
776be128a7 chore: update version to 5.0.0 2026-04-21 11:24:12 -06:00
sp4rk.y
096288bd37 Merge pull request #100 from JohnVeness/dl
dl.py: Improve English in the help text
2026-04-21 11:12:14 -06:00
John Veness
e2e56d7065 dl.py: Improve English in the help text 2026-04-18 13:28:45 +01:00
imSp4rky
982f821f19 fix(dash): deduplicate multi-period SegmentBase segments
Multi-period DASH manifests using SegmentBase with shared BaseURLs were downloading the entire file once per period. Deduplicate identical segments across periods so each file is only downloaded once. Also demote multi-period log message from info to debug.
2026-04-13 16:44:35 +00:00
imSp4rky
c56a92ed0c feat(track): add optional per-track session parameter (#96)
Originally authored by panitan103 — adds optional session parameter to Track class allowing services to pass per-track sessions with different headers/cookies/auth.

Changes for dev branch integration:
- Fix type hints to support both requests.Session and RnetSession
- Fix session fallback in dl.py: track.session or service.session
- Remove redundant `session or None` assignment

Co-Authored-By: panitan103 <panitan103@users.noreply.github.com>
2026-04-12 16:41:43 -06:00
imSp4rky
cb3535215d fix(dl): always report full error trace for download worker failures
Previously, unexpected errors only showed a generic message without the actual exception details or traceback. Simplify the error handler to always include the exception type/message and print the full trace.
2026-04-12 22:32:23 +00:00
imSp4rky
4179b9045e fix(tags): prevent metadata lookup failures from skipping group tag
Wrapped metadata provider lookup in try/except so custom tags (Group) are always applied even when IMDB/TMDB lookups fail. Also log mkvpropedit errors instead of silently discarding them.
2026-04-12 22:21:02 +00:00
imSp4rky
2e7fc1720d fix(drm): handle non-UTF-8 output from shaka-packager stderr
Shaka-packager can emit non-UTF-8 bytes in its log output, causing UnicodeDecodeError when reading stderr in text mode. Use explicit errors="replace" encoding. Also harden try_ensure_utf8 fallback paths to always return valid UTF-8 instead of raw bytes.
2026-04-12 22:19:44 +00:00
imSp4rky
8bdb942234 feat(dl): add download resume support via HTTP Range headers
Partial downloads are now preserved across interruptions and retries. When a control file and partial data exist, the downloader sends a Range header to resume from the last byte. Falls back to full re-download if the server doesn't support Range requests (no 206).
2026-04-12 11:40:15 -06:00
imSp4rky
8f4f947d0d fix(api): sync REST API download endpoint with updated dl command (#98)
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
2026-04-08 23:04:58 -06:00
imSp4rky
50d2b127ec style: apply ruff linting fixes across codebase 2026-04-08 22:09:19 -06:00
imSp4rky
725edd59e0 fix(gluetun): support WireGuard VPN ready detection
WireGuard is stateless and never emits the OpenVPN-specific "initialization sequence completed" log line, causing the readiness check to always time out. Also accept "public ip address is" which gluetun logs once the WireGuard tunnel is up.

Closes #99
2026-04-08 22:05:56 -06:00
imSp4rky
c5aa57c9db fix(dash): add SIDX parsing for SegmentBase manifests and deduplicate multi-period segments
Multi-period DASH manifests using SegmentBase with shared BaseURLs were downloading the entire file once per period, causing massive file size inflation. Parse the SIDX box to extract proper per-segment byte ranges and deduplicate identical segments across periods.
2026-04-06 02:45:17 +00:00
imSp4rky
fef68202e9 fix(dl): preserve per-language video selection in quality step
The video quality Cartesian product (resolution × range × codec) only picked the first matching track, collapsing multi-language selections back to a single language. Add language as a product dimension when -l best/all or -vl with multiple languages is used.
2026-04-04 09:56:20 -06:00
imSp4rky
c051d9df23 ci(security): add Bandit pre-commit hook 2026-04-02 13:23:26 -06:00
imSp4rky
e10dbeed94 fix(api): sanitize user-provided values in log statements to prevent log injection
Addresses 12 CodeQL high-severity log injection alerts (CWE-117) by stripping newline and control characters from user-provided values before logging.
2026-04-02 12:10:46 -06:00
imSp4rky
0ebf9278d1 fix(deps): bump aiohttp and pygments to resolve 11 security vulnerabilities 2026-04-02 11:44:54 -06:00
imSp4rky
bb0a800ab6 docs(api): update --export from string path to boolean flag
Update API docs, Swagger schema, handlers, and example config to reflect --export as a boolean flag that auto-generates export files in the configurable exports directory.
2026-04-02 11:27:39 -06:00
imSp4rky
fabc96ba1b feat(dl): change --export flag with manifest URL, subtitles, and track info
changed --export flag to export decryption keys, manifest URLs, subtitle URLs, and track info to a JSON file in the configurable exports directory. Manifest URLs are captured from DASH, ISM, and HLS parsers and propagated through the Tracks system via a new manifest_url attribute. Deduplicate Widevine/PlayReady export logic into a shared _write_export helper with dedicated EXPORT_LOCK. Add Tracks.filter() method that preserves metadata when filtering tracks by predicate.
2026-04-02 10:29:22 -06:00
imSp4rky
655e4197c3 fix(session): native rnet proxy support and cookie compat layer
Proxies now use rnet's native `proxies` parameter (`List[rnet.Proxy]`) with in-place `client.update()` for live proxy changes. Client is created lazily on first request, allowing headers, cookies, and proxies to be configured freely before any connection is established.

Cookie adapter supports RequestsCookieJar-compatible methods (jar, get_dict, clear) for seamless interop with cookie persistence.
2026-04-01 17:08:25 -06:00
imSp4rky
3aaca77c48 feat(serve): add service allowlist for global and per-user access control
Allow server operators to restrict which services are exposed via the API using serve.services (global) and serve.users.<key>.services (per-user).
Effective access is the intersection of both when both are set. Unlisted services return the same error as non-existent ones to prevent enumeration.
2026-03-31 23:06:07 -06:00
imSp4rky
8a714d6455 fix(template): detect folder spacer from template separators, not raw string
The previous heuristic checked the raw template string for dots, which could match dots inside variable names or title content, causing
Plex-friendly folder names to incorrectly use dots as spacers. Now strips template variables first and checks only the separators between them to determine user intent.
2026-03-31 09:13:44 -06:00