Compare commits

..
604 Commits
Author SHA1 Message Date
imSp4rky 3dcc584b2f chore(changelog): update changelog and cleanup readme 2026-06-03 11:11:23 -06:00
imSp4rky 538deac63e fix(session): proxy auth on http targets and string params support
Drop the manually-set Proxy-Authorization header in the Service base class. It was malformed (base64 of user:pass with no "Basic" scheme) and redundant: both rnet (Proxy.all) and requests authenticate from the credentials embedded in the proxy URL. The broken header was tunnelled harmlessly on HTTPS (CONNECT) but handed to the proxy on plaintext-http forward requests, causing HTTP 407 (e.g. http MPD/segment URLs behind an authenticated geofence proxy).

Also make RnetSession._build_url accept the same params shapes as requests (mapping, sequence of pairs, or a pre-built str/bytes query). urlencode() previously raised TypeError on a string params value.
2026-05-30 18:13:55 +00:00
imSp4rky 0561623825 docs: simplify README with demo and requirements 2026-05-29 19:01:57 -06:00
imSp4rky 4ce0dd5bb9 chore(changelog): update version date and add new features and fixes for 5.1.0 2026-05-29 18:27:31 -06:00
imSp4rky 191c4f47e0 refactor: remove dead aria2c and n_m3u8dl_re downloader code
The legacy aria2c, curl-impersonate, and N_m3u8DL-RE downloader backends were replaced by the unified in-process requests/rnet downloader. This strips the leftover references those removals missed.
2026-05-29 18:25:56 -06:00
imSp4rky accfe9cee1 refactor(dl): declare hybrid_base_only flag and extract standalone mux helper
Make the DV-ingredient marker a declared Video attribute (sibling of needs_duration_fix) instead of a runtime-stamped attribute read via getattr, so the flag is explicit and type-checked.

Extract mux_video_standalone() to replace the duplicated per-track mux logic shared by the hybrid and normal-mode paths, removing the copy-paste and the getattr probes. Behavior unchanged.
2026-05-29 17:13:29 -06:00
imSp4rky 5899c1eec8 refactor(example): showcase full unshackle feature surface 2026-05-29 12:54:37 -06:00
imSp4rky 6bc601db39 fix(dl): mux all requested ranges and select highest DV alongside hybrid
When -r included HYBRID, the dl pipeline only muxed the hybrid output and dropped every other downloaded range. DV was also treated purely as a hybrid ingredient pool, so an explicitly requested DV range never produced a standalone deliverable - only the lowest DV (the RPU ingredient) was selected.

- Select the best DV per resolution as a standalone deliverable when DV is in -r, while still using the lowest DV as the hybrid ingredient (dv_is_deliverable).
- Flag the ingredient-only DV (hybrid_base_only) so it is downloaded for the hybrid build but skipped during standalone muxing.
- Mux every requested range standalone after hybrid processing; build hybrids from deepcopied ingredients so the originals stay muxable.
- Add Tracks.merge_video_selections to de-dup the ingredient/deliverable overlap so a shared DV track is not downloaded or muxed twice.
2026-05-28 21:25:30 -06:00
imSp4rky fb8dc0bd9d refactor(ip_info): simplify lookup and trim cache
Cache only country/country_code (drop full IP/org/asn), bump CACHE_KEY and auto-purge stale cache versions. Dedup the three provider parsers into one normalize(). Use a plain retry-free requests session for lookups instead of the TLS-fingerprinted session, carrying only the proxy over, so a 429 returns directly and hands over to the next provider reliably.
2026-05-27 22:48:39 -06:00
imSp4rky 40104be738 fix(dash): inherit SegmentTemplate attributes across AdaptationSet/Representation
A SegmentTemplate can sit at both the AdaptationSet and Representation levels, with shared attrs like @timescale only on the outer node. The parser picked one or the other, dropping the outer node when an inner existed - so @timescale defaulted to 1 and only the first segment was emitted.

Merge instead: Representation node as base, inheriting any attribute or SegmentTimeline it omits from the AdaptationSet node (per ISO/IEC 23009-1).
2026-05-27 12:12:42 -06:00
imSp4rky 96fd971af0 feat(import): include cover-art attachments in --export/--import
Export URL-backed attachments per title and rebuild them on import so they ride along into the muxed output. Local font attachments are skipped (not portable).
2026-05-26 17:37:44 -06:00
imSp4rky 1cb0e4b766 feat(naming): per-service title_map remapping (#106)
Add services.<TAG>.title_map exact-match dict to rewrite service-provided titles before naming/output. Shared remap_titles helper applied on local dl, import, and client-side dl --remote (server stays raw so clients can override names for services they don't have installed locally).
2026-05-26 14:08:52 -06:00
imSp4rky f4544b4a70 fix(hls): resolve per-rendition KID in no-EXT-X-KEY fallback
Manifests that signal DRM only via EXT-X-SESSION-DATA share one session_drm across all renditions, carrying just the manifest-level KID. The fallback licensing path licensed without the track's real KID, so --vaults-only only looked up the shared KID and could not find the rendition's key even when the vault held it (--cdm-only masked this since the license returns all keys). Resolve the KID from the init segment's tenc box and pass it through, matching the EXT-X-KEY path.
2026-05-26 14:04:06 -06:00
imSp4rky f1febe7e43 chore(release): bump version to 5.1.0 2026-05-26 10:54:14 -06:00
imSp4rky b3a6db915b fix(cli): report broken command/service loads once and cleanly
Loaders raised at import time, and since Python does not cache a failed import, every command importing services re-ran and re-reported the same errors. Build the registry once, collect failures into LOAD_ERRORS, and surface them via a single click.ClickException at the list/get chokepoints so the message renders once without a traceback or cascade.
2026-05-25 19:50:11 -06:00
imSp4rky 5e3ffdeaa1 fix(hybrid): correct static L6 source and reset stale L5 active area
level_6() read MaxCLL/MaxFALL from the dovi_tool L1 line (dynamic per-shot content-light peak) and baked it into the static L6 field, so displays tone-map to a phantom peak and HDR brightness breaks. Read MaxCLL/MaxFALL from the static L6 block instead (mastering display parse unchanged), falling back to the HDR10 base stream only when the RPU has no L6 block. Add sanitize_l6() to clamp MaxCLL to the mastering-display peak and MaxFALL to MaxCLL (0 = "unknown" preserved); also applied to the HDR10+ -> DV path.

level_5() early-returned when cropdetect found no bars on the base, leaving whatever L5 offsets the DV source carried. When DV and HDR10 geometry differ (e.g. a letterboxed DV injected into an already-cropped base) the stale offsets rode through and signalled phantom bars. Always write the detected active area, including zeros, so stale source L5 resets to the full base frame.
2026-05-25 17:04:50 -06:00
imSp4rky 7017bd0460 feat(import): reconstruct downloads from an --export sidecar
Add a shareable --export schema and a new `unshackle import` command that rebuilds tracks from the export, injects the licensed content keys, and runs the normal download/decrypt/mux pipeline with no re-authentication or licensing.
2026-05-24 21:17:31 -06:00
imSp4rky 13f924f825 feat(dl): add real bitrate probing (-rvb/-rab)
Add --real-video-bitrate/-rvb and --real-audio-bitrate/-rab to measure a track's true bitrate from actual media size instead of the manifest's declared value, which is often inflated. Useful for accurate track listings and --vbitrate/--vbitrate-range selection.
Single-file tracks are measured exactly; segmented HLS/DASH/ISM are sampled. Only the top renditions per quality tier are probed, in parallel, so it stays fast. Without the flags, behaviour is unchanged.
2026-05-24 17:30:40 -06:00
imSp4rky 7654e91ebc feat(dl): gate s_lang/a_lang miss behind --best-available
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/
2026-05-22 13:52:35 -06:00
imSp4rky b0ae88812c fix(vaults): enable WAL on SQLite vault to fix concurrent locks 2026-05-21 23:00:39 +00:00
imSp4rky 746b573711 test(remote): add unit + e2e suite for remote-services subsystem
Covers RemoteClient/RemoteService, REST routes, handlers, SessionStore, InputBridge, DownloadQueueManager, errors, compression, and serve CLI. E2e tier opts in via --live and can auto-spawn its own serve.
2026-05-21 10:45:25 -06:00
imSp4rky 9c905ef7a3 fix(api): load real CDM for service init in list/session/download flows 2026-05-21 10:31:52 -06:00
imSp4rky c19a5ebdd2 fix(api): propagate Click default=None through service instantiation
Manual kwarg binding dropped None defaults, so services with required positional args like `type` or `profile` raised TypeError via the API while local CLI worked. Replace it with `ctx.invoke(service.cli, ...)` so Click handles defaults, and factor the duplicated setup into load_service_yaml / build_parent_ctx / instantiate_service.

Also resolve a real CDM (load_full_cdm) for search/list_titles/list_tracks; the lightweight stub lacks device_type/security_levelthat services read in __init__.
2026-05-21 08:49:16 -06:00
imSp4rky d2380cff97 feat(dl): live countdown for --slow delay 2026-05-18 13:33:46 -06:00
imSp4rky 684e56eb97 feat(tracks): configurable audio codec priority for tie-breaking
Adds optional `audio.codec_priority` list in unshackle.yaml to define preferred audio codec order when tracks share the same bitrate and language. Listed codecs rank in the order given; unlisted codecs retain bitrate-based order and fall after the listed group (soft priority - nothing dropped). Atmos and descriptive rules still apply last.
2026-05-18 11:08:55 -06:00
imSp4rky 900ad1fde1 fix(titles): normalize odd resolutions in filename quality token
Non-16:9 aspect ratios with widths like 1918x1080 or 1620x720 were producing filenames like 911p. Snap width to the nearest standard (3840/2560/1920/1280) within 50px and snap the resulting resolution back to the track's actual height when within 10px or already a standard step.
2026-05-17 19:06:53 -06:00
imSp4rkyandCodeName393 64da561534 feat(vaults): tolerate vault failures during key get/add
Wrap vault get_key/add_key/add_keys calls in broad exception handlers so a single failing vault (network, auth, driver error) no longer aborts the operation - other vaults are still consulted/written. Failure cause is logged at WARNING so issues remain debuggable.

Inspired by unshackle-dl/unshackle#104 by @CodeName393.

Co-authored-by: CodeName393 <62503817+CodeName393@users.noreply.github.com>
2026-05-17 12:18:32 -06:00
sp4rk.y 13dcd7aa1a Merge pull request #109 from JohnVeness/seasons
episode.py: season/seasons pluralization
2026-05-17 12:03:54 -06:00
imSp4rky 61fe16e8d7 feat(api): sync /api/download with dl CLI flags and add serve.* defaults
- Wire --no-proxy-download through download_manager + handlers + swagger
- Add tag/proxy/tmdb_id/animeapi_id/enrich/worst to DEFAULT_DOWNLOAD_PARAMS
- Normalize `slow` (bool/"MIN-MAX" string/list) to tuple before invoking dl.result
- Overlay any /api/download flag declared under `serve:` in unshackle.yaml as a default (downloads, workers, best_available, etc.); request body still wins
- Quiet successful worker stderr from `warning` to `debug` (kept under job.worker_stderr for ?full=true)
- Include HYBRID in range validator
- Document new flags + overlay layering + max_concurrent_downloads / download_job_retention_hours
2026-05-17 11:54:02 -06:00
John Veness e5a287bc14 episode.py: season/seasons pluralization
If num_seasons = 0, output "0 seasons" (not sure if this would ever occur). If num_seasons = 1, output "1 season". If num_seasons >=2 output "X seasons".
2026-05-17 15:06:55 +01:00
imSp4rky 34a6e2d8e2 fix(service): render request_input prompt via rich console 2026-05-16 16:43:47 -06:00
imSp4rky f26f9bcbe2 feat(video): normalize SPS VUI to match manifest-derived range
Some services ship HDR10/HLG bitstreams whose SPS VUI still carries BT.709 colour primaries/transfer/matrix, causing mediainfo and downstream players to mis-classify the file. The manifest-derived `Video.range` is the source of truth; rewrite the VUI with ffmpeg h264_metadata/hevc_metadata BSF after repackage and before mux. Skips SDR, DV, and HYBRID; no-op when the VUI is already correct.
2026-05-16 13:55:05 -06:00
imSp4rky ead88fe066 feat(hls): detect DV-composite tracks and restore signaling post-mux
Parse HLS SUPPLEMENTAL-CODECS to identify tracks that ship Dolby Vision RPU NALs in a stream whose primary codec is plain hvc1 (e.g. fMP4 ladders signalled as dvh1.08.x only via SUPPLEMENTAL-CODECS). Tag such tracks with the new `dv_compatible_bitstream` flag on Video.

Add DVFixup helper that runs `dovi_tool extract-rpu | inject-rpu` on flagged tracks before mux so the muxed MKV is recognised as Dolby Vision. Soft-fails to the source bitstream if dovi_tool is missing or any step errors.

Range stays whatever VIDEO-RANGE signalled. HDR10+ presence is a bitstream feature, not a codec-string feature, so services that know their encoder embeds HDR10+ SEI must override Video.range themselves.
2026-05-16 13:52:57 -06:00
imSp4rky b4d422459c refactor(hybrid): extract dovi_tool and run_step helpers
Centralise dovi_tool subcommand invocations behind a thin wrapper module so Hybrid no longer re-implements argv construction, status spinners, and stderr handling per call site. Adds a generic `run_step` helper for subprocess steps that must produce a non-empty output file.
2026-05-16 13:50:55 -06:00
imSp4rky cda8120b6d fix(title): use original-language audio for filename metadata
When a non-original audio language is the default (via muxing.default_language or sort order), the filename audio codec/channel fields still reflect the title's original-language track instead of whichever track appears first in the muxed MKV.
2026-05-16 11:56:04 -06:00
imSp4rky c0dc1eb91f docs(output): document muxing.default_language override 2026-05-15 08:22:45 -06:00
imSp4rky ccf8bafaf7 feat(mux): add muxing.default_language to override default track per type
Allows users to force a preferred audio/video/subtitle language as the MKV default track regardless of the title's original_language. Each track type falls back to its previous default rule when no match is found.
2026-05-14 14:24:23 -06:00
imSp4rky 0aca0e8888 feat(dl): cache content keys in-memory to skip duplicate license requests
A title with many tracks sharing the same KID issued one license request per track even when keys were identical. Add an in-memory KID -> key cache shared across all tracks of a single invocation, populated on vault hit and on license success. Subsequent tracks with cached KIDs short-circuit before the vault and license calls, reducing traffic to one request per unique KID.
2026-05-14 11:58:31 -06:00
imSp4rky 60441f05c4 fix(title): detect Atmos across all audio tracks for filename template
The {atmos?} placeholder checked only the first MediaInfo audio track, so a mux with a non-Atmos dub listed first dropped the Atmos tag from the filename even when another track carried JOC. Scan all audio tracks instead.
2026-05-14 10:22:55 -06:00
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.
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
imSp4rkyandpanitan103 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
imSp4rky 5e801580a3 fix(hybrid): read actual HDR metadata for HDR10+ to DV conversion
The L6 metadata in convert_hdr10plus_to_dv was hardcoded to 1000 nits max mastering display luminance. Now probes the source stream via ffprobe to extract the real mastering display and content light level values, preserving accurate luminance for sources mastered above 1000 nits.
2026-03-30 17:02:06 -06:00
imSp4rky 47b3390bd0 feat(dl): allow --slow to accept custom delay range
--slow now supports an optional MIN-MAX range (e.g., --slow 20-40) for custom delay between title downloads. Bare --slow retains the original 60-120s default. Minimum delay enforced at 20 seconds.
2026-03-30 16:25:29 -06:00
imSp4rky c7fd2a904c Merge branch 'dev' of https://github.com/unshackle-dl/unshackle into dev 2026-03-29 23:46:28 -06:00
imSp4rky 13ebdddaf4 Merge branch 'main' into dev 2026-03-29 23:45:53 -06:00
imSp4rky d37a1a514f chore(gitignore): ignore binary files in unshackle/binaries/ 2026-03-30 05:27:37 +00:00
imSp4rky ccc494be06 Merge branch 'feat/unified-downloader' into dev
# Conflicts:
#	unshackle/core/manifests/hls.py
2026-03-30 05:24:09 +00:00
imSp4rky d3594ca67c fix(remote): forward track selection params to server and improve error display
Forward range, vcodec, quality, and best_available from client to server so services fetch the correct manifests (e.g. HDR10 instead of SDR).
Exit with error when no video tracks match the requested range filter.
Use Rich logger for remote API errors instead of plain click.ClickException.
2026-03-30 05:20:32 +00:00
imSp4rky 1a6f2c5b7e fix(serve): allow remote-only mode without output_template and fix CORS/auth for Cloudflare
Move output_template validation from config init to dl command so serve, search, and other non-download commands work without it configured. Fix CORS header to use X-Secret-Key (matching actual auth header). Exempt /api/health from auth for Cloudflare tunnel health checks.
2026-03-30 04:36:23 +00:00
imSp4rky 3d5e46a2e3 feat(hls): probe TS segments for resolution and codec when master playlist lacks RESOLUTION/CODECS tags
Some HLS services serve master playlists without RESOLUTION or CODECS attributes, leaving video tracks with no width/height/codec info which causes crashes in by_resolutions() quality selection.

After parsing tracks in to_tracks(), any video track missing resolution or codec is now probed by fetching the first 8KB of its first TS segment and parsing the H.264/H.265 SPS NAL unit to extract the actual width, height, and codec. This approach mirrors how cat-catch/hls.js determines resolution from the media data rather than relying on playlist metadata.
2026-03-29 15:55:53 -06:00
Andy 5a3ac81ff9 feat(session): translate requests 'data' kwarg to rnet equivalents for compatibility 2026-03-26 16:36:35 -06:00
Andy e323f6f3b3 feat(template): add configurable folder naming via output_template.folder (#94)
Adds an optional `folder` key under `output_template` to customize output folder names using the same template variables as file naming.
2026-03-25 21:42:47 -06:00
Andy 10cca7d0ea fix(sanitize): restore parentheses stripping in filename sanitization (#93)
Commit 6ce7b6c accidentally removed () from the unsafe-characters regex
2026-03-25 19:46:00 -06:00
Andy 7358619a40 fix(deps): bump PyJWT minimum to 2.12.0 for CVE-2026-32597
PyJWT <= 2.11.0 accepts unknown `crit` header extensions in violation of RFC 7515 §4.1.11. Bump lower bound to 2.12.0 which includes the fix.
2026-03-25 15:16:21 -06:00
Andy b524585d78 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-25 15:06:41 -06:00
Andy fe1ccd085c Revert "fix(drm): add track ID fallback for mp4decrypt CBCS zero-KID content"
This reverts commit 23466cae8b.
2026-03-25 14:39:08 -06:00
Andy 23466cae8b fix(drm): add track ID fallback for mp4decrypt CBCS zero-KID content
Some CBCS-encrypted content has an all-zeros default_KID in the tenc box while the real KID is only in the PSSH boxes. mp4decrypt matches keys against the tenc KID, so it silently skips decryption when the provided KID doesn't match. This adds a track ID-based key fallback when a zero KID is detected, matching the existing shaka-packager zero-KID fallback behavior.
2026-03-25 14:36:26 -06:00
Andy c930abc6fd fix(subtitle): decompress gzip/zlib responses for subtitle downloads
The requests downloader used decode_content=False on raw socket reads, which skipped HTTP content-encoding decompression. Subtitle files served with Content-Encoding: gzip were saved as raw compressed bytes, then mangled by try_ensure_utf8 falling back to CP1252 decoding.

Remove decode_content=False from the raw read path — the speed gain comes from raw.read() itself, not from skipping decompression. Also add gzip/zlib magic byte detection in try_ensure_utf8 as a safety net for any edge cases where compressed data reaches encoding detection.
2026-03-24 17:44:23 -06:00
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 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
Andy eeec4e1f1b feat(tracks): add edition tags to output filenames
Extend track.edition to support a list of tags (e.g., ["IMAX", "3D"]) that are inserted into the output filename before the resolution.
2026-02-26 11:11:00 -07:00
Sp5rky 6cdfd2828b Merge pull request #66 from CodeName393/Config-Filenames
Add option to include episode titles and fix video resolution bug
2026-02-26 10:30:07 -07:00
Andy 9dc56e63c4 fix: correct formatting and add missing newlines in selector and EXAMPLE service 2026-02-26 08:10:21 -07:00
Sp5rky 31f8532131 Merge pull request #83 from CodeName393/service.py
Improve service.py
2026-02-26 08:07:05 -07:00
Sp5rky 8d05a8ceb8 Merge pull request #79 from CodeName393/select-title-update
Select title update
2026-02-26 08:02:31 -07:00
CodeName393 c5ef13df5d Update selector.py 2026-02-26 15:25:09 +09:00
CodeName393 1611fcc971 Update dl.py 2026-02-26 15:18:41 +09:00
CodeName393 0f25b0ce52 Update selector.py 2026-02-26 15:18:21 +09:00
CodeName393 00b4f2cdd1 Update selector.py 2026-02-26 15:16:34 +09:00
CodeName393 65e6ae88d0 Update dl.py 2026-02-26 15:16:22 +09:00
Andy 820db5f179 refactor(providers): extract metadata providers into modular system
- Create `unshackle/core/providers/` package with abstract base class, IMDBApi (free, no key), SIMKL, and TMDB provider implementations
- Add consensus-based ID enrichment: cross-references IMDB IDs with TMDB and SIMKL, drops all data from providers that disagree on tmdb_id (likely resolved to wrong title)
- Cache enriched IDs alongside raw provider data so they survive cache round-trips
- Genericize TitleCacher with `cache_provider()`/`get_cached_provider()` replacing provider-specific methods; respect `--no-cache` flag
- Add `--imdb` CLI flag to dl command for direct IMDB ID lookup
2026-02-25 19:02:18 -07:00
Andy 42d6ef5765 fix(n_m3u8dl_re): disable segment count validation for duration-based DASH
N_m3u8DL-RE uses Math.Ceiling to calculate segment counts from SegmentTemplate @duration, which overshoots by 1 when the division has a small fractional remainder. This causes 404 on the phantom last segment and fails with "Segment count check not pass".

Upstream bug: nilaoda/N_m3u8DL-RE#108
2026-02-25 11:11:41 -07:00
CodeName393 30269b6c17 Fix 2026-02-26 02:07:06 +09:00
CodeName393 547e9f481c Merge branch 'dev' into Config-Filenames 2026-02-26 02:06:07 +09:00
CodeName393 bde1945f67 Fix 2026-02-26 02:05:40 +09:00
CodeName393 a4e1c6bb75 Fix 2026-02-26 02:01:45 +09:00
CodeName393 b26d47fd9b Update dl.py 2026-02-25 19:27:13 +09:00
CodeName393 772bacfc8f Update selector.py 2026-02-25 19:26:35 +09:00
CodeName393 d261b4715d Fix 2026-02-25 19:22:59 +09:00
CodeName393 05dc682a2d Merge branch 'dev' into service.py 2026-02-25 19:21:22 +09:00
CodeName393 86d464dc8e Fix 2026-02-25 19:19:56 +09:00
CodeName393 20bc7d2dba Fix 2026-02-25 19:18:01 +09:00
CodeName393 b1d28d3229 Fix 2026-02-25 19:17:11 +09:00
Andy 21f0186ebf feat(dl): add --output option to override download directory 2026-02-25 00:12:24 -07:00
Andy 86a7a95b6f docs: update and correct configuration documentation 2026-02-24 23:50:33 -07:00
Andy d3fb0b6b24 refactor(example): migrate EXAMPLE service to track_request pattern 2026-02-24 13:29:10 -07:00
Andy 34f4d6bff7 fix(track): fallback to requests downloader from n_m3u8dl_re for unsupported track types 2026-02-24 12:57:46 -07:00
CodeName393 eefb6fcad7 Fix 2026-02-25 01:44:33 +09:00
CodeName393 c78a649170 Fix 2 2026-02-25 01:34:58 +09:00
CodeName393 c01e3993ce Fix 1 2026-02-25 01:34:24 +09:00
CodeName393 eba9f846b0 Improve service.py
Fix the issue of missing the get_playready_license service helper function on service.py
2026-02-25 00:44:04 +09:00
Andy b11b23e529 chore(release): bump version to 3.1.0 2026-02-23 16:27:05 -07:00
Andy 19341e2569 refactor(dl): remove legacy multi-fetch loop for unmigrated services 2026-02-23 16:21:54 -07:00
Andy d0341f6844 feat(core): add TrackRequest system for multi-codec/multi-range support
Add TrackRequest dataclass to Service base class that centralizes CLI vcodec/range/best_available params. Services read from self.track_request instead of accessing ctx.parent.params directly.

- Add TrackRequest dataclass with codecs, ranges, best_available fields
- Set self.track_request in Service.__init__() from CLI params
- Add _get_tracks_for_variants() helper for per-codec/range API calls
- Update dl.py to detect migrated vs legacy services automatically
- Handle HYBRID+other ranges (e.g. -r HYBRID,SDR) correctly in dl.py
- Support --best-available with multi-range/codec (skip unavailable)
2026-02-23 15:47:27 -07:00
Andy 983fd18d53 fix(session): reduce default max_retries from 10 to 5 for CurlSession
fix(service): reduce max_retries from 15 to 5 for HTTPAdapter
2026-02-23 12:59:07 -07:00
Andy 8b63be4f3e feat(dl): add --repack flag to insert REPACK tag in output filenames 2026-02-22 11:42:35 -07:00
Andy 8a4399665e fix(dl): handle cross-device moves when temp and downloads differ 2026-02-22 10:52:04 -07:00
Andy 4814ba9144 fix(dl): overwrite existing files on re-download and use atomic replace
The collision-avoidance logic was preventing overwrites of files from previous runs.

Also switch subtitles with OnSegmentFilter from n_m3u8dl_re to the requests downloader so segment filtering works at download time.
2026-02-21 15:33:07 -07:00
Andy ff093a7896 fix(dl): allow selection of audio tracks for 'all' languages in addition to 'best' 2026-02-20 21:40:41 -07:00
Andy 829ae01000 fix(hybrid): accept HDR10+ tracks as valid base layer for HYBRID mode
HYBRID mode previously required a plain HDR10 track, rejecting HDR10+ (HDR10P) even though it's a perfectly valid (and superior) base layer.
HDR10+ is now preferred over HDR10 when both are available, preserving dynamic metadata in the final DV Profile 8 output.
2026-02-18 15:56:55 -07:00
Andy e7120bd063 fix(attachment): sanitize filenames with illegal Windows characters 2026-02-17 16:02:59 -07:00
Andy 42ee9d67a3 fix(hybrid): skip bitrate filter for DV tracks in HYBRID mode 2026-02-17 15:38:55 -07:00
Andy b0f5b11820 feat(debug): log binary tool versions at session start 2026-02-17 14:39:28 -07:00
Andy c10257b8dc Revert "feat(debug): add JSONL debug logging to decryption, muxing, and all downloaders"
This reverts commit cc89f4ca93.
2026-02-17 14:37:50 -07:00
Andy cc89f4ca93 feat(debug): add JSONL debug logging to decryption, muxing, and all downloaders
Expand debug logging coverage for better diagnostics when investigating download/decryption issues like QUICKTIME/cbcs problem.
2026-02-17 13:58:36 -07:00
CodeName393 d2f221f3fc Restore code comments 2026-02-18 03:36:54 +09:00
CodeName393 57ecddfeeb Delete terminal reset logic 2026-02-18 03:27:35 +09:00
CodeName393 d21a59f306 dl.py Update 2026-02-18 02:43:58 +09:00
CodeName393 b9bf8fddf5 Selector Update 2026-02-18 02:41:00 +09:00
Andy 0217086abf style: fix ruff E721, E701, and E722 lint errors 2026-02-16 13:37:23 -07:00
Andy df92f9e4b6 refactor(hybrid): replace log.info with console status and add JSONL debug logging 2026-02-16 13:33:11 -07:00
Andy 9ed56709cd Merge branch 'dev' of https://github.com/unshackle-dl/unshackle into dev 2026-02-15 17:38:52 -07:00
Andy f96f1f9a95 feat(hybrid): add L5 active area and dynamic L6 luminance metadata
Add crop detection via ffmpeg to generate Level 5 active area metadata, resolving DV/HDR10 black bar mismatches. Update Level 6 to extract actual luminance values from the RPU instead of hardcoding defaults.
2026-02-15 17:38:43 -07:00
Sp5rky 9f9a609d71 Merge pull request #77 from CodeName393/Select-Titles
Add Select titles option
2026-02-15 16:19:42 -07:00
Andy cee7d9a75f fix(n_m3u8dl_re): pass all content keys for DualKey DRM decryption 2026-02-15 13:37:49 -07:00
Andy bf9087a1ce chore(release): bump version to 3.0.0
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
2026-02-15 13:04:42 -07:00
Andy 23cc351f77 feat(tracks): prioritize Atmos audio tracks over higher bitrate non-Atmos 2026-02-15 12:08:27 -07:00
Andy 132d3549f9 fix(main): update copyright year dynamically in version display 2026-02-11 16:01:33 -07:00
Andy 3ee554401a feat(HLS): improve audio codec handling with error handling for codec extraction 2026-02-10 08:34:54 -07:00
Andy 29a697a8e7 fix(tracks): close temp session and improve path type error 2026-02-08 20:04:22 -07:00
Andy c5b063391c fix(serve): default PlayReady access to none
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).
2026-02-08 20:00:39 -07:00
Andy 5fa0b33664 revert(monalisa): pass key via argv again
Reverts the env/stdin key passing change introduced in 6c83790, since ML-Worker builds in use expect the key as argv[1].
2026-02-08 19:51:22 -07:00
Andy 5650c2b591 fix(hls): remove no-op encryption_data reassignment 2026-02-08 10:43:49 -07:00
Andy 5f49663ea8 fix(monalisa): harden wasm calls and license handling
- Validate _monalisa_context_alloc return and cleanup on init failure
- Derive deterministic KID when DCID missing to avoid collisions
- Ensure stackRestore always runs via try/finally in _ccall
- Log base64 decode failures without leaking license contents
- Add bounds/alignment checks for i32 memory writes
2026-02-08 10:39:23 -07:00
CodeName393 dd19f405a4 Add selector 2026-02-09 02:21:04 +09:00
CodeName393 dbebf68f18 Add select-titles 2026-02-09 02:20:26 +09:00
Andy 6b8a8ba8a8 feat(cdm): normalize CDM detection for local and remote implementations
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
2026-02-08 00:37:53 -07:00
Andy b9fb928292 fix(service): redact proxy credentials in logs
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.
2026-02-07 20:36:25 -07:00
Andy 984a8b9efa fix(proxies): harden surfshark and windscribe selection 2026-02-07 20:34:31 -07:00
Andy 71adee4ec6 fix(api): log PSSH extraction failures 2026-02-07 20:29:53 -07:00
Andy ee8f7cb650 docs(config): clarify sdh_method uses subtitle-filter 2026-02-07 20:29:31 -07:00
Andy d576174f62 fix(naming): keep technical tokens with scene_naming off
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).
2026-02-07 20:24:32 -07:00
Andy 425b3764f4 fix(titles): avoid None/double spaces in HDR tokens
Ensure dynamic-range tokens use safe fallback when not in DYNAMIC_RANGE_MAP and append exactly one space-separated token without trailing/double spaces.
2026-02-07 19:55:45 -07:00
Andy 29f0e4eee8 fix(config): normalize playready_remote remote_cdm keys 2026-02-07 19:47:21 -07:00
Andy 44ea9a90a7 fix(titles): remove trailing space from HDR dynamic range label 2026-02-07 19:46:35 -07:00
Andy 96411e5d7d fix(hls): keep range offset numeric and align MonaLisa licensing
- 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
2026-02-07 19:44:23 -07:00
Andy d404f213b1 fix(dl): avoid selecting all variants when multiple audio codecs requested
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).
2026-02-07 19:37:48 -07:00
Andy 6c83790834 fix(monalisa): avoid leaking secrets and add worker safety
- 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.
2026-02-07 19:24:15 -07:00
Andy a04f1ad4db fix(gluetun): stop leaking proxy/vpn secrets to process list
- 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
2026-02-07 19:22:13 -07:00
Andy 774b9ba96c fix(dl): preserve proxy_query selector (not resolved URI) 2026-02-07 19:09:29 -07:00
Andy 0b9a3a75f8 fix(serve)!: make PlayReady users config consistently a mapping
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.
2026-02-07 19:06:22 -07:00
Andy c7d4a68cbf fix(aria2c): warn on config mismatch and wait for RPC ready
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.
2026-02-07 19:04:39 -07:00
Andy 4bc2e93d09 fix(dl): support snake_case keys for RemoteCdm
Use safe get() fallbacks for RemoteCdm config keys and default security_level to 3000 to avoid KeyError when snake_case is used.
2026-02-07 19:00:12 -07:00
Andy de41395a45 fix(dl): invert audio codec suffixing when splitting 2026-02-07 18:56:30 -07:00
Andy e50dd3f2bc Merge branch 'dev' of https://github.com/unshackle-dl/unshackle into dev
# Conflicts:
#	CHANGELOG.md
2026-02-07 18:47:23 -07:00
Andy 6f3aafebc5 docs(changelog): update cliff config and regenerate changelog
merge commit filtering, deduplication, granular chore parsing, and regenerate CHANGELOG.md using git-cliff.
2026-02-07 18:46:43 -07:00
Andy a66234190c docs(changelog): complete 2.4.0 notes 2026-02-07 18:05:03 -07:00
Andy 62aa85c666 chore(release): bump version to 2.4.0 2026-02-07 14:52:54 -07:00
Andy 03c309303c fix(downloader): restore requests progress for single-url downloads 2026-02-06 23:34:15 -07:00
Andy ace89760e7 fix(hls): finalize n_m3u8dl_re outputs
- 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.
2026-02-06 16:17:06 -07:00
Andy 3eede98376 Merge branch 'fix/hybrid-temp-cleanup' 2026-02-06 11:16:44 -07:00
Andy 57918cd2a3 fix(dl): always clean up hybrid temp hevc outputs 2026-02-06 11:15:05 -07:00
Sp5rky 001d15e651 Merge pull request #75 from CodeName393/Remove-hybrid-havc-temp-file
Remove hybrid havc temp file
2026-02-06 11:14:45 -07:00
Andy 2308644374 chore(api): remove remote services 2026-02-06 10:46:39 -07:00
CodeName393 8f0b9eafd7 Remove hybrid havc temp file
Add a hybrid track to the track processing list to fix the problem that the hybrid-processed hevc file remains in the temp folder.
2026-02-06 17:53:48 +09:00
Andy 939ca25c5b fix(dl): keep descriptive and standard audio for requested langs
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
2026-02-05 12:57:52 -07:00
Sp5rky 58903c7b14 Merge pull request #70 from CodeName393/Missing-HLS-Curl-Session-Processing
Fix Missing HLS Curl Session Processing
2026-02-05 12:43:27 -07:00
CodeName393 3fa4a81a39 Fix Missing HLS Curl Session Processing 2026-02-05 19:25:51 +09:00
Andy b16610ac63 fix(progress): bind per-track bars and force terminal completion 2026-02-04 12:55:02 -07:00
Andy 5fae23eb99 feat(proxy): add specific server selection for WindscribeVPN 2026-02-04 19:49:52 +00:00
Andy 6186ff764b fix(progress): force track bar completion on terminal states 2026-02-04 12:31:49 -07:00
Sp5rky 207756c090 Merge pull request #69 from CodeName393/HDR-Vivid-File-Name-Processing
Fixed HDR Vivid showing the file name Range as 'None'
2026-02-04 12:24:24 -07:00
CodeName393 9e194f4868 Fix 2026-02-05 02:51:24 +09:00
CodeName393 5b50a6cd79 HDR Vivid 2026-02-05 02:50:03 +09:00
CodeName393 289c8a3b23 HDR Vivid 2026-02-05 02:49:50 +09:00
Andy a6494d9b54 fix(dl): prevent attachment downloads during --skip-dl
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.
2026-02-03 21:20:26 -07:00
Andy c83b7a853e fix(mux): avoid audio codec suffix on split-audio outputs 2026-02-03 17:21:51 -07:00
Andy b70c350d0b docs(changelog): add 2.4.0 release notes 2026-02-03 13:54:32 -07:00
CodeName393 ad7dd69ecd Merge branch 'dev' into Config-Filenames 2026-02-03 23:01:18 +09:00
CodeName393 ca3a6cc3ea Update 2026-02-03 23:00:53 +09:00
Andy cacb695093 fix(subtitles): preserve sidecar originals
Use original subtitle files for sidecar output while keeping muxed conversion behavior.

Fixes #59
2026-02-02 21:43:27 -07:00
Andy 64875e8371 Merge branch 'quiet-sleeping-crane' into dev 2026-02-02 20:51:17 -07:00
Andy 5b9be075de feat(audio): codec lists and split muxing 2026-02-02 20:51:09 -07:00
Andy 1cde8964c1 fix(dash): preserve MPD DRM instead of overwriting from init segment
The init_data DRM extraction was unconditionally overwriting DRM already extracted from MPD ContentProtection elements. This caused failures when init segments contain malformed PSSH data while the MPD has valid PSSH.

Now only falls back to init_data extraction when no DRM was found from the manifest, matching the behavior in version 2.1.0.
2026-02-02 12:02:16 -07:00
Andy cc55fd8922 fix(dash): add CENC namespace support for PSSH extraction
Some MPD manifests use the cenc: namespace prefix for PSSH elements (e.g., <cenc:pssh>) instead of non-namespaced <pssh>. This caused DRM extraction to fail for services.

- Add {urn:mpeg:cenc:2013}pssh fallback for Widevine PSSH extraction
- Add {urn:mpeg:cenc:2013}pssh fallback for PlayReady PSSH extraction
2026-02-02 10:59:15 -07:00
Andy 84466e12de Merge branch 'feat/monalisa-drm' into dev 2026-02-02 08:25:43 -07:00
Andy ecedcb93eb fix(drm): hide Shaka Packager message for MonaLisa decryption 2026-02-02 08:24:13 -07:00
Andy a07191ac4f fix(binaries): search subdirectories for binary files
Allow binaries to be found in subdirectories of the binaries folder.
2026-02-01 14:04:42 -07:00
Andy 6dd1ce6df9 fix(dash): handle high startNumber in SegmentTimeline for DVR manifests
When a DASH manifest has a high startNumber (common in DVR/catch-up content from live streams), the segment range calculation would produce an empty range because end_number was set to len(segment_durations) rather than being offset by startNumber.
2026-02-01 11:12:29 -07:00
Andy d0d8044fb3 feat(video): detect interlaced scan type from MPD manifests 2026-01-31 23:51:57 -07:00
Andy d4328f0eb7 fix(binaries): search subdirectories for binary files
Allow binaries to be found in subdirectories of the binaries folder.
2026-01-31 22:06:04 -07:00
Andy 3fcad1aa01 feat(drm): add MonaLisa DRM support to core infrastructure
- 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
2026-01-31 22:05:44 -07:00
Andy ef338f0124 fix(downloader): correct progress bar tracking for segmented downloads
Use segmented=True when downloading multiple URLs to prevent inner downloads from overriding the total segment count, which caused the progress bar to always appear green (finished state).

This is still WIP so will continue to monitor.
2026-01-31 20:00:30 -07:00
Andy caf67a6998 feat(env): add ML-Worker binary for DRM licensing 2026-01-31 16:58:10 -07:00
Andy 5cf488cb34 docs: add configuration documentation WIP 2026-01-30 16:34:49 -07:00
Andy c352884c17 fix(proxy): collect servers from all locations in WindscribeVPN
Previously get_random_server only collected servers from the first location matching a country code. Now it collects from all matching locations before selecting randomly.
2026-01-30 15:52:44 -07:00
Andy f852a43e7f Merge branch 'main' into dev 2026-01-30 15:44:17 -07:00
Andy d2d86fa6b7 Merge remote-tracking branch 'origin/main' into dev 2026-01-30 15:39:50 -07:00
Andy e2b65cef4a fix(proxy): remove regional restrictions from WindscribeVPN
OpenVPN credentials now work reliably on all regions, not just US, AU, and NZ. Remove the supported_regions check that was blocking other country codes.
2026-01-30 22:38:46 +00:00
Andy 385fcb2752 Revert "Merge pull request #64 from Aerglonus/dev"
This reverts commit 55bc2b16ee, reversing
changes made to 8c8c9368ba.
2026-01-30 15:52:06 +00:00
Sp5rky 55bc2b16ee Merge pull request #64 from Aerglonus/dev
fix(proxies): Fixes WindscribeVPN server authentication
2026-01-30 08:19:27 -07:00
Andy 8c8c9368ba fix(manifests): correct DRM type selection for remote PlayReady CDMs
HLS: Filter segment keys by CDM type during aria2c merge phase to prevent incorrect Widevine selection when using PlayReady-only CDMs. The merge phase now uses filter_keys_for_cdm() before get_supported_key(), matching the pattern used in initial licensing.

DASH: Extend PlayReady CDM detection to include remote CDMs with is_playready attribute, not just native PlayReadyCdm instances. This ensures correct DRM extraction order from init_data when using remote PlayReady CDMs.
2026-01-29 10:34:03 -07:00
CodeName393 77d55153e6 Add insert_episodename_into_filenames config option 2026-01-29 02:38:50 +09:00
CodeName393 4988d37fe9 Update episode name handling in filename 2026-01-29 02:38:31 +09:00
CodeName393 d03d54b0bb Add filename configuration options
Added filename configuration options for handling Unicode and episode names.
2026-01-29 02:37:34 +09:00
Aerglonus a96bc9e4a6 fix(proxies): Fixes WindscribeVPN server authentication 2026-01-27 00:48:03 -06:00
Andy b72a5dd84a fix(n_m3u8dl_re): remove duplicate --write-meta-json argument causing download failures 2026-01-26 22:02:36 -07:00
Andy ab762fc81f fix(serve): correct PlayReady RemoteCDM server validation 2026-01-26 22:01:47 -07:00
Andy cf537891f7 fix(subs): route pycaption-unsupported formats to pysubs2 in auto mode 2026-01-26 10:13:03 -07:00
Andy 4b1d938b49 feat(cdm): add remote PlayReady CDM support via pyplayready RemoteCdm 2026-01-26 01:08:54 -07:00
Andy 98d579dc9b feat(serve): add PlayReady CDM support alongside Widevine 2026-01-26 00:48:20 -07:00
Andy 91a2d76f88 refactor: remove remote-service code until feature is more complete
Temporarily removes client-side remote service discovery and authentication until the implementation is more fleshed out and working.
2026-01-24 16:10:45 -07:00
Andy 4b30090d87 feat(gluetun): improve VPN connection display and Windscribe support 2026-01-24 13:36:04 -07:00
Andy e77f000494 feat: Gluetun VPN integration and remote service enhancements
Major features:
- Native Docker-based Gluetun VPN proxy provider with multi-provider support
  (NordVPN, Windscribe, Surfshark, ExpressVPN, and 50+ more)
- Stateless remote service architecture with local session caching
- Client-side authentication for remote services (browser, 2FA, OAuth support)

Key changes:
- core/proxies/windscribevpn.py: Enhanced proxy handling
- core/crypto.py: Cryptographic utilities
- docs/VPN_PROXY_SETUP.md: Comprehensive VPN/proxy documentation
2026-01-24 11:23:13 -07:00
Andy e3767716f3 feat(debug): add download output verification logging
Add comprehensive debug logging to diagnose N_m3u8DL-RE download failures where the process exits successfully but produces no output files.
2026-01-24 10:36:43 -07:00
Andy 6f5f25fa9b refactor(remote_auth): remove unused requests.Session
The session was created with headers but never used. The class saves sessions locally via the cache rather than uploading to a remote server.
2026-01-23 17:20:39 -07:00
Andy 0c7d20c943 fix(api): validate Bearer prefix before extracting API key
The replace("Bearer ", "") approach returned the full Authorization header value when the prefix was not present, incorrectly treating other auth schemes (e.g., "Basic xyz") as API keys.
2026-01-23 17:20:22 -07:00
Andy 6b90a19632 perf(aria2c): improve download performance with singleton manager
- Use singleton _Aria2Manager to reuse one aria2c process via RPC
- Add downloads via aria2.addUri instead of stdin input file
- Track per-GID byte-level progress (completedLength/totalLength)
- Add thread-safe operations with threading.Lock
- Enable graceful cancellation by removing individual downloads via RPC
2026-01-23 17:14:12 -07:00
Andy b8e2f3da3f feat(titles): use track source attribute for service name in filenames
Allow services to set a custom `source` attribute on tracks, which will be used in the filename instead of the service class name.
2026-01-22 20:21:00 -07:00
Andy af49560345 Merge branch 'main' into dev 2026-01-22 14:47:25 -07:00
Andy f0bae6d6b4 fix(cdm): correct error key casing in Decrypt Labs API response parsing
The API returns error details with capital "Error" key, but the code was checking for lowercase "error", causing error details to be ignored.
2026-01-22 14:13:27 -07:00
Sp5rky acbb92bb82 Merge pull request #46 from MasterOfKay/main
Merging dash-naming feature - adds optional dash separator format for filenames
2026-01-22 13:58:24 -07:00
Sp5rky 73a4bf8764 Merge pull request #53 from CodeName393/main
Merging after code review - fixes binary path handling
2026-01-22 13:53:23 -07:00
stabbedbybrick a60247f485 add WVTT (WebVTT in MP4) subtitle converter for subby (#61)
* 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
2026-01-22 12:41:41 -07:00
Andy 9463870da7 fix(serve): use correct pywidevine users config format
pywidevine's serve module expects users to be a dict mapping secret keys to user objects with devices and username, not a simple list.
This was causing TypeError when accessing CDM endpoints.
2026-01-22 17:33:19 +00:00
CodeName393 3c049a1fc0 Fix n_m3u8dl_re 2026-01-22 15:28:24 +09:00
CodeName393 f3cc1d080e Fix Hybrid 2026-01-22 15:27:57 +09:00
Andy eaad0e3cc2 chore(release): bump version to 2.3.1 2026-01-22 00:39:41 +00:00
CodeName393 b384139b41 Fix 2 2026-01-22 02:41:21 +09:00
CodeName393 9cd67568d2 Fix 2026-01-22 02:34:34 +09:00
CodeName393 28b91fddd3 Merge branch 'main' into main 2026-01-22 02:29:08 +09:00
Andy 71bbb27d47 fix(deps): upgrade vulnerable dependencies for security alerts
- urllib3: 2.5.0 -> 2.6.3 (CVE-2025-66418, CVE-2025-66471, CVE-2026-21441)
- aiohttp: 3.13.2 -> 3.13.3 (8 CVEs including CVE-2025-69223, CVE-2025-69227)
- fonttools: 4.60.1 -> 4.61.1 (CVE-2025-66034)
- filelock: 3.19.1 -> 3.20.3 (CVE-2025-68146, CVE-2026-22701)
- virtualenv: 20.34.0 -> 20.36.1 (CVE-2026-22702)
2026-01-21 15:09:51 +00:00
Andy 766447cd71 fix(hls): prefer media playlist keys over session keys for accurate KID matching
Session keys from master playlists often contain PSSHs with multiple KIDs covering all tracks, causing licensing to return keys for wrong KIDs.

Changes:
- Unified DRM licensing logic for all downloaders
- Prefer media playlist EXT-X-KEY tags which contain track-specific KIDs
- Add filter_keys_for_cdm() to select keys matching configured CDM type
- Add get_track_kid_from_init() to extract KID from init segment with fallback to drm.kid from PSSH
- Track initial_drm_key to prevent double-licensing on first segment
- Simplify n_m3u8dl_re block to reuse common licensing flow
- Use strict PlayReady keyformat matching via PR_PSSH.SYSTEM_ID URN instead of loose substring match
- Fix PlayReady keyformat comparisons that incorrectly compared strings to PlayReadyCdm class
- Fix byterange header format in get_track_kid_from_init() to use HLS.calculate_byte_range()

Also fixes PlayReady keyformat matching in:
- unshackle/core/tracks/track.py
- unshackle/core/drm/playready.py

Fixes download failures where track_kid was null or mismatched, causing wrong content keys to be obtained during PlayReady/Widevine licensing.
2026-01-21 00:20:07 +00:00
Andy 477fd7f2eb fix(n_m3u8dl_re): include language in DASH audio track selection
When DASH manifests have multiple audio AdaptationSets with the same representation IDs (e.g., both English and Japanese having id="0"), N_m3u8DL-RE would download the same track twice.

Now includes the language alongside the ID in selection args to properly disambiguate tracks across adaptation sets.
2026-01-19 20:18:45 +00:00
Andy 90a7db2e46 fix(subs): update SubtitleEdit CLI syntax and respect conversion_method
- Use lowercase format names (subrip, webvtt, advancedsubstationalpha) to match SubtitleEdit 4.x CLI requirements
- Change /Convert to /convert for consistency with CLI docs
- Convert Path objects to strings explicitly for subprocess calls
- Respect conversion_method config in SDH stripping - skip SubtitleEdit when user has set pysubs2/pycaption/subby as their preferred method
- Add stderr suppression to SubtitleEdit calls
2026-01-19 00:32:19 +00:00
Andy abd8fc2eb9 chore(release): bump version to 2.3.0 2026-01-18 19:03:14 +00:00
Andy e99cfddaec fix(subs): handle WebVTT cue identifiers and overlapping multi-line cues
Some services use WebVTT files with:
- Cue identifiers (Q0, Q1, etc.) before timing lines that pysubs2/pycaption incorrectly parses as subtitle text
- Multi-line subtitles split into separate cues with 1ms offset times and different line: positions (e.g., line:77% for top, line:84% for bottom)

Added detection and sanitization functions:
- has_webvtt_cue_identifiers(): detects cue identifiers before timing
- sanitize_webvtt_cue_identifiers(): removes problematic cue identifiers
- has_overlapping_webvtt_cues(): detects overlapping cues needing merge
- merge_overlapping_webvtt_cues(): merges cues sorted by line position
2026-01-18 04:44:08 +00:00
Andy 4e11f69a58 fix(drm): filter Widevine PSSH by system ID instead of sorting
The previous sorting approach crashed with KeyError when unsupported DRM systems were present in the init segment. Now uses direct filtering
2026-01-17 13:36:57 +00:00
Andy aec3333888 fix(subs): handle negative TTML values in multi-value attributes
The previous regex only matched negative size values when they were the entire quoted attribute (e.g., "-5%"). This failed for multi-value attributes like tts:extent="-5% 7.5%" causing pycaption parse errors.

The new pattern matches negative values anywhere in the text and preserves the unit during replacement.

Closes #47
2026-01-16 14:16:47 +00:00
Andy 68ad76cbb0 feat(config): add unicode_filenames option to preserve native characters
Add config option to disable ASCII transliteration in filenames, allowing preservation of Korean, Japanese, Chinese, and other native language characters instead of converting them via unidecode.

Closes #49
2026-01-16 13:43:50 +00:00
Andy 18b0534020 fix(subs): strip whitespace from ASS font names
Use removeprefix instead of removesuffix and add strip() to handle ASS subtitle files that have spaces after commas in Style definitions.

Fixes #57
2026-01-16 13:42:11 +00:00
Andy d0cefa9d58 fix(drm): include shaka-packager binary in error messages 2026-01-16 13:26:15 +00:00
Andy a01f335cfc fix(dash): handle N_m3u8DL-RE merge and decryption
- Add skip_merge flag for N_m3u8DL-RE to prevent duplicate init data
- Pass content_keys to N_m3u8DL-RE for internal decryption handling
- Use shutil.move() instead of manual merge when skip_merge is True
- Skip manual decryption when N_m3u8DL-RE handles it internally

Fixes audio corruption ("Box 'OG 2' size is too large") when using N_m3u8DL-RE with DASH manifests that have SegmentBase init data. The init segment was being written twice: once by N_m3u8DL-RE during its internal merge, and again by dash.py during post-processing.
2026-01-16 13:25:34 +00:00
Andy b01fc3c8d1 fix(dash): handle placeholder KIDs and improve DRM init from segments
- Add CENC namespace support for kid/default_KID attributes
- Detect and replace placeholder/test KIDs in Widevine PSSH:
  - All zeros (key rotation default)
  - Sequential 0x00-0x0f pattern
  - Shaka Packager test pattern
- Change DRM init condition from `not track.drm` to `init_data` to ensure DRM is always re-initialized from init segments

Fixes issue where Widevine PSSH contains placeholder KIDs while the real KID is only in ContentProtection default_KID attributes.
2026-01-15 12:50:22 +00:00
Andy 44acfbdc89 fix(drm): correct PSSH system ID comparison in PlayReady
Remove erroneous `.bytes` accessor from PSSH.SYSTEM_ID comparisons in from_track() and from_init_data() methods. The pyplayready PSSH.SYSTEM_ID is already the correct type for comparison with parsed PSSH box system_ID values.
2026-01-15 12:48:18 +00:00
Andy d1d3daf750 chore(release): bump version to 2.2.0 2026-01-15 03:07:18 +00:00
Andy a7b6e9e680 feat(drm): add CDM-aware PlayReady fallback detection
Add PlayReady PSSH/KID extraction from track and init data with CDM-aware ordering. When PlayReady CDM is selected, tries PlayReady first then falls back to Widevine. When Widevine CDM is selected (default), tries Widevine first then falls back to PlayReady.
2026-01-15 02:49:56 +00:00
Andy 415544775b fix(vaults): adaptive batch sizing for bulk key operations 2026-01-14 23:04:54 +00:00
Andy 6740dd3dfa Revert "fix(vaults): batch bulk key operations to avoid query limits"
This reverts commit 7e7bc7aecf.
2026-01-14 23:00:43 +00:00
Andy fcd70e5b0f fix(titles): detect HDR10 in hybrid DV filenames correctly
Hybrid DV+HDR10 files were named "DV.H.265" instead of "DV.HDR.H.265" because the HDR10 detection only checked hdr_format_full which contains "Dolby Vision / SMPTE ST 2094". The "HDR10" indicator is in hdr_format_commercial, not hdr_format_full.

Now checks both fields for HDR10 compatibility indicators.
2026-01-14 22:25:58 +00:00
Andy 7e7bc7aecf fix(vaults): batch bulk key operations to avoid query limits 2026-01-11 08:21:02 +00:00
Andy ede38648db fix(util): improve test command error detection and add natural sorting 2026-01-07 16:22:45 +00:00
Andy 17a91ee4bb feat(debug): add comprehensive debug logging for downloaders and muxing 2026-01-05 09:50:33 +00:00
CodeName393 450cde7c80 Troubleshooting some string overlap 2025-12-31 19:27:39 +09:00
CodeName393 197fe76f7a Replace ffmpeg string with FFMPEG variable 2025-12-31 19:27:10 +09:00
MasterOfKay 8aec80246a added config.py changes 2025-11-28 16:27:26 +01:00
Andy dc9823cd28 chore(release): bump version to 2.1.0 2025-11-27 23:35:56 +00:00
Andy 6fa3554b70 fix(dl): preserve attachments when rebuilding track list
Attachments (screenshots, fonts) were being dropped when title.tracks was rebuilt from kept_tracks, causing image files to remain in temp directory after muxing. The cleanup code iterated over an empty attachments list since they were orphaned during track filtering.
2025-11-27 23:03:53 +00:00
Andy 965482a1e4 feat: merge upstream dev branch
- Add Gluetun dynamic VPN-to-HTTP proxy provider
   - Add remote services and authentication system
   - Add country code utilities
   - Add Docker binary detection
   - Update proxy providers
2025-11-25 20:23:06 +00:00
Andy 2d4bf140fa fix(dash): add AdaptationSet-level BaseURL resolution
Add support for BaseURL elements at the AdaptationSet level per DASH spec. The URL resolution chain now properly follows: MPD → Period → AdaptationSet → Representation.
2025-11-25 16:09:28 +00:00
Andy d0816787ce fix: restrict WindscribeVPN to supported regions 2025-11-24 18:17:00 +00:00
Andy 3d384b8e3e fix(windscribevpn): add error handling for unsupported regions in get_proxy method 2025-11-23 08:06:43 +00:00
Andy e0a666ada6 fix(utilities): make space-hyphen-space handling conditional on scene_naming 2025-11-21 19:22:20 +00:00
Andy 26c81779fa fix(utilities): handle space-hyphen-space separators in sanitize_filename
Pre-process space-hyphen-space patterns (e.g., "Title - Episode") before other character replacements to prevent creating problematic dot-hyphen-dot (.-.) patterns in filenames.

This addresses PR #44 by fixing the root cause rather than post-processing the problematic pattern. The fix ensures that titles like "Show - S01E01" become "Show.S01E01"
2025-11-21 19:14:54 +00:00
Andy 3b32462251 feat(cdm): add per-track quality-based CDM selection during runtime DRM switching
Enable quality-based CDM selection during runtime DRM switching by passing track quality to get_cdm() calls. This allows different CDMs to be used for different video quality levels within the same download session.

Example configuration:
  cdm:
    SERVICE:
      "<=1080": wv_l3_local     # Widevine L3 for SD/HD
      ">1080": pr_sl3_remote    # PlayReady SL3 for 4K
2025-11-16 21:59:10 +00:00
Andy a7a8c882d8 fix(video): correct CICP enum values to match ITU-T H.273 specification
- Add Primaries.Unspecified (value 2) per user request and H.273 spec
- Rename Primaries value 0 from Unspecified to Reserved for spec accuracy
- Rename Transfer value 0 from Unspecified to Reserved for consistency
- Simplify Transfer value 2 from Unspecified_Image to Unspecified
- Update condition check to use enum values instead of numeric tuple
- Enhance docstring with detailed sources and rationale for changes

All CICP values verified against ITU-T H.273, ISO/IEC 23091-2, H.264/H.265 specifications, and FFmpeg AVColorPrimaries/AVColorTransferCharacteristic enums.
2025-11-16 17:28:32 +00:00
Andy 7cc4af207e feat(export): enhance track export with URL, descriptor, and hex-formatted keys 2025-11-15 18:20:47 +00:00
Andy 492134b8ff fix(hls): convert range_offset to int to prevent TypeError
Fixed TypeError in calculate_byte_range where range_offset was a string instead of int. The byte_range.split("-")[0] returns a string, but the calculate_byte_range method expects fallback_offset parameter to be int.
2025-11-14 23:08:13 +00:00
Andy 6975f4f9f4 fix(deps): pin pyplayready to <0.7 to avoid KID extraction bug 2025-11-12 15:48:06 +00:00
Andy 751b97017b chore(deps): upgrade dependencies to latest versions 2025-11-11 23:35:09 +00:00
Andy 0bc8c637d2 Merge branch 'dev' 2025-11-10 22:32:31 +00:00
Andy 76d73355f7 docs(readme): remove dev branch warning for main merge 2025-11-10 22:31:15 +00:00
Andy c1e7fcab01 docs(changelog): set release date for version 2.0.0 2025-11-10 22:29:44 +00:00
Andy 7883ff56c6 docs(changelog): add --no-video flag and PR #38 credit 2025-11-10 22:17:20 +00:00
Andy 9488a40f51 feat(dl): add --no-video flag to skip video track downloads
Add new -nv/--no-video CLI flag that allows users to download audio, subtitles, attachments, and chapters without downloading video tracks.

Fixes #39
2025-11-10 22:12:15 +00:00
Andy 1ebb62ee91 refactor(tags): remove environment variable fallbacks for API keys 2025-11-09 23:46:31 +00:00
Andy 240c70a2aa fix(tags): skip metadata lookup when API keys not configured 2025-11-09 23:30:33 +00:00
Andy eef06fb986 fix: suppress verbose fontTools logging when scanning system fonts 2025-11-09 23:19:12 +00:00
Andy 87ff66f8fe fix: ensure subtitles use requests downloader instead of n_m3u8dl_re if Descriptor.URL
PR #38 refactored n_m3u8dl_re track selection to support DASH/ISM subtitle tracks, but this broke some subtitle downloads. Services that use direct URL downloads (Descriptor.URL) for subtitles, which n_m3u8dl_re does not support.
2025-11-09 21:27:19 +00:00
Andy 59d35da6d0 chore(deps): update requests to >=2.32.5 2025-11-09 17:47:51 +00:00
Andy 55db8da125 refactor: remove unnecessary underscore prefixes from function names 2025-11-08 22:53:47 +00:00
Andy 5d20bf9d52 fix(subtitles): fix closure bug preventing SDH subtitle stripping
Fixed a Python late binding closure issue in the SDH subtitle duplication logic that prevented strip_hearing_impaired() from being called correctly.
2025-11-08 22:49:23 +00:00
stabbedbybrick 9ed5133c4c N_m3u8DL-RE: Improve track selection, add download arguments and option to load manifest from file (#38)
* feat: Add 'from_file', 'downloader_args' to Track

* feat: Add loading HLS playlist from file

* refactor: Improve track selection, args for n_m3u8dl_re
2025-11-08 13:57:52 -07:00
Andy 90e4030a88 fix(n_m3u8dl_re): read lang attribute from DASH manifests correctly
The track_selection function was using findall() to search for lang child elements, but in DASH manifests lang is an XML attribute on AdaptationSet. This caused language selection to fail for region-specific codes like es-419.
2025-11-08 06:04:37 +00:00
Andy 11bcca9632 fix(cdm): apply session key fix to custom_remote_cdm
Apply the same partial cached keys fix from decrypt_labs_remote_cdm to custom_remote_cdm. When cached keys don't cover all required KIDs, store them in session["cached_keys"] instead of session["keys"] to allow parse_license() to properly combine vault_keys + cached_keys + license_keys.
2025-11-08 03:02:17 +00:00
Andy cc7263884f fix(cdm): resolve session key handling for partial cached keys
When decrypt-labs returns cached keys that don't cover all required KIDs, the CDM now properly stores them in session["cached_keys"] instead of session["keys"]. This allows parse_license() to correctly combine vault_keys + cached_keys + license_keys, fixing downloads that previously failed when mixing cached and fresh licenses.
2025-11-08 03:00:19 +00:00
Andy 0c3a6c47f2 fix(dl): prevent vault loading when --cdm-only flag is set
The --cdm-only flag was only preventing vault queries during DRM operations but vaults were still being loaded
2025-11-06 07:05:44 +00:00
Andy 8a46655d21 feat(subtitle): preserve original formatting when no conversion requested
Add preserve_formatting config option to prevent automatic subtitle processing that strips formatting tags and styling. When enabled (default: true), WebVTT files skip pycaption read/write cycle to preserve tags like <i>, <b>, positioning, and other formatting.
2025-11-03 23:01:31 +00:00
Andy 8b0b3045e3 feat(fonts): add Linux font support for ASS/SSA subtitles
Implements cross-platform font discovery and intelligent fallback system for ASS/SSA subtitle rendering on Linux/macOS systems.

Windows support has not been tested
2025-11-03 20:23:45 +00:00
Andy f00790f31b feat: add service-specific configuration overrides
Implement comprehensive per-service config override system that allows any configuration section (dl, n_m3u8dl_re, aria2c, subtitle, etc.) to be customized on a per-service basis.

Fixes #13
2025-11-03 16:56:58 +00:00
Andy f979e94235 fix(session): remove padding extension from OkHttp JA3 fingerprints
Remove extension 21 (TLS padding) from okhttp4 and okhttp5 JA3 strings to resolve SSL/TLS handshake failures.
2025-11-03 05:32:57 +00:00
Andy de48a98e92 docs(changelog): complete v2.0.0 release documentation 2025-11-03 03:27:36 +00:00
Andy f1fe940708 fix(session): update OkHttp fingerprint presets 2025-11-03 03:16:54 +00:00
Andy 565b0e0ea7 feat(session): add custom fingerprint and preset support
Add support for custom TLS/HTTP fingerprints to session() function, enabling services to impersonate Android/OkHttp clients instead of just browsers.
2025-11-03 01:15:49 +00:00
Andy 001f6a0146 feat(cache): add TMDB and Simkl metadata caching to title cache 2025-11-02 23:33:24 +00:00
Andy 27d0ca84a3 fix(dash): correct segment count calculation for startNumber=0
Fix off-by-one error in SegmentTemplate segment enumeration when startNumber is 0. Previously, the code would request one extra segment beyond what exists, causing 404 errors on the final segment.

The issue was that end_number was calculated as a segment count via math.ceil(), but then used incorrectly with range(start_number, end_number + 1), treating it as both a count and an inclusive endpoint.

Changed to explicitly calculate segment_count first, then derive end_number as: start_number + segment_count - 1

Example:
- Duration: 3540.996s, segment duration: 4s
- Before: segments 0-886 (887 segments) - segment 886 doesn't exist
- After: segments 0-885 (886 segments) - correct
2025-11-02 20:30:06 +00:00
Andy 597a8b7912 fix(naming): improve HDR detection with comprehensive transfer checks and hybrid DV+HDR10 support
HDR10/PQ detection now includes:
- PQ (most common)
- SMPTE ST 2084 (CICP value 16)
- BT.2100
- BT.2020-10
- smpte2084 (lowercase variant)

HLG detection now includes:
- HLG
- Hybrid Log-Gamma
- ARIB STD-B67 (CICP value 18)
- arib-std-b67 (lowercase variant)

Hybrid DV+HDR10 detection:
- Now checks full hdr_format field for both "Dolby Vision" AND
  ("HDR10" OR "SMPTE ST 2086")
- Properly generates filenames like "Movie.2160p.DV HDR H.265.mkv"
- MediaInfo reports: "Dolby Vision / SMPTE ST 2086, HDR10 compatible"

Also adds null safety for transfer characteristics to prevent errors when the field is None.
2025-11-02 03:19:14 +00:00
Andy 6ebdfa8818 fix(subtitle): resolve SDH stripping crash with VTT files
Fixes #34
2025-10-31 14:51:25 +00:00
Andy 351a606258 feat(api): add default parameter handling and improved error responses
Add default parameter system to API server that matches CLI behavior, eliminating errors from missing optional parameters.
2025-10-30 05:16:14 +00:00
Andy 504de2197a fix(drm): add explicit UTF-8 encoding to mp4decrypt subprocess calls
Fixes 'charmap' codec can't decode byte error that occurs on Windows
when mp4decrypt outputs non-ASCII characters. Without explicit encoding,
2025-10-28 18:49:13 +00:00
Andy 5c8eb2107a feat(api): complete API enhancements for v2.0.0
- Add missing download parameters (latest_episode, exact_lang, audio_description, no_mux)
- Expand OpenAPI schema with comprehensive documentation for all 40+ download parameters
- Add robust parameter validation with clear error messages
- Implement job filtering by status/service and sorting capabilities
2025-10-26 04:40:55 +00:00
Andy d0c6a7fa63 feat(api): add url field to services endpoint response 2025-10-26 04:19:43 +00:00
Andy d658b1bb26 docs: improve GitHub issue templates for better bug reports and feature requests 2025-10-24 01:17:07 +00:00
Andy bee2abcf5c docs: improve GitHub issue templates for better bug reports and feature requests 2025-10-24 01:16:01 +00:00
Andy 4787be8190 docs: update CHANGELOG for audio description feature 2025-10-24 00:56:28 +00:00
Andy ec3e150846 feat(dl): add --audio-description flag to download AD tracks
Add support for downloading audio description tracks via the --audio-description/-ad flag. Previously, descriptive audio tracks were always filtered out. Users can now optionally include them.

Fixes #33
2025-10-24 00:53:47 +00:00
Andy 3571c5eb3c style: apply ruff formatting fixes 2025-10-23 18:11:30 +00:00
Andy bdd219d90c chore: update CHANGELOG.md for version 2.0.0 2025-10-22 21:10:14 +00:00
Andy 07574d8d02 refactor(binaries): remove unused mypy import 2025-10-22 20:47:46 +00:00
Andy 9b5d233c69 fix(dl): validate HYBRID mode requirements before download
Add validation to check that both HDR10 and DV tracks are available when HYBRID mode is requested. This prevents wasted downloads when the hybrid processing would fail due to missing tracks.
2025-10-22 20:46:52 +00:00
Andy 98d4bb4333 fix(config): support config in user config directory across platforms
Fixes #23
2025-10-22 16:48:03 +00:00
Andy 1c48b282de Merge branch 'dev' of https://github.com/unshackle-dl/unshackle into dev 2025-10-22 01:37:34 +00:00
Andy 93debf149a Merge commit 'refs/pull/19/head' of https://github.com/unshackle-dl/unshackle into dev 2025-10-22 01:36:51 +00:00
Andy 57fc07ea41 Merge commit 'refs/pull/19/head' of https://github.com/unshackle-dl/unshackle into dev 2025-10-22 01:34:46 +00:00
TPD94 df09998a47 Update .gitignore 2025-10-21 21:19:55 -04:00
TPD94 e04399fbce Update binaries.py
Refactor code to search for binaries either in root of binary folder or in a subfolder named after the binary.
2025-10-21 21:18:36 -04:00
TPD94 087df59fb6 Update hls.py 2025-10-21 21:07:24 -04:00
Andy 5384b775a4 refactor(session): modernize type annotations to PEP 604 syntax 2025-10-20 21:09:19 +00:00
Andy 2a90e60a49 Merge PR #31: feat: add retry handler to curl_cffi Session 2025-10-20 19:59:58 +00:00
stabbedbybrick 1409f93de5 feat: add retry handler to curl_cffi Session 2025-10-20 18:28:12 +02:00
Andy d3ca8e7039 fix(tags): gracefully handle missing TMDB/Simkl API keys
Simkl now requires a client_id from https://simkl.com/settings/developer/
2025-10-20 03:13:30 +00:00
TPD94 8f2ead2107 Merge branch 'unshackle-dl:main' into main 2025-10-18 21:06:23 -04:00
Andy 9921690339 feat: add service-specific configuration overrides
Add support for per-service configuration overrides allowing fine-tuned control of downloader and command options on a service-by-service basis.

Fixes #13
2025-10-18 07:32:17 +00:00
Andy 3dd12b0cbe chore(api): fix import ordering in download_manager and handlers 2025-10-18 07:05:05 +00:00
Andy ed1314572b feat(dl): add --latest-episode option to download only the most recent episode
Adds a new CLI option `-le, --latest-episode` that automatically selects and downloads only the single most recent episode from a series, regardless of which season it's in.

Fixes #28
2025-10-18 07:04:11 +00:00
Andy 7a49a6a4f9 docs: add dev branch and update README 2025-10-17 20:41:09 +00:00
Andy 888647ad64 feat(proxies): add WindscribeVPN proxy provider support
Add WindscribeVPN as a new proxy provider option, following the same pattern as NordVPN and SurfsharkVPN implementations.

Fixes: #29
2025-10-17 20:21:47 +00:00
Andy 133f91a2e8 feat(cdm): add highly configurable CustomRemoteCDM for flexible API support
Add new CustomRemoteCDM class to support custom CDM API providers with maximum configurability through YAML configuration alone. This addresses GitHub issue #26 by enabling integration with third-party CDM APIs.
2025-10-17 00:28:43 +00:00
Andy a7bde29401 fix: only exclude forced subs when --forced-subs flag is not set
Previously, forced subtitles were incorrectly included when they matched
languages in the lang configuration, even without the --forced-subs flag.
This caused forced subs to appear when using language configs or the -l
parameter, which violated the expected behavior.
2025-10-15 22:39:44 +00:00
Andy 6c1cb21630 Merge branch 'feature/enhanced-debug-logging' into feature/add-rest-api 2025-10-13 23:50:01 +00:00
Andy 8437ba24d5 feat: Add comprehensive JSON debug logging system
Implements a complete structured logging system for troubleshooting and service development.

Features:
- Binary toggle via --debug flag or debug: true in config
- JSON Lines (.jsonl) format for easy parsing and analysis
- Comprehensive logging of all operations:
  * Session info (version, platform, Python version)
  * CLI parameters and service configuration
  * CDM details (Widevine/PlayReady, security levels)
  * Authentication status
  * Title and track metadata
  * DRM operations (PSSH, KIDs, license requests)
  * Vault queries with key retrieval
  * Full error traces with context

- Configurable key logging via debug_keys option
- Smart redaction (passwords, tokens, cookies always redacted)
- Error logging for all critical operations:
  * Authentication failures
  * Title fetching errors
  * Track retrieval errors
  * License request failures (Widevine & PlayReady)
  * Vault operation errors

- Removed old text logging system
2025-10-13 23:49:01 +00:00
Andy 45902bba13 fix: use keyword arguments for Attachment constructor in font attachment
Fixes #24

When attaching fonts for ASS/SSA subtitles, the Attachment class was being
called with positional arguments instead of keyword arguments. This caused
the font Path object to be incorrectly interpreted, leading to an error:
"Invalid URL 'Arial (arial)': No scheme supplied."

Changed Attachment(font, name) to Attachment(path=font, name=name) to
explicitly pass arguments by keyword, ensuring proper parameter handling.
2025-10-13 16:43:31 +00:00
Andy 062e060fca Merge branch 'main' into feature/add-rest-api 2025-10-08 21:42:34 +00:00
Andy 170a427af0 chore: bump version to 1.4.8 2025-10-08 21:30:01 +00:00
Andy 283736c57b revert: remove tinycss SyntaxWarning suppression and fix isort
Revert the warnings filter added in 2d5e807 as it didn't work as expected to suppress the tinycss SyntaxWarning. Also fix isort order in prd.py for pyplayready imports.
2025-10-08 21:26:26 +00:00
Andy 3f6a7e1f68 feat: add --exact-lang flag for precise language matching
New --exact-lang CLI flag that enables exact language code matching instead of fuzzy matching. This allows users to get specific regional variants without matching all related variants.

Examples:
- `-l es-419` normally matches all Spanish (es-ES, es-419, es-MX)
- `-l es-419 --exact-lang` matches ONLY es-419 (Latin American Spanish)

Fixes language detection issue where specific variants like es-419 (Latin American Spanish) would match all Spanish variants instead of just close regional variants.
2025-10-08 01:54:30 +00:00
TPD94 b48eecacb5 Merge branch 'unshackle-dl:main' into main 2025-10-05 22:03:31 -04:00
Posi+ive e9ba78cec3 Specify 'utf-8' encoding when opening temp file. (#22) 2025-10-05 16:42:21 -06:00
Andy 4cec942613 feat(vaults): add DecryptLabs API support to HTTP vault 2025-10-04 21:58:30 +00:00
Andy d6f8e42f52 feat: add --no-mux flag to skip muxing tracks into container files
Add --no-mux command-line option to allow downloading individual track
files without muxing them into a container file (.mkv/.mka/.mks).
This addresses use cases where users want to download tracks separately,
such as:
- Downloading only subtitles as individual .srt/.vtt files
- Keeping audio/video/subtitle tracks as separate files
- Converting subtitle formats without creating container files
When --no-mux is used:
- Tracks are saved as individual files with descriptive suffixes
- Video tracks: filename.{codec}.ext
- Audio tracks: filename.{language}.{codec}.ext
- Subtitle tracks: filename.{language}.forced.sdh.ext (as applicable)
- Folder structure respects --no-folder flag

Resolves #21
2025-10-01 18:11:40 +00:00
Andy e4bb7e9135 Merge branch 'main' into feature/add-rest-api 2025-10-01 04:53:44 +00:00
Andy c1e2d68e57 docs: add pysubs2 conversion_method to configuration documentation 2025-10-01 04:19:25 +00:00
Andy acbbe734ab Merge branch 'feature/pysubs2-subtitle conversion' into main 2025-10-01 04:16:06 +00:00
Andy 6d0a210efb fix: (subtitle): Move pysubs2 to not be auto while in "testing" phase. 2025-10-01 04:11:22 +00:00
Sp5rky 26a94b7135 feat: add pysubs2 subtitle conversion with extended format support
- Add pysubs2 as default conversion method for 'auto' mode
- Add support for MicroDVD (.sub), MPL2 (.mpl2), and TMP (.tmp) formats
- Implement convert_with_pysubs2() method with fallback to standard conversion
- Update from_mime() to handle new subtitle format types
- Map all supported formats to pysubs2 format identifiers
- Update docstrings to reflect new capabilities
2025-09-30 14:12:51 -06:00
Sp5rky 2d5e807e7d fix: suppress tinycss SyntaxWarning by initializing filter before imports
Move warnings.filterwarnings() to the very top of __main__.py before any
other imports to ensure it takes effect before tinycss is loaded during
module discovery. This fixes the SyntaxWarning from the unmaintained
tinycss package (a dependency of subby).

refs #20
2025-09-30 13:55:21 -06:00
Sp5rky ebe5012aeb fix: upgrade pyplayready to 0.6.3 and resolve import compatibility issues
- Upgrade pyplayready from >=0.6.0 to >=0.6.3
- Upgrade subby to 0.3.23 (commit 5a925c36) for lxml 6.x compatibility
- Upgrade lxml from 5.4.0 to 6.0.2
- Fix pyplayready exception imports to use root-level imports (compatible with both 0.6.0 and 0.6.3)
- Suppress SyntaxWarning from unmaintained tinycss dependency

The pyplayready package changed its API between versions:
- v0.6.0: exceptions in pyplayready.exceptions module
- v0.6.3: exceptions in pyplayready.misc.exceptions module
Using root-level imports (from pyplayready import ...) works with both versions.

ref #20
2025-09-30 13:50:17 -06:00
Sp5rky 06c4c02312 Merge branch 'main' of https://github.com/unshackle-dl/unshackle 2025-09-30 12:54:25 -06:00
Sp5rky c77a8737ef fix: update lxml constraint and pyplayready import path
- Update lxml dependency to allow version 6.x (required by subby 0.3.23)
- Fix pyplayready exception import path (moved to misc.exceptions in 0.6.3)

fixes #17
2025-09-30 12:54:13 -06:00
Sp5rky 0f4a68ca62 fix: update lxml constraint and pyplayready import path
- Update lxml dependency to allow version 6.x (required by subby 0.3.23)
- Fix pyplayready exception import path (moved to misc.exceptions in 0.6.3)

fixes #17
2025-09-30 12:53:27 -06:00
TPD94 3a8dfb26fe Merge branch 'unshackle-dl:main' into main 2025-09-30 02:18:58 -04:00
Andy e0154741f0 fix: dl.py
old leftover bug, sub-format was default to SRT
2025-09-30 05:03:22 +00:00
TPD94 c9bb0e4224 Merge branch 'unshackle-dl:main' into main 2025-09-30 00:15:09 -04:00
TPD94 e1e2e35ff4 Update binaries.py to check subdirs in binaries folders named after the binary 2025-09-30 00:14:44 -04:00
TPD94 ceea4ac827 Fixed RemoteCDM, added curl_cffi to instance checks. (#18)
* Fix remoteCDM, add curl_cffi to instance check

* Update dash.py
2025-09-29 20:57:42 -06:00
Andy 97f7eb0674 Changes for API/UI 2025-09-30 02:14:14 +00:00
TPD94 03f08159b4 Update dash.py 2025-09-29 21:01:55 -04:00
TPD94 724703d14b Update .gitignore 2025-09-29 20:56:25 -04:00
TPD94 da00258ae0 Merge branch 'unshackle-dl:main' into main 2025-09-29 20:55:05 -04:00
TPD94 4f3d0f1f7a Update .gitignore 2025-09-29 20:54:42 -04:00
TPD94 bade3f8c09 Update .gitignore 2025-09-29 20:53:38 -04:00
TPD94 55f116f1e8 Delete .idea directory 2025-09-29 20:53:16 -04:00
TPD94 2e2f8f5099 Fix remoteCDM, add curl_cffi to instance check 2025-09-29 20:48:59 -04:00
Andy 4459ec4db6 Merge branch 'main' into feature/add-rest-api 2025-09-29 17:05:46 +00:00
Sp5rky 2afc59624d feat: add REST API server with download management
Very early dev work, more changes will be active in this branch.

- Implement download queue management and worker system
- Add OpenAPI/Swagger documentation
- Include download progress tracking and status endpoints
- Add API authentication and error handling
- Update core components to support API integration
2025-09-28 21:49:00 -06:00
Andy e92e5c2ba3 feat: add AC4 codec support in Audio class and update mime/profile handling 2025-09-26 04:42:06 +00:00
Andy 30314fdb46 Fix missing movie/episode changes for last commit 2025-09-26 01:41:07 +00:00
Andy a2c6798fe6 fix: optimize audio track sorting by grouping descriptive tracks and sorting by bitrate, fixes bug that does not identify ATMOS or DD+ as the highest quality available in filenaming. 2025-09-25 23:21:35 +00:00
Andy bc26bf3046 feat: update changelog for version 1.4.7 2025-09-25 06:29:46 +00:00
Andy 35efdbff6d feat: add curl_cffi session support with browser impersonation
Add new session utility with curl_cffi support for anti-bot protection
Update all manifest parsers (DASH, HLS, ISM, M3U8) to accept curl_cffi sessions
Add browser impersonation support (Chrome, Firefox, Safari)
Fix cookie handling compatibility between requests and curl_cffi
Suppress HTTPS proxy warnings for better UX
Maintain full backward compatibility with requests.Session
2025-09-25 06:27:14 +00:00
Andy 63b7a49c1a feat: Add decrypt_labs_api_key to Config initialization and change duplicate track log level to debug 2025-09-25 06:22:50 +00:00
Andy 98ecf6f876 feat: Add download retry count option to download function 2025-09-23 01:32:00 +00:00
Andy 5df6914536 feat: Add options for required subtitles and best available quality in download command 2025-09-23 01:28:55 +00:00
Andy c1df074965 Change new dynamic CDM selection text to be in Debug only 2025-09-14 04:25:57 +00:00
Andy da60a396dd Fix: Prevent KeyError when reusing remote CDMs in dynamic selection
Creates a copy of the CDM dictionary before modification to prevent the original configuration from being mutated, allowing the same CDM to be selected multiple times within a session without errors.
2025-09-14 01:14:01 +00:00
Andy a99a391395 chore: bump version to 1.4.6 and update changelog 2025-09-13 04:01:45 +00:00
Andy ed32939d83 feat: Add quality-based CDM selection for dynamic CDM switching
Implements dynamic CDM selection based on video track resolution to optimize
CDM usage. Automatically selects appropriate security level (L3/SL2K for ≤1080p, L1/SL3K for >1080p) based on content requirements.

Key Features:
- Quality-based CDM configuration with threshold operators (>=, >, <=, <)
- Pre-selection based on highest quality across all video tracks
- Maintains backward compatibility with existing CDM configurations
- Single CDM per session to avoid inefficient switching
2025-09-13 03:59:13 +00:00
Andy 4006593a8a Fix: Implement lazy DRM loading for multi-track key retrieval
- Add deferred DRM loading to M3U8 parser to mark tracks for later processing
- Optimize prepare_drm to load DRM just-in-time during download process
2025-09-12 06:38:14 +00:00
Andy 307be4549b Fix vault caching count and NoneType iteration issues
- Fix 'NoneType' object is not iterable error in decrypt_labs_remote_cdm
- Fix vault count display showing 0/3 instead of actual successful vault count
2025-09-10 06:33:46 +00:00
Andy a82828768d feat: automatic audio language metadata for embedded audio tracks
- Add intelligent embedded audio language detection at mux stage
- Automatically set audio language metadata when no separate audio tracks exist
- Respect user flags (-V, --no-audio) to avoid unnecessary processing
- Smart video track selection based on title language with fallbacks
- Improved default track selection to prioritize title language matches
- Enhanced FFmpeg repackaging with audio stream metadata injection
- Works automatically for all services without service-specific code
2025-09-10 00:57:14 +00:00
Andy d18a5de0d0 fix: Improve import ordering and code formatting
- Reorder imports in decrypt_labs_remote_cdm.py for better organization
- Clean up trailing whitespace in SQLite.py
2025-09-10 00:53:52 +00:00
Andy 04b540b363 fix: Resolve service name transmission and vault case sensitivity issues
Fixed DecryptLabsRemoteCDM sending 'generic' instead of proper service names and added case-insensitive vault lookups for SQLite/MySQL vaults. Also added local vault integration to DecryptLabsRemoteCDM
2025-09-09 18:53:11 +00:00
Andy 6137146705 chore: bump version to 1.4.5 and update changelog
- Update version from 1.4.4 to 1.4.5 in core/__init__.py
- Add comprehensive changelog entry for v1.4.5 with all changes since 1.4.4
- Include enhanced CDM support, caching improvements, and bug fixes
2025-09-09 03:53:42 +00:00
Andy 859d09693c feat(cdm): Update User-Agent to use dynamic version
- Replace hardcoded version "1.0" with dynamic version import in DecryptLabsRemoteCDM User-Agent header.
2025-09-09 03:49:01 +00:00
Andy 5f022635cb feat(cdm): Optimize get_cached_keys_if_exists for L1/L2 devices
- Always send get_cached_keys_if_exists=True for L1/L2 devices to leverage
- the API's automatic caching optimization. This reduces unnecessary license
- requests by prioritizing cached keys for these security levels.
2025-09-06 22:10:35 +00:00
Andy ad66502c0c feat(cdm): Add fallback to Widevine common cert for L1 devices
- Use default Widevine common privacy certificate when no service certificate is provided for L1 devices
- Add get_widevine_service_certificate method to EXAMPLE service for config-based certificates
- Improve certificate handling with more descriptive return messages
2025-09-06 20:30:11 +00:00
Andy e462f07b7a Merge branch 'main' of https://github.com/unshackle-dl/unshackle 2025-09-06 19:39:39 +00:00
Andy 83b600e999 fix(cdm): Clean up session data when retrieving cached keys
Remove decrypt_labs_session_id and challenge from session when cached keys exist but there are missing kids, ensuring clean state for subsequent requests.
2025-09-06 19:38:54 +00:00
Andy ea8a7b00c9 fix(cdm): Clean up session data when retrieving cached keys
Remove decrypt_labs_session_id and challenge from session when cached keys exist but there are missing kids, ensuring clean state for subsequent requests.
2025-09-06 18:52:20 +00:00
Andy 16ee4175a4 feat(dl): ✨ Truncate PSSH string for display in non-debug mode
* Added `_truncate_pssh_for_display` method to limit the width of PSSH strings shown in the console.
* Ensures better readability of DRM information by truncating long strings.
2025-09-05 02:15:10 +00:00
Andy f722ec69b6 fix(tags): 🐛 Fix formatting issues 2025-09-03 14:51:22 +00:00
Andy 2330297ea4 feat(kv): ✨ Enhance vault loading and key copying logic
* Implemented `_load_vaults` function to load and validate vaults by name.
* Improved `_copy_service_data` to handle key copying with better logging and error handling.
* Updated `copy` command to utilize the new vault loading function and streamline the process.
* Enhanced key insertion logic in MySQL and SQLite vaults to avoid inserting existing keys.
2025-09-03 14:50:51 +00:00
Andy 86bb162868 feat(tags): Enhance tag handling for TV shows and movies from Simkl data
Fixes #15
2025-09-02 22:01:44 +00:00
Andy 501cfd68e8 fix(cdm): Add error message for missing service certificate in CDM session 2025-09-02 19:16:34 +00:00
Andy 76fb2eea95 feat: implement intelligent caching system for CDM license requests 2025-09-02 18:48:34 +00:00
Andy ea5ec40bcd Merge branch 'main' of https://github.com/unshackle-dl/unshackle 2025-09-02 17:34:12 +00:00
Andy 329850b043 feat(cdm): Enhance key retrieval logic and improve cached keys handling 2025-09-02 17:33:31 +00:00
Andy 73595f3b50 feat(cdm): Enhance key retrieval logic and improve cached keys handling 2025-09-02 17:23:02 +00:00
Andy 1e82283133 fix(tags): Fix import order. 2025-09-02 04:13:43 +00:00
Andy ab13dde9d2 feat(changelog): Update changelog for version 1.4.4 with enhanced CDM support, configuration options, and various improvements 2025-09-02 04:10:28 +00:00
Andy 9fd0895128 feat(cdm): Refactor DecryptLabsRemoteCDM full support for Widevine/Playready and ChromeCDM 2025-09-02 04:02:52 +00:00
Andy ed744205ad fix(tags): 🐛 Fix Matroska tag compliance with official specification
- Update IMDB tags to use ID only (tt123456) instead of URLs
  - Update TMDB tags to use prefix/id format (movie/123456, tv/123456)
  - Update TVDB tags to use numeric ID only
  - Add XML escaping for tag values
  - Fix XML declaration to use double quotes

Fixes #15
2025-09-01 21:02:08 +00:00
Andy 3ef43afeed feat(cdm): Add DecryptLabs CDM configurations for Chrome and PlayReady devices with updated User-Agent and service certificate 2025-09-01 00:34:07 +00:00
Andy 26851cbe7c feat(cdm): Enhance DecryptLabsRemoteCDM with improved session management and caching support and better support for remote WV/PR 2025-09-01 00:31:00 +00:00
Andy b4efdf3f2c feat(cdm): Enhance DecryptLabsRemoteCDM to support cached keys and improve license handling 2025-08-28 17:09:55 +00:00
Andy eb30620626 fix(main): As requested old devine version removed from banner to avoid any confusion the developer of this software. Original GNU is still applys. 2025-08-26 23:16:00 +00:00
Andy 7b71d6631c fix(main): As requested old devine version removed from banner to avoid any confusion the developer of this software. Original GNU is still applys. 2025-08-26 22:49:46 +00:00
Andy 5949931b56 feat(config): Add new configuration options for device certificate status list and language preferences 2025-08-20 05:28:58 +00:00
Andy ddfc0555c9 style(config): Clean up unshackle-example.yaml with correct accurate information. 2025-08-20 05:20:59 +00:00
Andy 3dda3290d3 feat(release): Bump version to 1.4.3 and update changelog with new features and improvements 2025-08-20 05:10:45 +00:00
Andy 19ff200617 refactor(drm): Simplify decrypt method by removing unused parameter and streamline logic 2025-08-20 05:10:38 +00:00
Andy ed0f03eca3 fix(dependencies): Remove unnecessary data extra requirement from langcodes 2025-08-16 03:34:56 +00:00
Andy 72f65adcb2 feat(ip-info): Fix few more issues with the get_ip_info make sure we failover to different provider on 429 errors and allow future for more API providers to be added later. 2025-08-16 00:28:05 +00:00
Andy 50a5a23341 feat(ip-info): Add cached IP info retrieval with fallback tester to avoid rate limiting 2025-08-15 22:40:07 +00:00
Andy e10c760821 feat(release): Bump version to 1.4.2 and update changelog with new features and fixes 2025-08-14 17:56:01 +00:00
Andy 990084ab1f feat(tags): Implement session management for API requests with retry logic 2025-08-14 02:14:46 +00:00
Andy 8e598f7d6a Merge branch 'main' of https://github.com/unshackle-dl/unshackle 2025-08-13 15:00:33 +00:00
Andy 06687b51fb feat(config): Add series_year option to control year inclusion in titles and YAML configuration 2025-08-13 15:00:30 +00:00
Sp5rky eb1be7e253 Update README.md 2025-08-12 20:51:29 -06:00
Andy eac2ff4cee feat(hls): Enhance segment retrieval by allowing all file types and clean up empty segment directories. Fixes issues with VTT files from HLS not being found correctly due to new HLS "changes" 2025-08-12 20:25:42 +00:00
Andy 798b5bf3cd feat(hls): Enhance segment merging with recursive file search and fallback to binary concatenation 2025-08-11 03:53:17 +00:00
Andy 725f7be563 fix(dl): Adjust per_language logic to ensure correct audio track selection and not download all tracks for selected language. 2025-08-09 17:39:36 +00:00
Andy b2686ca2b1 feat(vault): Add no_push option to Vault and its subclasses to control key reception 2025-08-08 23:38:52 +00:00
Andy abc3b4f1a4 feat(dl): Add audio language option to override language for audio tracks 2025-08-08 21:57:49 +00:00
Andy 9952758b38 feat(changelog): Update changelog with enhanced tagging configuration and improvements 2025-08-08 05:03:57 +00:00
Andy f56e7c1ec8 chore(release): Bump version to 1.4.1 and update changelog with title caching features 2025-08-08 04:57:32 +00:00
Andy 096b7d70f8 Merge remote-tracking branch 'origin/main' into feature/title-caching 2025-08-08 04:50:46 +00:00
Andy 460878777d refactor(tags): Simplify Simkl search logic and soft-fail when no results found 2025-08-07 17:56:36 +00:00
Andy 9eb6bdbe12 feat(tags): Enhance tag_file function to prioritize provided TMDB ID if --tmdb is used 2025-08-06 22:15:16 +00:00
Andy 41d203aaba feat(config): Add options for tagging with group name and IMDB/TMDB details and new API endpoint of simkl if no tmdb api key is added. 2025-08-06 21:34:14 +00:00
Andy 0c6909be4e feat(dl): Update language option default to 'orig' if no -l is set, avoids hardcoded en 2025-08-06 21:33:23 +00:00
Andy f0493292af feat: Implement title caching system to reduce API calls
- Add configurable title caching with fallback support
- Cache titles for 30 minutes by default, with 24-hour fallback on API failures
- Add --no-cache and --reset-cache CLI flags for cache control
- Implement region-aware caching to handle geo-restricted content
- Use SHA256 hashing for cache keys to handle complex title IDs
- Add cache configuration variables to config system
- Document new caching options in example config

This caching system significantly reduces redundant API calls when debugging
or modifying CLI parameters, improving both performance and reliability.
2025-08-06 17:08:58 +00:00
Andy ead05d08ac fix(subtitle): Handle ValueError in subtitle filtering for multiple colons in time references fixes issues with subtitles that contain multiple colons 2025-08-06 01:28:03 +00:00
Andy 8c1f51a431 refactor: Remove Dockerfile and .dockerignore from the repository 2025-08-05 23:56:07 +00:00
Sp5rky 1d4e8bf9ec Update CHANGELOG.md 2025-08-05 17:43:57 -06:00
Andy b4a1f2236e feat: Bump version to 1.4.0 and update changelog with new features and fixes 2025-08-05 23:37:45 +00:00
Andy 3277ab0d77 feat(playready): Enhance KID extraction from PSSH with base64 support and XML parsing 2025-08-05 23:28:30 +00:00
Andy be0f7299f8 style(dl): Standardize quotation marks for service attribute checks 2025-08-05 23:27:59 +00:00
Andy 948ef30de7 feat(dl): Add support for services that do not support subtitle downloads 2025-08-05 20:22:08 +00:00
Andy 1bd63ddc91 feat(titles): Better detection of DV across all codecs in Episode and Movie classes dvhe.05.06 was not being detected correctly. 2025-08-05 18:33:51 +00:00
Andy 4dff597af2 feat(dl): Fix track selection to support combining -V, -A, -S flags
Previously, using multiple track selection flags like `-S -A` would not work
as expected. The flags were treated as mutually exclusive, resulting in only
one type of track being downloaded.

This change refactors the track selection logic to properly handle combinations:

- Multiple "only" flags now work together (e.g., `-S -A` downloads both)
- Exclusion flags (`--no-*`) continue to work and override selections
- Default behavior (no flags) remains unchanged

Fixes #10
2025-08-05 15:48:17 +00:00
Andy 8dbdde697d feat(hybrid): Enhance extraction and conversion processes with dymanic spinning bars to follow the rest of the codebase. 2025-08-05 14:57:51 +00:00
Andy 63c697f082 feat(series): Enhance tree representation with season breakdown 2025-08-04 19:30:27 +00:00
Andy 3e0835d9fb feat(dl): Improve DRM track decryption handling 2025-08-04 19:30:27 +00:00
Andy c6c83ee43b feat(dl): Enhance language selection for video and audio tracks, including original language support 2025-08-04 19:30:27 +00:00
Andy 507690834b feat(tracks): Add support for HLG color transfer characteristics in video arguments 2025-08-04 19:28:11 +00:00
Andy f8a58d966b feat(subtitle): Add filtering for unwanted cues in WebVTT subtitles 2025-08-03 22:10:17 +00:00
Andy 8d12b735ff feat(dl): Add option to include forced subtitle tracks 2025-08-03 22:00:21 +00:00
Andy 1aaea23669 Revert "feat: Implement terminal cleanup on exit and signal handling in ComfyConsole"
This reverts commit 091d7335a3.
2025-08-03 15:26:30 +00:00
Andy e3571b9518 feat(update_checker): Enhance update checking logic and cache handling 2025-08-03 06:58:59 +00:00
Andy b478a00519 chore: Bump unshackle version to 1.3.0 in uv.lock 2025-08-03 06:45:40 +00:00
Andy 24fb8fb00c chore: Update changelog with new features, enhancements, and fixes for version 1.3.0 2025-08-03 06:30:53 +00:00
Andy 63e9a78b2a chore: Bump version to 1.3.0 and update changelog with mp4decrypt support and enhancements 2025-08-03 06:26:24 +00:00
Andy a2bfe47993 feat(drm): ✨ Add support for mp4decrypt as a decryption method
* Introduced a new configuration option for DRM decryption in `unshackle.yaml`.
* Updated the `decrypt` methods in `PlayReady` and `Widevine` classes to allow using `mp4decrypt`.
* Enhanced the `Config` class to manage decryption methods per service.
* Added `mp4decrypt` binary detection in the binaries module.
2025-08-03 06:23:43 +00:00
Andy cf4dc1ce76 feat: Add unshackle-example.yaml to replace the unshackle.yaml file, you can now make changes to the unshackle.yaml file and pull from the the repo without issues. 2025-08-03 00:54:29 +00:00
Andy 40028c81d7 Merge branch 'feature/scene-naming-option' 2025-08-03 00:48:22 +00:00
Andy 06df10cb58 fix: rename 'servers' to 'server_map' for proxy configuration in unshackle.yaml to resolve nord/surfshark incorrect named config 2025-08-01 20:23:03 +00:00
Andy d61bec4a8c feat: Add scene naming option to configuration and update naming logic in titles 2025-08-01 18:40:40 +00:00
Andy 058bb60502 feat: update path of update_check.json to .gitignore 2025-08-01 17:44:11 +00:00
Andy 7583129e8f feat: Enhance credential management and CDM configuration in unshackle.yaml 2025-08-01 17:41:19 +00:00
Andy 4691694d2e feat: Add Unspecified_Image option to Transfer enum in Video class.
The Transfer enum was missing value 2, which according to ITU-T H.Sup19 standards represents "Unspecified (Image
  characteristics are unknown or are determined by the application)". This value is often used for still image coding systems.
2025-08-01 17:10:55 +00:00
Andy a07345a0a2 refactor: Replace log.exit calls with ValueError exceptions for error handling in Hybrid class 2025-07-31 23:48:22 +00:00
Andy 091d7335a3 feat: Implement terminal cleanup on exit and signal handling in ComfyConsole 2025-07-31 18:25:18 +00:00
Andy 8c798b95c4 fix: Correct URL handling and improve key retrieval logic in HTTP vault 2025-07-31 15:45:12 +00:00
Andy 46c28fe943 feat: Add update check interval configuration and implement rate limiting for update checks 2025-07-30 23:36:59 +00:00
Andy 22c9aa195e feat: Bump version to 1.2.0 and update changelog, I'll eventually learn symantic versioning. 2025-07-30 23:15:20 +00:00
Andy 776d8f3df0 feat: Update version to 1.1.1 and add update checking functionality 2025-07-30 23:12:13 +00:00
Andy 67caf71295 Merge branch 'hdr10ptest' 2025-07-30 22:49:01 +00:00
Andy 3ed76d199c chore(workflow): 🗑️ Remove Docker build and publish workflow, its too messy at the moment doing manual builds for now. 2025-07-30 22:48:00 +00:00
Andy 4de9251f95 feat(tracks): ✨ Add duration fix handling for video and hybrid tracks 2025-07-30 21:39:34 +00:00
Andy d2fb409ad9 feat(hybrid): ✨ Add HDR10+ support for conversion to Dolby Vision and enhance metadata extraction 2025-07-30 21:14:50 +00:00
Andy fdff3a1c56 refactor(env): ✨ Enhance dependency check with detailed categorization and status summary 2025-07-30 20:12:43 +00:00
Andy 5d1f2eb458 feat(attachment): ✨ Ensure temporary directory is created for downloads 2025-07-30 18:52:36 +00:00
Andy 3efac3d474 feat(vaults): ✨ Enhance vault loading with success status 2025-07-30 17:29:06 +00:00
Andy f578904b76 feat(subtitle): ✨ Add information into unshackle.yaml on how to use new Subby subtitle conversion. 2025-07-30 02:18:35 +00:00
Andy 9f20159605 feat(hybrid): ✨ Display resolution of HDR10 track in hybrid mode console output and clean up unused code 2025-07-30 02:08:07 +00:00
Andy 4decb0d107 feat(dl): ✨ Enhance hybrid processing to handle HDR10 and DV tracks separately by resolution, Hotfix for -q 2160,1080 both tracks will have Hybrid correctly now. 2025-07-30 01:09:59 +00:00
Sp5rky 80c40c8677 Merge pull request #1 from unshackle-dl/Hybrid-HDR
Hybrid HDR
2025-07-29 20:40:24 -04:00
Andy 26ef48c889 fix(download): 🐛 Skip Content-Length validation for compressed responses in curl_impersonate and requests 2025-07-30 00:32:25 +00:00
Andy 5dad2746b1 feat(subtitles): ✨ Integrate subby library for enhanced subtitle processing and conversion methods 2025-07-30 00:24:55 +00:00
Andy 24aa4647ed chore: ✨ Add CHANGELOG.md to document notable changes and version history 2025-07-29 20:32:35 +00:00
Andy eeb553cb22 chore: 🔖 Bump version to 1.1.0 in pyproject.toml, __init__.py, and uv.lock to follow correct Semantic Versioning. 2025-07-29 19:48:34 +00:00
Andy 06c96b88a5 fix(download): 🐛 Skip Content-Length validation for compressed responses in curl_impersonate and requests. The fix ensures that when Content-Encoding indicates compression, we skip the validation by setting content_length = 0, allowing the downloads to complete successfully. 2025-07-29 19:13:50 +00:00
Andy e8e376ad51 fix(hybrid): 🐛 Fix import order and add missing json import
fix(uv): 🐛 Update unshackle package version to 1.0.2
2025-07-29 19:11:11 +00:00
Andy fbb140ec90 feat(EXAMPLE): ✨ Add support for HDR10 and DV tracks in hybrid mode 2025-07-29 17:57:01 +00:00
Andy 16a684c77f fix(dl): 🐛 Check for dovi_tool availability in hybrid mode 2025-07-29 17:47:27 +00:00
Andy c97de0c32b feat(hybrid): ✨ Implement HDR10+DV hybrid processing and injection support
Original code by @P0llUx12 - Discord
2025-07-29 17:40:02 +00:00
Andy c81b7f192e fix(install): 🐛 Improve UV installation process and error handling
* Enhanced the installation script for `uv` by:
  * Adding checks for existing installations.
  * Improving error messages for PowerShell script execution.
  * Ensuring `uv` is correctly added to the PATH for the current session.
* Updated the installation confirmation messages for clarity.
2025-07-25 22:40:46 +00:00
Andy 1b9fbe3401 fix(env): 🐛 Improve handling of directory paths in info command
* Enhanced the `info` command to support both single `Path` objects and lists of `Path` objects.
* For lists, each path is now displayed on a separate line, improving readability.
* Maintained original logic for single `Path` objects to ensure consistent behavior.
2025-07-25 18:46:55 +00:00
Andy f69eb691d7 feat(binaries): ✨ Add support for MKVToolNix and mkvpropedit
* Introduced `MKVToolNix` and `mkvpropedit` binaries to the project.
* Updated the environment check to include required status for dependencies.
* Enhanced the `Tracks` class to raise an error if `MKVToolNix` is not found.
* Modified the `_apply_tags` function to utilize the `mkvpropedit` binary from the binaries module.
2025-07-25 18:27:14 +00:00
Andy 05ef841282 fix(env): 🐛 Update Shaka-Packager binary retrieval method
* Changed the binary retrieval for `Shaka-Packager` to use `find_binary` for improved accuracy.
* This ensures the correct binary is located and used in the environment checks.
2025-07-25 18:18:00 +00:00
Andy 454f19a0f7 fix(env): 🐛 Update binary search functionality to use binaries.find
* Refactored the `find_binary` function to utilize `binaries.find` for improved binary detection.
* Updated dependency path retrieval to ensure accurate results.
2025-07-25 18:09:06 +00:00
Andy 4276267455 feat(proxies): ✨ Add SurfsharkVPN support
Original code by @p0llux12 - Discord

- Introduced `SurfsharkVPN` class for proxy service integration.
- Updated configuration to include `surfsharkvpn` in proxy providers.
- Removed legacy `nordvpn` configuration from YAML.
- Enhanced `dl.py` and `search.py` to utilize `SurfsharkVPN`.
2025-07-25 09:03:08 +00:00
Andy ab40dc1bf0 Merge branch 'main' of https://github.com/unshackle-dl/unshackle 2025-07-25 08:32:27 +00:00
Andy ec16e54c10 fix(binaries): 🐛 Improve local binary search functionality
* Added logic to check for executables in a local `binaries` directory.
* Enhanced Windows support by checking for `.exe` extensions.
* Removed unnecessary `binaries/` entry from `.gitignore`.
2025-07-25 08:32:26 +00:00
Sp5rky 20285f4522 Update issue templates 2025-07-20 20:59:48 -06:00
Andy eaa5943b8e Include yaml updates showing how to use new multiple service folders 2025-07-20 16:51:38 +00:00
Andy 4385035b05 fix(cfg): 🐛 Update services directory handling
* Updated the `services` directory assignment to ensure it is always treated as a list, improving consistency in configuration handling. Allows to provide multiple different service folders.
2025-07-20 16:49:44 +00:00
Andy cb26ac6fa2 feat: ✨ Update version display in main.py
* Changed the version display in `__main__.py` to include copyright information.
2025-07-20 15:45:50 +00:00
Andy 95674d5739 Update readme with better instructions for docker usage with correct downloads path 2025-07-20 05:38:46 +00:00
Andy 80eb31387f fix: update shaka packager version and enhance Docker run command with additional volume mounts 2025-07-20 04:00:57 +00:00
Andy 75cd556737 feat: update GitHub Actions workflow for Docker image build and add Docker installation instructions to README 2025-07-20 03:17:24 +00:00
Andy c30b9112a1 Fixes github actions not finding the uv.lock file 2025-07-20 02:50:50 +00:00
Andy 52ce0ed7f7 feat: add Dockerfile and GitHub Actions workflow for building and publishing Docker image 2025-07-20 02:47:08 +00:00
Andy 5a15e240d5 Remove branding and year from header. 2025-07-19 14:55:59 +00:00
Andy 4f4542c41c fixes: ✨ add SubtitleCodecChoice for resolving issues with config clicktype selection, using names like VTT or SRT was not working as expected
* Introduced `SubtitleCodecChoice` to allow selection of subtitle codecs with support for enum names, values, and common aliases.
* Updated `--sub-format` option in `dl.py` to utilize the new `SubtitleCodecChoice`.
2025-07-19 05:44:33 +00:00
Andy fa0224b19f fix: add video_only condition to subtitle track selection logic fixes issues where ccextractor would run even with -V enabled 2025-07-18 23:21:13 +00:00
Andy 49a0eb7cf3 feat: implement VideoCodecChoice for enhanced codec selection 2025-07-18 23:20:46 +00:00
Andy ed03b1a4fd refactor: improve track selection logic in dl.py 2025-07-18 23:19:01 +00:00
Andy fd2d9829a1 feat: add .github/ to .gitignore to exclude GitHub-related files 2025-07-18 21:53:57 +00:00
Andy 703ed6f3b2 refactor: reorganize Planned Features section in README for clarity 2025-07-18 18:54:55 +00:00
Andy 6037b640ac feat: update .gitignore and enhance README with planned features 2025-07-18 18:30:55 +00:00
Andy 1a49d288f8 fix: change default value of set_terminal_bg to False 2025-07-18 01:40:55 +00:00
Andy e98c9ef66a feat: enhance CONFIG.md with new configuration options for curl_impersonate, filenames, n_m3u8dl_re, and nordvpn 2025-07-18 01:39:47 +00:00
Sp5rky 41965cba28 Update README.md 2025-07-17 19:28:33 -06:00
Andy 5b8cd847dc Add fancy icon. 2025-07-18 01:19:54 +00:00
Andy d856cb81d8 refactor: streamline README by removing outdated service and legal information and moved it directly to the WIKI 2025-07-18 01:11:44 +00:00
Andy d37014f53f Initial Commit 2025-07-18 00:46:05 +00:00
266 changed files with 49126 additions and 125739 deletions
+15
View File
@@ -0,0 +1,15 @@
root = true
[*]
end_of_line = lf
charset = utf-8
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.{feature,json,md,yaml,yml,toml}]
indent_size = 2
[*.md]
trim_trailing_whitespace = false
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
+65
View File
@@ -0,0 +1,65 @@
---
name: Bug report
about: Create a report to help us improve
title: ""
labels: ""
assignees: Sp5rky
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Run command `uv run unshackle [...]`
2. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**System Details**
- OS: [e.g. Windows 11, Ubuntu 22.04, macOS 14]
- unshackle Version: [e.g. 1.0.1]
**Dependency Versions** (if relevant)
- Shaka-packager: [e.g. 2.6.1]
- n_m3u8dl-re: [e.g. 0.3.0-beta]
- aria2c: [e.g. 1.36.0]
- ffmpeg: [e.g. 6.0]
- Other: [e.g. ccextractor, subby]
**Logs/Error Output**
<details>
<summary>Click to expand logs</summary>
```
Paste relevant error messages or stack traces here
```
</details>
**Configuration** (if relevant)
Please describe relevant configuration settings (DO NOT paste credentials or API keys):
- Downloader used: [e.g. requests, aria2c, n_m3u8dl_re]
- Proxy provider: [e.g. NordVPN, none]
- Other relevant config options
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
---
**⚠️ Important:**
- **DO NOT include service-specific implementation code** unless you have explicit rights to share it
- **DO NOT share credentials, API keys, WVD files, or authentication tokens**
- For service-specific issues, describe the behavior without revealing proprietary implementation details
- Focus on core framework issues (downloads, DRM, track handling, CLI, configuration, etc.)
+53
View File
@@ -0,0 +1,53 @@
---
name: Feature request
about: Suggest an idea for this project
title: ""
labels: ""
assignees: Sp5rky
---
**Feature Category**
What area does this feature request relate to?
- [ ] Core framework (downloaders, DRM, track handling)
- [ ] CLI/commands (new commands or command improvements)
- [ ] Configuration system
- [ ] Manifest parsing (DASH, HLS, ISM)
- [ ] Output/muxing (naming, metadata, tagging)
- [ ] Proxy system
- [ ] Key vault system
- [ ] Documentation
- [ ] Other (please specify)
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is.
Example: "I'm always frustrated when [...]"
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Reference implementations** (if applicable)
Have you seen this feature in other tools?
- [ ] Vinetrimmer
- [ ] yt-dlp
- [ ] Other: [please specify]
Please describe how it works there (without sharing proprietary code).
**Use case / Impact**
- How would this feature benefit users?
- How often would you use this feature?
- Does this solve a common workflow issue?
**Additional context**
Add any other context or screenshots about the feature request here.
---
**⚠️ Note:**
This project focuses on the core framework and tooling. Service-specific feature requests should focus on what the framework should support, not specific service implementations.
+91
View File
@@ -0,0 +1,91 @@
name: Release
on:
workflow_dispatch:
push:
branches: [main]
paths:
- "pyproject.toml"
permissions:
contents: write
jobs:
check-version:
runs-on: ubuntu-latest
outputs:
should_release: ${{ steps.version_check.outputs.should_release }}
new_version: ${{ steps.version_check.outputs.new_version }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Check for major/minor version bump
id: version_check
run: |
NEW_VERSION=$(grep -m1 '^version' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
echo "Detected version in pyproject.toml: $NEW_VERSION"
LATEST_TAG=$(git tag --list | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)
echo "Latest git tag: $LATEST_TAG"
if [ -z "$LATEST_TAG" ]; then
echo "No previous tag found, treating as new release"
echo "should_release=true" >> "$GITHUB_OUTPUT"
echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
exit 0
fi
OLD_MAJOR=$(echo "$LATEST_TAG" | cut -d. -f1)
OLD_MINOR=$(echo "$LATEST_TAG" | cut -d. -f2)
NEW_MAJOR=$(echo "$NEW_VERSION" | cut -d. -f1)
NEW_MINOR=$(echo "$NEW_VERSION" | cut -d. -f2)
if [ "$NEW_MAJOR" -gt "$OLD_MAJOR" ] || [ "$NEW_MINOR" -gt "$OLD_MINOR" ]; then
echo "Major or minor version bump detected: $LATEST_TAG -> $NEW_VERSION"
echo "should_release=true" >> "$GITHUB_OUTPUT"
else
echo "Patch-only change ($LATEST_TAG -> $NEW_VERSION), skipping release"
echo "should_release=false" >> "$GITHUB_OUTPUT"
fi
echo "new_version=$NEW_VERSION" >> "$GITHUB_OUTPUT"
release:
needs: check-version
if: needs.check-version.outputs.should_release == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Set up Python
run: uv python install 3.12
- name: Install dependencies and build
run: |
uv sync
uv build
- name: Extract changelog for release
id: changelog
run: |
VERSION=${{ needs.check-version.outputs.new_version }}
# Extract the section for this version from CHANGELOG.md
awk "/^## \[$VERSION\]/{found=1; next} /^## \[/{if(found) exit} found{print}" CHANGELOG.md > release_notes.md
echo "Extracted release notes:"
cat release_notes.md
- name: Create GitHub Release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
VERSION=${{ needs.check-version.outputs.new_version }}
gh release create "$VERSION" \
--title "$VERSION" \
--notes-file release_notes.md \
dist/unshackle-${VERSION}-py3-none-any.whl \
dist/unshackle-${VERSION}.tar.gz
+254
View File
@@ -0,0 +1,254 @@
# unshackle
unshackle.yaml
unshackle.yml
update_check.json
*.mkv
*.mp4
*.exe
*.dll
*.crt
*.wvd
*.prd
*.der
*.pem
*.bin
*.db
*.db-shm
*.db-wal
*.db-journal
*.ttf
*.otf
device_cert
device_client_id_blob
device_private_key
device_vmp_blob
unshackle/binaries/*
!unshackle/binaries/placehere.txt
unshackle/cache/
unshackle/cookies/
unshackle/certs/
unshackle/WVDs/
unshackle/PRDs/
temp/
logs/
services/
/.[^/]*/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
poetry.lock
poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
.vscode/
.github/copilot-instructions.md
CLAUDE.md
AGENTS.md
graphify-out/
# CodeQL local analysis
.codeql-db/
.codeql-results/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
# Test fixtures with private/local content (services, IDs, credentials)
tests/remote/e2e/fixtures/fixtures.yaml
+33
View File
@@ -0,0 +1,33 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v4.4.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.15.12
hooks:
- id: ruff
args: [--fix]
- repo: https://github.com/pycqa/isort
rev: 9.0.0a3
hooks:
- id: isort
- repo: https://github.com/PyCQA/bandit
rev: 1.9.4
hooks:
- id: bandit
args:
- -c=pyproject.toml
- --severity-level=medium
- --confidence-level=medium
additional_dependencies: ['bandit[toml]']
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
args: [--markdown-linebreak-ext=md]
-1928
View File
File diff suppressed because it is too large Load Diff
+706
View File
@@ -0,0 +1,706 @@
# Changelog
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
This changelog is automatically generated using [git-cliff](https://git-cliff.org).
## [5.1.0] - 2026-05-29
### Features
- *ip-info*: Consolidate IP lookup, add ipinfo.io token support
- *dl*: Cache content keys in-memory to skip duplicate license requests
- *mux*: Add muxing.default_language to override default track per type
- *hls*: Detect DV-composite tracks and restore signaling post-mux
- *video*: Normalize SPS VUI to match manifest-derived range
- *api*: Sync /api/download with dl CLI flags and add serve.* defaults
- *vaults*: Tolerate vault failures during key get/add
- *tracks*: Configurable audio codec priority for tie-breaking
- *dl*: Live countdown for --slow delay
- *dl*: Gate s_lang/a_lang miss behind --best-available
- *dl*: Add real bitrate probing (-rvb/-rab)
- *import*: Reconstruct downloads from an --export sidecar
- *naming*: Per-service title_map remapping
- *import*: Include cover-art attachments in --export/--import
### Bug Fixes
- *manifests*: Clean stale .!dev resume markers before merge
- *sanitize*: Preserve parentheses, strip unidecode bracket artifacts
- *tracks*: Honor --worst in hybrid range selection
- *dl*: Re-pick DV/HDR10 when HYBRID falls back under best_available
- *proxies/nordvpn*: Use *.proxy.nordvpn.com for HTTPS proxy
- *template_formatter*: Preserve dash separator around empty conditional
- *dl*: Prefer Atmos in -l best/all language selection
- *title*: Detect Atmos across all audio tracks for filename template
- *title*: Use original-language audio for filename metadata
- *service*: Render request_input prompt via rich console
- *titles*: Normalize odd resolutions in filename quality token
- *api*: Propagate Click default=None through service instantiation
- *api*: Load real CDM for service init in list/session/download flows
- *vaults*: Enable WAL on SQLite vault to fix concurrent locks
- *hybrid*: Correct static L6 source and reset stale L5 active area
- *cli*: Report broken command/service loads once and cleanly
- *hls*: Resolve per-rendition KID in no-EXT-X-KEY fallback
- *dash*: Inherit SegmentTemplate attributes across AdaptationSet/Representation
- *dl*: Mux all requested ranges and select highest DV alongside hybrid
- *session*: Proxy auth on http targets and string params support
### Documentation
- Update docs to match current codebase
- *output*: Document muxing.default_language override
- Simplify README with demo and requirements
### Changes
- *routes, subtitle, track*: Improve code readability by formatting list structures
- *hybrid*: Extract dovi_tool and run_step helpers
- *ip_info*: Simplify lookup and trim cache
- *example*: Showcase full unshackle feature surface
- *dl*: Declare hybrid_base_only flag and extract standalone mux helper
- Remove dead aria2c and n_m3u8dl_re downloader code
### Maintenance
- *pre-commit*: Update hook versions in .pre-commit-config.yaml
## [5.0.0] - 2026-05-04
### Features
- *dl*: Add --vbitrate-range and --abitrate-range options for bitrate range selection
- *remote*: Server vault lookups, service CDM mapping, key display, and service param forwarding
- *remote*: Interactive auth handshake, server CDM, cache round-trip, and serve remote-only mode
- *template*: Add configurable folder naming via output_template.folder
- *hls*: Probe TS segments for resolution and codec when master playlist lacks RESOLUTION/CODECS tags
- *downloader*: Consolidate into unified requests-based downloader
- *dash*: Refactor segment extraction and add content period validation
- *downloader*: Optimize download throughput with Queue-based threading and raw reads
- *session*: Replace curl_cffi with rnet for TLS-fingerprinted HTTP
- *session*: Translate requests 'data' kwarg to rnet equivalents for compatibility
- *dl*: Allow --slow to accept custom delay range
- *serve*: Add service allowlist for global and per-user access control
- *dl*: Change --export flag with manifest URL, subtitles, and track info
- *dl*: Add download resume support via HTTP Range headers
- *track*: Add optional per-track session parameter
- *session*: Optimize header handling in session requests
- *kv*: Add search subcommand to look up KID across vaults
- *kv*: Add --local-only flag to copy/sync
- *config*: Per-title-type folder templates
- *dl*: Add --no-proxy-download flag
- *docs*: Add AGENTS.md to .gitignore
### Bug Fixes
- Update actions/checkout to v5 in release workflow
- *audio*: Support 'xheaac' profile
- *drm*: Include external KID in PSSH when it differs from existing KIDs
- *deps*: Bump PyJWT minimum to 2.12.0 for CVE-2026-32597
- *sanitize*: Restore parentheses stripping in filename sanitization
- *serve*: Allow remote-only mode without output_template and fix CORS/auth for Cloudflare
- *remote*: Forward track selection params to server and improve error display
- *subtitle*: Decompress gzip/zlib responses for subtitle downloads
- *drm*: Add zero-KID fallback for mp4decrypt and clear HLS track.drm after download
- *drm*: Add track ID fallback for mp4decrypt CBCS zero-KID content
- *hybrid*: Read actual HDR metadata for HDR10+ to DV conversion
- *template*: Detect folder spacer from template separators, not raw string
- *session*: Native rnet proxy support and cookie compat layer
- *deps*: Bump aiohttp and pygments to resolve 11 security vulnerabilities
- *api*: Sanitize user-provided values in log statements to prevent log injection
- *dl*: Preserve per-language video selection in quality step
- *dash*: Add SIDX parsing for SegmentBase manifests and deduplicate multi-period segments
- *gluetun*: Support WireGuard VPN ready detection
- *api*: Sync REST API download endpoint with updated dl command
- *drm*: Handle non-UTF-8 output from shaka-packager stderr
- *tags*: Prevent metadata lookup failures from skipping group tag
- *dl*: Always report full error trace for download worker failures
- *dash*: Deduplicate multi-period SegmentBase segments
- *dash*: Expand CICP enums to full H.273 range
- *drm*: Pass per-segment PSSH to PlayReady license callback
- *drm*: Pass per-segment PSSH to Widevine license callback
- *hls*: Carry DRM keys forward across EXT-X-KEY rotation
### Documentation
- *api*: Update --export from string path to boolean flag
### Performance Improvements
- *downloader*: Optimize hot loop and threading efficiency
- *downloader*: Parallel byte-range fetch for single-URL tracks
### Maintenance
- *gitignore*: Ignore binary files in unshackle/binaries/
- Update version to 5.0.0
## [4.0.0] - 2026-03-17
### Features
- *hybrid*: Add L5 active area and dynamic L6 luminance metadata
- *debug*: Add JSONL debug logging to decryption, muxing, and all downloaders
- *debug*: Log binary tool versions at session start
- *dl*: Add --repack flag to insert REPACK tag in output filenames
- *core*: Add TrackRequest system for multi-codec/multi-range support
- *dl*: Add --output option to override download directory
- *tracks*: Add edition tags to output filenames
- *templates*: [**breaking**] Add customizable output filename templates
- *templates*: Add configurable language tagging rule engine
- Update unshackle version to 4.0.0
- *dl*: Add --animeapi and --enrich options for anime metadata and tagging
- *dl*: Add skip messages for --no-audio and --no-chapters flags
- *dl*: Extract closed captions from HLS manifests and improve CC extraction
- *dl*: Add --worst flag and SHIELD OkHttp fingerprint preset
- *dl*: Add --remote flag for downloading via remote unshackle server
- *remote*: Add server-CDM mode, manifest transfer, and region-aware proxy
- *remote*: Add zlib compression for API payloads and gzip transport
- *remote*: Fetch service list and CLI options from server for --remote help
- *session*: Add IP validation for session access and enhance session management
### Bug Fixes
- *n_m3u8dl_re*: Pass all content keys for DualKey DRM decryption
- *hybrid*: Skip bitrate filter for DV tracks in HYBRID mode
- *attachment*: Sanitize filenames with illegal Windows characters
- *hybrid*: Accept HDR10+ tracks as valid base layer for HYBRID mode
- *dl*: Allow selection of audio tracks for 'all' languages in addition to 'best'
- *dl*: Overwrite existing files on re-download and use atomic replace
- *dl*: Handle cross-device moves when temp and downloads differ
- *track*: Fallback to requests downloader from n_m3u8dl_re for unsupported track types
- *n_m3u8dl_re*: Disable segment count validation for duration-based DASH
- Correct formatting and add missing newlines in selector and EXAMPLE service
- *dependencies*: Update pyplayready version to 0.8.3 and adjust dependencies
- *drm*: Update PlayReady KID extraction for pyplayready 0.8.3 compatibility
- *api*: Resolve Sentinel serialization, missing params, and add search endpoint
- *dash*: Pass period_filter to n_m3u8dl_re via filtered MPD file
- *title*: Add HDR Vivid Format HDR Tag
- *ism*: Prevent duplicate track IDs for audio tracks with same lang/codec/bitrate
- *aria2c*: Correct progress bar tracking for HLS downloads
- *dl*: Filter CC subtitle languages with --s-lang and extract all manifest CCs
- *serve*: Use X-Secret-Key header for REST API auth to match pywidevine
- *drm*: Preserve original PSSH for content_id-based Widevine manifests
- *drm*: Add zero-KID fallback for mp4decrypt and clear HLS track.drm after download
### Documentation
- Update and correct configuration documentation
- *dl*: Add comprehensive list of available `dl` keys and their descriptions
- Update API and configuration documentation with example service tags
### Changes
- *hybrid*: Replace log.info with console status and add JSONL debug logging
- *dl*: Remove legacy multi-fetch loop for unmigrated services
- *example*: Migrate EXAMPLE service to track_request pattern
- *providers*: Extract metadata providers into modular system
- *remote*: Deduplicate CDM loading, proxy resolution, and license handling
### Maintenance
- *changelog*: Update changelog for upcoming release and reorganize sections
- *changelog*: Update changelog
- *changelog*: Tag v4.0.0 release
## [3.0.0] - 2026-02-15
### Features
- *titles*: Use track source attribute for service name in filenames
- *debug*: Add download output verification logging
- Gluetun VPN integration and remote service enhancements
- *gluetun*: Improve VPN connection display and Windscribe support
- *serve*: Add PlayReady CDM support alongside Widevine
- *cdm*: Add remote PlayReady CDM support via pyplayready RemoteCdm
- *env*: Add ML-Worker binary for DRM licensing
- *video*: Detect interlaced scan type from MPD manifests
- *drm*: Add MonaLisa DRM support to core infrastructure
- *audio*: Codec lists and split muxing
- *proxy*: Add specific server selection for WindscribeVPN
- *cdm*: Normalize CDM detection for local and remote implementations
- *HLS*: Improve audio codec handling with error handling for codec extraction
- *tracks*: Prioritize Atmos audio tracks over higher bitrate non-Atmos
### Bug Fixes
- *subs*: Update SubtitleEdit CLI syntax and respect conversion_method
- *n_m3u8dl_re*: Include language in DASH audio track selection
- *hls*: Prefer media playlist keys over session keys for accurate KID matching
- *deps*: Upgrade vulnerable dependencies for security alerts
- *serve*: Use correct pywidevine users config format
- *cdm*: Correct error key casing in Decrypt Labs API response parsing
- *api*: Validate Bearer prefix before extracting API key
- *serve*: Correct PlayReady RemoteCDM server validation
- *n_m3u8dl_re*: Remove duplicate --write-meta-json argument causing download failures
- *manifests*: Correct DRM type selection for remote PlayReady CDMs
- *proxies*: Fixes WindscribeVPN server authentication
- *subs*: Route pycaption-unsupported formats to pysubs2 in auto mode
- *proxy*: Remove regional restrictions from WindscribeVPN
- *proxy*: Collect servers from all locations in WindscribeVPN
- *downloader*: Correct progress bar tracking for segmented downloads
- *binaries*: Search subdirectories for binary files
- *dash*: Handle high startNumber in SegmentTimeline for DVR manifests
- *drm*: Hide Shaka Packager message for MonaLisa decryption
- *dash*: Add CENC namespace support for PSSH extraction
- *dash*: Preserve MPD DRM instead of overwriting from init segment
- *subtitles*: Preserve sidecar originals
- *mux*: Avoid audio codec suffix on split-audio outputs
- *dl*: Prevent attachment downloads during --skip-dl
- *progress*: Force track bar completion on terminal states
- *progress*: Bind per-track bars and force terminal completion
- *dl*: Keep descriptive and standard audio for requested langs
- *dl*: Always clean up hybrid temp hevc outputs
- *hls*: Finalize n_m3u8dl_re outputs
- *downloader*: Restore requests progress for single-url downloads
- *dl*: Invert audio codec suffixing when splitting
- *dl*: Support snake_case keys for RemoteCdm
- *aria2c*: Warn on config mismatch and wait for RPC ready
- *serve*: [**breaking**] Make PlayReady users config consistently a mapping
- *dl*: Preserve proxy_query selector (not resolved URI)
- *gluetun*: Stop leaking proxy/vpn secrets to process list
- *monalisa*: Avoid leaking secrets and add worker safety
- *dl*: Avoid selecting all variants when multiple audio codecs requested
- *hls*: Keep range offset numeric and align MonaLisa licensing
- *titles*: Remove trailing space from HDR dynamic range label
- *config*: Normalize playready_remote remote_cdm keys
- *titles*: Avoid None/double spaces in HDR tokens
- *naming*: Keep technical tokens with scene_naming off
- *api*: Log PSSH extraction failures
- *proxies*: Harden surfshark and windscribe selection
- *service*: Redact proxy credentials in logs
- *monalisa*: Harden wasm calls and license handling
- *hls*: Remove no-op encryption_data reassignment
- *serve*: Default PlayReady access to none
- *tracks*: Close temp session and improve path type error
- *main*: Update copyright year dynamically in version display
### Reverts
- *monalisa*: Pass key via argv again
### Documentation
- Add configuration documentation WIP
- *changelog*: Add 2.4.0 release notes
- *changelog*: Update cliff config and regenerate changelog
- *changelog*: Complete 2.4.0 notes
- *config*: Clarify sdh_method uses subtitle-filter
### Performance Improvements
- *aria2c*: Improve download performance with singleton manager
### Changes
- *remote_auth*: Remove unused requests.Session
- Remove remote-service code until feature is more complete
### Maintenance
- *api*: Remove remote services
### Chore
- *release*: [**breaking**] Bump version to 3.0.0
## [2.3.0] - 2026-01-18
### Features
- *config*: Add unicode_filenames option to preserve native characters
### Bug Fixes
- *drm*: Correct PSSH system ID comparison in PlayReady
- *dash*: Handle placeholder KIDs and improve DRM init from segments
- *dash*: Handle N_m3u8DL-RE merge and decryption
- *drm*: Include shaka-packager binary in error messages
- *subs*: Strip whitespace from ASS font names
- *subs*: Handle negative TTML values in multi-value attributes
- *drm*: Filter Widevine PSSH by system ID instead of sorting
- *subs*: Handle WebVTT cue identifiers and overlapping multi-line cues
## [2.2.0] - 2026-01-15
### Features
- *debug*: Add comprehensive debug logging for downloaders and muxing
- *drm*: Add CDM-aware PlayReady fallback detection
### Bug Fixes
- *util*: Improve test command error detection and add natural sorting
- *vaults*: Batch bulk key operations to avoid query limits
- *titles*: Detect HDR10 in hybrid DV filenames correctly
- *vaults*: Adaptive batch sizing for bulk key operations
## [2.1.0] - 2025-11-27
### Features
- *export*: Enhance track export with URL, descriptor, and hex-formatted keys
- *cdm*: Add per-track quality-based CDM selection during runtime DRM switching
- Merge upstream dev branch
### Bug Fixes
- *deps*: Pin pyplayready to <0.7 to avoid KID extraction bug
- *hls*: Convert range_offset to int to prevent TypeError
- *video*: Correct CICP enum values to match ITU-T H.273 specification
- *utilities*: Handle space-hyphen-space separators in sanitize_filename
- *utilities*: Make space-hyphen-space handling conditional on scene_naming
- *windscribevpn*: Add error handling for unsupported regions in get_proxy method
- Restrict WindscribeVPN to supported regions
- *dash*: Add AdaptationSet-level BaseURL resolution
- *dl*: Preserve attachments when rebuilding track list
## [2.0.0] - 2025-11-10
### Features
- Add REST API server with download management
- Add comprehensive JSON debug logging system
- *cdm*: Add highly configurable CustomRemoteCDM for flexible API support
- *proxies*: Add WindscribeVPN proxy provider support
- *dl*: Add --latest-episode option to download only the most recent episode
- Add service-specific configuration overrides
- Add retry handler to curl_cffi Session
- *dl*: Add --audio-description flag to download AD tracks
- *api*: Add url field to services endpoint response
- *api*: Complete API enhancements for v2.0.0
- *api*: Add default parameter handling and improved error responses
- *cache*: Add TMDB and Simkl metadata caching to title cache
- *session*: Add custom fingerprint and preset support
- *fonts*: Add Linux font support for ASS/SSA subtitles
- *subtitle*: Preserve original formatting when no conversion requested
- *dl*: Add --no-video flag to skip video track downloads
### Bug Fixes
- Use keyword arguments for Attachment constructor in font attachment
- Only exclude forced subs when --forced-subs flag is not set
- Update lxml constraint and pyplayready import path
- *tags*: Gracefully handle missing TMDB/Simkl API keys
- *config*: Support config in user config directory across platforms
- *dl*: Validate HYBRID mode requirements before download
- *drm*: Add explicit UTF-8 encoding to mp4decrypt subprocess calls
- *subtitle*: Resolve SDH stripping crash with VTT files
- *naming*: Improve HDR detection with comprehensive transfer checks and hybrid DV+HDR10 support
- *dash*: Correct segment count calculation for startNumber=0
- *session*: Update OkHttp fingerprint presets
- *session*: Remove padding extension from OkHttp JA3 fingerprints
- *dl*: Prevent vault loading when --cdm-only flag is set
- *cdm*: Resolve session key handling for partial cached keys
- *cdm*: Apply session key fix to custom_remote_cdm
- *n_m3u8dl_re*: Read lang attribute from DASH manifests correctly
- *subtitles*: Fix closure bug preventing SDH subtitle stripping
- Ensure subtitles use requests downloader instead of n_m3u8dl_re if Descriptor.URL
- Suppress verbose fontTools logging when scanning system fonts
- *tags*: Skip metadata lookup when API keys not configured
### Documentation
- Improve GitHub issue templates for better bug reports and feature requests
- Add dev branch and update README
- Update CHANGELOG for audio description feature
- *changelog*: Complete v2.0.0 release documentation
- *changelog*: Add --no-video flag and PR #38 credit
- *changelog*: Set release date for version 2.0.0
- *readme*: Remove dev branch warning for main merge
### Changes
- *session*: Modernize type annotations to PEP 604 syntax
- *binaries*: Remove unused mypy import
- Remove unnecessary underscore prefixes from function names
- *tags*: Remove environment variable fallbacks for API keys
### Maintenance
- *api*: Fix import ordering in download_manager and handlers
- Update CHANGELOG.md for version 2.0.0
## [1.4.8] - 2025-10-08
### Features
- Add AC4 codec support in Audio class and update mime/profile handling
- Add pysubs2 subtitle conversion with extended format support
- Add --no-mux flag to skip muxing tracks into container files
- *vaults*: Add DecryptLabs API support to HTTP vault
- Add --exact-lang flag for precise language matching
### Bug Fixes
- Optimize audio track sorting by grouping descriptive tracks and sorting by bitrate, fixes bug that does not identify ATMOS or DD+ as the highest quality available in filenaming.
- Update lxml constraint and pyplayready import path
- Dl.py
- Upgrade pyplayready to 0.6.3 and resolve import compatibility issues
- Suppress tinycss SyntaxWarning by initializing filter before imports
- (subtitle): Move pysubs2 to not be auto while in "testing" phase.
### Reverts
- Remove tinycss SyntaxWarning suppression and fix isort
### Documentation
- Add pysubs2 conversion_method to configuration documentation
### Maintenance
- Bump version to 1.4.8
## [1.4.7] - 2025-09-25
### Features
- Add options for required subtitles and best available quality in download command
- Add download retry count option to download function
- Add decrypt_labs_api_key to Config initialization and change duplicate track log level to debug
- Add curl_cffi session support with browser impersonation
- Update changelog for version 1.4.7
## [1.4.6] - 2025-09-13
### Features
- Automatic audio language metadata for embedded audio tracks
- Add quality-based CDM selection for dynamic CDM switching
### Bug Fixes
- Resolve service name transmission and vault case sensitivity issues
- Improve import ordering and code formatting
### Maintenance
- Bump version to 1.4.6 and update changelog
## [1.4.5] - 2025-09-09
### Features
- *changelog*: Update changelog for version 1.4.4 with enhanced CDM support, configuration options, and various improvements
- *cdm*: Enhance key retrieval logic and improve cached keys handling
- Implement intelligent caching system for CDM license requests
- *tags*: Enhance tag handling for TV shows and movies from Simkl data
- *kv*: Enhance vault loading and key copying logic
- *dl*: Truncate PSSH string for display in non-debug mode
- *cdm*: Add fallback to Widevine common cert for L1 devices
- *cdm*: Optimize get_cached_keys_if_exists for L1/L2 devices
- *cdm*: Update User-Agent to use dynamic version
### Bug Fixes
- *tags*: Fix import order.
- *cdm*: Add error message for missing service certificate in CDM session
- *tags*: Fix formatting issues
### Maintenance
- Bump version to 1.4.5 and update changelog
## [1.4.4] - 2025-09-02
### Features
- *ip-info*: Add cached IP info retrieval with fallback tester to avoid rate limiting
- *ip-info*: Fix few more issues with the get_ip_info make sure we failover to different provider on 429 errors and allow future for more API providers to be added later.
- *release*: Bump version to 1.4.3 and update changelog with new features and improvements
- *config*: Add new configuration options for device certificate status list and language preferences
- *cdm*: Enhance DecryptLabsRemoteCDM to support cached keys and improve license handling
- *cdm*: Enhance DecryptLabsRemoteCDM with improved session management and caching support and better support for remote WV/PR
- *cdm*: Add DecryptLabs CDM configurations for Chrome and PlayReady devices with updated User-Agent and service certificate
- *cdm*: Refactor DecryptLabsRemoteCDM full support for Widevine/Playready and ChromeCDM
### Bug Fixes
- *dependencies*: Remove unnecessary data extra requirement from langcodes
- *main*: As requested old devine version removed from banner to avoid any confusion the developer of this software. Original GNU is still applys.
- *tags*: Fix Matroska tag compliance with official specification
### Changes
- *drm*: Simplify decrypt method by removing unused parameter and streamline logic
## [1.4.2] - 2025-08-14
### Features
- *dl*: Add audio language option to override language for audio tracks
- *vault*: Add no_push option to Vault and its subclasses to control key reception
- *hls*: Enhance segment merging with recursive file search and fallback to binary concatenation
- *hls*: Enhance segment retrieval by allowing all file types and clean up empty segment directories. Fixes issues with VTT files from HLS not being found correctly due to new HLS "changes"
- *config*: Add series_year option to control year inclusion in titles and YAML configuration
- *tags*: Implement session management for API requests with retry logic
- *release*: Bump version to 1.4.2 and update changelog with new features and fixes
### Bug Fixes
- *dl*: Adjust per_language logic to ensure correct audio track selection and not download all tracks for selected language.
## [1.4.1] - 2025-08-08
### Features
- Implement title caching system to reduce API calls
- *dl*: Update language option default to 'orig' if no -l is set, avoids hardcoded en
- *config*: Add options for tagging with group name and IMDB/TMDB details and new API endpoint of simkl if no tmdb api key is added.
- *tags*: Enhance tag_file function to prioritize provided TMDB ID if --tmdb is used
- *changelog*: Update changelog with enhanced tagging configuration and improvements
### Bug Fixes
- *subtitle*: Handle ValueError in subtitle filtering for multiple colons in time references fixes issues with subtitles that contain multiple colons
### Changes
- Remove Dockerfile and .dockerignore from the repository
- *tags*: Simplify Simkl search logic and soft-fail when no results found
## [1.4.0] - 2025-08-05
### Features
- *update_checker*: Enhance update checking logic and cache handling
- *dl*: Add option to include forced subtitle tracks
- *subtitle*: Add filtering for unwanted cues in WebVTT subtitles
- *tracks*: Add support for HLG color transfer characteristics in video arguments
- *dl*: Enhance language selection for video and audio tracks, including original language support
- *dl*: Improve DRM track decryption handling
- *series*: Enhance tree representation with season breakdown
- *hybrid*: Enhance extraction and conversion processes with dymanic spinning bars to follow the rest of the codebase.
- *dl*: Fix track selection to support combining -V, -A, -S flags
- *titles*: Better detection of DV across all codecs in Episode and Movie classes dvhe.05.06 was not being detected correctly.
- *dl*: Add support for services that do not support subtitle downloads
- *playready*: Enhance KID extraction from PSSH with base64 support and XML parsing
- Bump version to 1.4.0 and update changelog with new features and fixes
### Maintenance
- Update changelog with new features, enhancements, and fixes for version 1.3.0
- Bump unshackle version to 1.3.0 in uv.lock
## [1.3.0] - 2025-08-03
### Features
- Add update check interval configuration and implement rate limiting for update checks
- Implement terminal cleanup on exit and signal handling in ComfyConsole
- Add Unspecified_Image option to Transfer enum in Video class.
- Enhance credential management and CDM configuration in unshackle.yaml
- Update path of update_check.json to .gitignore
- Add scene naming option to configuration and update naming logic in titles
- Add unshackle-example.yaml to replace the unshackle.yaml file, you can now make changes to the unshackle.yaml file and pull from the the repo without issues.
- *drm*: Add support for mp4decrypt as a decryption method
### Bug Fixes
- Correct URL handling and improve key retrieval logic in HTTP vault
- Rename 'servers' to 'server_map' for proxy configuration in unshackle.yaml to resolve nord/surfshark incorrect named config
### Changes
- Replace log.exit calls with ValueError exceptions for error handling in Hybrid class
### Maintenance
- Bump version to 1.3.0 and update changelog with mp4decrypt support and enhancements
## [1.2.0] - 2025-07-30
### Features
- *dl*: Enhance hybrid processing to handle HDR10 and DV tracks separately by resolution, Hotfix for -q 2160,1080 both tracks will have Hybrid correctly now.
- *hybrid*: Display resolution of HDR10 track in hybrid mode console output and clean up unused code
- *subtitle*: Add information into unshackle.yaml on how to use new Subby subtitle conversion.
- *vaults*: Enhance vault loading with success status
- *attachment*: Ensure temporary directory is created for downloads
- *tracks*: Add duration fix handling for video and hybrid tracks
- *hybrid*: Add HDR10+ support for conversion to Dolby Vision and enhance metadata extraction
- Update version to 1.1.1 and add update checking functionality
- Bump version to 1.2.0 and update changelog, I'll eventually learn symantic versioning.
### Changes
- *env*: Enhance dependency check with detailed categorization and status summary
### Maintenance
- *workflow*: Remove Docker build and publish workflow, its too messy at the moment doing manual builds for now.
## [1.1.0] - 2025-07-30
### Features
- Update version display in main.py
- *proxies*: Add SurfsharkVPN support
- *binaries*: Add support for `MKVToolNix` and `mkvpropedit`
- *subtitles*: Integrate `subby` library for enhanced subtitle processing and conversion methods
- *hybrid*: Implement HDR10+DV hybrid processing and injection support
- *EXAMPLE*: Add support for HDR10 and DV tracks in hybrid mode
### Bug Fixes
- *cfg*: Update services directory handling
- *binaries*: Improve local binary search functionality
- *env*: Update binary search functionality to use `binaries.find`
- *env*: Update `Shaka-Packager` binary retrieval method
- *env*: Improve handling of directory paths in `info` command
- *install*: Improve UV installation process and error handling
- *download*: Skip Content-Length validation for compressed responses in curl_impersonate and requests. The fix ensures that when Content-Encoding indicates compression, we skip the validation by setting content_length = 0, allowing the downloads to complete successfully.
- *dl*: Check for dovi_tool availability in hybrid mode
- *download*: Skip Content-Length validation for compressed responses in curl_impersonate and requests
### Maintenance
- Bump version to 1.1.0 in pyproject.toml, __init__.py, and uv.lock to follow correct Semantic Versioning.
- Add CHANGELOG.md to document notable changes and version history
## [1.0.1] - 2025-07-20
### Features
- Enhance CONFIG.md with new configuration options for curl_impersonate, filenames, n_m3u8dl_re, and nordvpn
- Update .gitignore and enhance README with planned features
- Add .github/ to .gitignore to exclude GitHub-related files
- Implement VideoCodecChoice for enhanced codec selection
- Add Dockerfile and GitHub Actions workflow for building and publishing Docker image
- Update GitHub Actions workflow for Docker image build and add Docker installation instructions to README
### Bug Fixes
- Change default value of set_terminal_bg to False
- Add video_only condition to subtitle track selection logic fixes issues where ccextractor would run even with -V enabled
- Add SubtitleCodecChoice for resolving issues with config clicktype selection, using names like VTT or SRT was not working as expected
- Update shaka packager version and enhance Docker run command with additional volume mounts
### Changes
- Streamline README by removing outdated service and legal information and moved it directly to the WIKI
- Reorganize Planned Features section in README for clarity
- Improve track selection logic in dl.py
[unreleased]: https://github.com/unshackle-dl/unshackle/compare/5.0.0..HEAD
[5.0.0]: https://github.com/unshackle-dl/unshackle/compare/4.0.0..5.0.0
[4.0.0]: https://github.com/unshackle-dl/unshackle/compare/3.0.0..4.0.0
[3.0.0]: https://github.com/unshackle-dl/unshackle/compare/2.3.0..3.0.0
[2.3.0]: https://github.com/unshackle-dl/unshackle/compare/2.2.0..2.3.0
[2.2.0]: https://github.com/unshackle-dl/unshackle/compare/2.1.0..2.2.0
[2.1.0]: https://github.com/unshackle-dl/unshackle/compare/2.0.0..2.1.0
[2.0.0]: https://github.com/unshackle-dl/unshackle/compare/1.4.8..2.0.0
[1.4.8]: https://github.com/unshackle-dl/unshackle/compare/1.4.7..1.4.8
[1.4.7]: https://github.com/unshackle-dl/unshackle/compare/1.4.6..1.4.7
[1.4.6]: https://github.com/unshackle-dl/unshackle/compare/1.4.5..1.4.6
[1.4.5]: https://github.com/unshackle-dl/unshackle/compare/1.4.4..1.4.5
[1.4.4]: https://github.com/unshackle-dl/unshackle/compare/1.4.2..1.4.4
[1.4.2]: https://github.com/unshackle-dl/unshackle/compare/1.4.1..1.4.2
[1.4.1]: https://github.com/unshackle-dl/unshackle/compare/1.4.0..1.4.1
[1.4.0]: https://github.com/unshackle-dl/unshackle/compare/1.3.0..1.4.0
[1.3.0]: https://github.com/unshackle-dl/unshackle/compare/1.2.0..1.3.0
[1.2.0]: https://github.com/unshackle-dl/unshackle/compare/1.1.0..1.2.0
[1.1.0]: https://github.com/unshackle-dl/unshackle/compare/1.0.1..1.1.0
-1
View File
@@ -1 +0,0 @@
docs.unshackle.dev
+45
View File
@@ -0,0 +1,45 @@
# Development
This project is managed using [UV](https://github.com/astral-sh/uv), a fast Python package and project manager.
Install the latest version of UV before continuing. Development currently requires Python 3.9+.
## Set up
Starting from Zero? Not sure where to begin? Here's steps on setting up this Python project using UV. Note that
UV installation instructions should be followed from the UV Docs: https://docs.astral.sh/uv/getting-started/installation/
1. Clone the Repository:
```shell
git clone https://github.com/unshackle-dl/unshackle
cd unshackle
```
2. Install the Project with UV:
```shell
uv sync
```
This creates a Virtual environment and then installs all project dependencies and executables into the Virtual
environment. Your System Python environment is not affected at all.
3. Run commands in the Virtual environment:
```shell
uv run unshackle
```
Note:
- UV automatically manages the virtual environment for you - no need to manually activate it
- You can use `uv run` to prefix any command you wish to run under the Virtual environment
- For example: `uv run unshackle --help` to run the main application
- JetBrains PyCharm and Visual Studio Code will automatically detect the UV-managed virtual environment
- For more information, see: https://docs.astral.sh/uv/concepts/projects/
4. Install Pre-commit tooling to ensure safe and quality commits:
```shell
uv run pre-commit install
```
+674
View File
@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
+835
View File
@@ -0,0 +1,835 @@
# Changelog
This is Devine's Original Changelog, kept this here for historical reasons.
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Versions [3.0.0] and older use a format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
but versions thereafter use a custom changelog format using [git-cliff](https://git-cliff.org).
## [3.3.3] - 2024-05-07
### Bug Fixes
- *dl*: Automatically convert TTML Subs to WebVTT for MKV support
- *Subtitle*: Correct timestamps when merging fragmented WebVTT
### Changes
- *env*: List all directories as table in info
- *env*: List possible config path locations when not found
- *binaries*: Move all binary definitions to core/binaries file
- *curl-impersonate*: Remove manual fix for curl proxy SSL
- *curl-impersonate*: Update the default browser to chrome124
- *Config*: Move possible config paths out of func to constant
- *utilities*: Remove get_binary_path, use binaries.find instead
- *dl*: Improve readability of download worker errors
- *env*: Shorten paths on Windows with env vars
## [3.3.2] - 2024-04-16
### Bug Fixes
- *Video*: Ensure track is supported in change_color_range()
- *Video*: Optionalise constructor args, add doc-string & checks
- *Audio*: Optionalise constructor args, add doc-string & checks
- *Subtitle*: Optionalise constructor args, add doc-string & checks
- *HLS*: Ensure playlist.stream_info.codecs exists before use
- *HLS*: Ensure playlist.stream_info.resolution exists before use
- *env*: List used config path, otherwise the default path
- *cfg*: Use loaded config path instead of hardcoded default
- *Basic*: Return None not Exception if no proxy configured
### Changes
- *Video*: Do not print "?"/"Unknown" values in str()
- *Audio*: Do not print "?"/"Unknown" values in str()
- *Subtitle*: Do not print "?"/"Unknown" values in str()
- *Audio*: List lang after codec for consistency with other Tracks
- *Video*: Return None if no m3u RANGE, not SDR
- *env*: Use -- to indicate no config found/loaded
### New Contributors
- [retouching](https://github.com/retouching)
## [3.3.1] - 2024-04-05
### Features
- *dl*: Add *new* --workers to set download threads/workers
### Bug Fixes
- *Chapter*: Cast values to int prior to formatting
- *requests*: Fix multithreaded downloads
- *Events*: Dereference subscription store from ephemeral store
### Changes
- *dl*: Change --workers to --downloads
### New Contributors
- [knowhere01](https://github.com/knowhere01)
## [3.3.0] - 2024-04-02
### Features
- Add support for MKV Attachments via Attachment class
- *dl*: Automatically attach fonts used within SSAv4 subs
- *dl*: Try find SSAv4 fonts in System OS fonts folder
- *Basic*: Allow single string URIs for countries
- *Basic*: Allow proxy selection by index (one-indexed)
- *Events*: Add new global Event Observer API
### Bug Fixes
- *curl-impersonate*: Set Cert-Authority Bundle for HTTPS Proxies
- *Basic*: Make query case-insensitive
- *WVD*: Ensure WVDs dir exists before moving WVD file
- *WVD*: Fix empty path to WVDs folder check
- *WVD*: Move log out of loop to save performance
- *WVD*: Move log with path before Device load
- *WVD*: Add exists/empty checks to WVD folder dumps
- *Basic*: Fix variable typo regression
### Changes
- *Basic*: Improve proxy format checks
- *WVD*: Print error if path to parse doesn't exist
- *WVD*: Seperate logs in loop for visual clarity
- *Track*: Move from OnXyz callables to Event observer
## [3.2.0] - 2024-03-25
### Features
- *ClearKey*: Pass session not proxy str in from_m3u_key method
- *Track*: Allow Track to choose downloader to use
- *search*: New Search command, Service method, SearchResult Class
### Bug Fixes
- *dl*: Include chapters when muxing
- *aria2c*: Support aria2(c) 1.37.0 by handling upstream regression
- *MultipleChoice*: Simplify super() call and value types
- *dl*: Add single mux job if there's no video tracks
- *Track*: Compute Track ID from the `this` variable, not `self`
- *DASH/HLS*: Don't merge folders, skip final merge if only 1 segment
- *dl*: Use click.command() instead of click.group()
- *HLS*: Remove save dir even if final merge wasn't needed
- *Track*: Fix order of operation mistake in get_track_name
- *requests*: Set HTTP pool connections/maxsize to max workers
- *Video*: Delete original file after using change_color_range()
- *Video*: Delete original file after using remove_eia_cc()
- *requests*: Manually compute default max_workers or pool size is None
- *requests*: Block until connection freed if too many connections
- *HLS*: Delete subtitle segments as they are merged
- *HLS*: Delete video/audio segments after FFmpeg merge
### Changes
- *ClearKey*: Only use User-Agent if none set in from_m3u_key
- *Track*: Remove TERRITORY_MAP constant, trim SAR China manually
- *Track*: Default the track name to it's lang's script/territory
- *Service*: Go back to the default pool_maxsize in Session
## [3.1.0] - 2024-03-05
### Features
- *cli*: Implement MultipleChoice click param based on Choice param
- *dl*: Skip video lang filter if --v-lang unused & only 1 video lang
- *dl*: Change --vcodec default to None, use any codec
- *dl*: Support multiple -r/--range and mux ranges separately
- *Subtitle*: Convert from fTTML->TTML & fVTT->WebVTT post-download
- *Track*: Make ID optional, Automatically compute one if not provided
- *Track*: Add a name property to use for the Track Name
### Bug Fixes
- *dl*: Have --sub-format default to None to keep original sub format
- *HLS*: Use filtered out segment key info
- *Track*: Don't modify lang when getting name
- *Track*: Don't use fallback values "Zzzz"/"ZZ" for track name
- *version*: The `__version__` variable forgot to be updated
### Changes
- Move dl command's download_track() to Track.download()
- *dl*: Remove unused `get_profiles()` method
- *DASH*: Move data values from track url to track data property
- *DASH*: Change how Video FPS is gotten to remove FutureWarning log
- *Track*: Add type checks, improve typing
- *Track*: Remove swap() method and it's uses
- *Track*: Remove unused DRM enum
- *Track*: Rename Descriptor's M3U & MPD to HLS & DASH
- *Track*: Remove unnecessary bool casting
- *Track*: Move the path class instance variable with the rest
- *Track*: Return new path on move(), raise exceptions on errors
- *Track*: Move delete and move methods near start of Class
- *Track*: Rename extra to data, enforce type as dict
### Builds
- Explicitly use marisa-trie==1.1.0 for Python 3.12 wheels
## [3.0.0] - 2024-03-01
### Added
- Support for Python 3.12.
- Audio track's Codec Enum now has [FLAC](https://en.wikipedia.org/wiki/FLAC) defined.
- The Downloader to use can now be set in the config under the [downloader key](CONFIG.md#downloader-str).
- New Multi-Threaded Downloader, `requests`, that makes HTTP(S) calls using [Python-requests](https://requests.readthedocs.io).
- New Multi-Threaded Downloader, `curl_impersonate`, that makes HTTP(S) calls using [Curl-Impersonate](https://github.com/yifeikong/curl-impersonate) via [Curl_CFFI](https://github.com/yifeikong/curl_cffi).
- HLS manifests specifying a Byte range value without starting offsets are now supported.
- HLS segments that use `EXT-X-DISCONTINUITY` are now supported.
- DASH manifests with SegmentBase or only BaseURL are now supported.
- Subtitle tracks from DASH manifests now automatically marked as SDH if `urn:tva:metadata:cs:AudioPurposeCS:2007 = 2`.
- The `--audio-only/--subs-only/--chapters-only` flags can now be used simultaneously. For example, `--subs-only`
with `--chapters-only` will get just Subtitles and Chapters.
- Added `--video-only` flag, which can also still be simultaneously used with the only "only" flags. Using all four
of these flags will have the same effect as not using any of them.
- Added `--no-proxy` flag, disabling all uses of proxies, even if `--proxy` is set.
- Added `--sub-format` option, which sets the wanted output subtitle format, defaulting to SubRip (SRT).
- Added `Subtitle.reverse_rtl()` method to use SubtitleEdit's `/ReverseRtlStartEnd` functionality.
- Added `Subtitle.convert()` method to convert the loaded Subtitle to another format. Note that you cannot convert to
fTTML or fVTT, but you can convert from them. SubtitleEdit will be used in precedence over pycaption if available.
Converting to SubStationAlphav4 requires SubtitleEdit, but you may want to manually alter the Canvas resolution after
the download.
- Added support for SubRip (SRT) format subtitles in `Subtitle.parse()` via pycaption.
- Added `API` Vault Client aiming for a RESTful like API.
- Added `Chapters` Class to hold the new reworked `Chapter` objects, automatically handling stuff like order of the
Chapters, Chapter numbers, loading from a chapter file or string, and saving to a chapter file or string.
- Added new `chapter_fallback_name` config option allowing you to set a Chapter Name Template used when muxing Chapters
into an MKV Container with MKVMerge. Do note, it defaults to no Chapter Fallback Name at all, but MKVMerge will force
`Chapter {i:02}` at least for me on Windows with the program language set to English. You may want to instead use
`Chapter {j:02}` which will do `Chapter 01, Intro, Chapter 02` instead of `Chapter 01, Intro, Chapter 03` (an Intro
is not a Chapter of story, but it is the 2nd Chapter marker, so It's up to you how you want to interpret it).
- Added new `Track.OnSegmentDownloaded` Event, called any time one of the Track's segments were downloaded.
- Added new `Subtitle.OnConverted` Event, called any time that Subtitle is converted.
- Implemented `__add__` method to `Tracks` class, allowing you to add to the first Tracks object. For example, making
it handy to merge HLS video tracks with DASH tracks, `tracks = dash_tracks + hls_tracks.videos`, or for iterating:
`for track in dash.videos + hls.videos: ...`.
- Added new utility `get_free_port()` to get a free local port to use, though it may be taken by the time it's used.
### Changed
- Moved from my forked release of pymp4 (`rlaphoenix-pymp4`) back to the original `pymp4` release as it is
now up-to-date with some of my needed fixes.
- The DASH manifest is now stored in the Track `url` property to be reused by `DASH.download_track()`.
- Encrypted DASH streams are now downloaded in full and then decrypted, instead of downloading and decrypting
each individual segment. Unlike HLS, DASH cannot dynamically switch out the DRM/Protection information.
This brings both CPU and Disk IOPS improvements, as well as fixing rare weird decryption anomalies like broken
or odd timestamps, decryption failures, or broken a/v continuity.
- When a track is being decrypted, it now displays "Decrypting" and afterward "Decrypted" in place of the download
speed.
- When a track finishes downloaded, it now displays "Downloaded" in place of the download speed.
- When licensing is needed and fails, the track will display "FAILED" in place of the download speed. The track
download will cancel and all other track downloads will be skipped/cancelled; downloading will end.
- The fancy smart quotes (`“` and `”`) are now stripped from filenames.
- All available services are now listed if you provide an invalid service tag/alias.
- If a WVD file fails to load and looks to be in the older unsupported v1 format, then instructions on migrating to
v2 will be displayed.
- If Shaka-Packager prints an error (i.e., `:ERROR:` log message) it will now raise a `subprocess.CalledProcessError`
exception, even if the process return code is 0.
- The Video classes' Primaries, Transfer, and Matrix classes had changes to their enum names to better represent their
values and uses. See the changed names in the [commit](https://github.com/unshackle-dl/unshackle/commit/c159672181ee3bd07b06612f256fa8590d61795c).
- SubRip (SRT) Subtitles no longer have the `MULTI-LANGUAGE SRT` header forcefully removed. The root cause of the error
was identified and fixed in this release.
- Since `Range.Transfer.SDR_BT_601_625 = 5` has been removed, `Range.from_cicp()` now internally remaps CICP transfer
values of `5` to `6` (which is now `Range.Transfer.BT_601 = 6`).
- Referer and User-Agent Header values passed to the aria2(c) downloader is now set via the dedicated `--referer` and
`--user-agent` options respectively, instead of `--header`.
- The aria2(c) `-j`, `-x`, and `-s` option values can now be set by the config under the `aria2c` key in the options'
full names.
- The aria2(c) `-x`, and `-s` option values now use aria2(c)'s own default values for them instead of `16`. The `j`
option value defaults to ThreadPoolExecutor's algorithm of `min(32,(cpu_count+4))`.
- The download progress bar now states `LICENSING` on the speed text when licensing DRM, and `LICENSED` once finished.
- The download progress bar now states `CANCELLING`/`CANCELLED` on the speed text when cancelling downloads. This is to
make it more clear that it didn't just stop, but stopped as it was cancelled.
- The download cancel/skip events were moved to `constants.py` so it can be used across the codebase easier without
argument drilling. `DL_POOL_STOP` was renamed to `DOWNLOAD_CANCELLED` and `DL_POOL_SKIP` to `DOWNLOAD_LICENCE_ONLY`.
- The Cookie header is now calculated for each URL passed to the aria2(c) downloader based on the URL. Instead of
passing every single cookie, which could have two cookies with the same name aimed for different host names, we now
pass only cookies intended for the URL.
- The aria2(c) process no longer prints output to the terminal directly. unshackle now only prints contents of the
captured log messages to the terminal. This allows filtering out of errors and warnings that isn't a problem.
- DASH and HLS no longer download segments silencing errors on all but the last retry as the downloader rework makes
this unnecessary. The errors will only be printed on the final retry regardless.
- `Track.repackage()` now saves as `{name}_repack.{ext}` instead of `{name}.repack.{ext}`.
- `Video.change_color_range()` now saves as `{name}_{limited|full}_range.{ext}` instead of `{name}.range{0|1}.{ext}`.
- `Widevine.decrypt()` now saves as `{name}_decrypted.{ext}` instead of `{name}.decrypted.{ext}`.
- Files starting with the save path's name and using the save path's extension, but not the save path, are no longer
deleted on download finish/stop/failure.
- The output container format is now explicitly specified as `MP4` when calling `shaka-packager`.
- The default downloader is now `requests` instead of `aria2c` to reduce required external dependencies.
- Reworked the `Chapter` class to only hold a timestamp and name value with an ID automatically generated as a CRC32 of
the Chapter representation.
- The `--group` option has been renamed to `--tag`.
- The config file is now read from three more locations in the following order:
1) The unshackle Namespace Folder (e.g., `%appdata%/Python/Python311/site-packages/unshackle/unshackle.yaml`).
2) The Parent Folder to the unshackle Namespace Folder (e.g., `%appdata%/Python/Python311/site-packages/unshackle.yaml`).
3) The AppDirs User Config Folder (e.g., `%localappdata%/unshackle/unshackle.yaml`).
Location 2 allows having a config at the root of a portable folder.
- An empty config file is no longer created when no config file is found.
- You can now set a default cookie file for a Service, [see README](README.md#cookies--credentials).
- You can now set a default credential for a Service, [see config](CONFIG.md#credentials-dictstr-strlistdict).
- Services are now auth-less by default and the error for not having at least a cookie or credential is removed.
Cookies/Credentials will only be loaded if a default one for the service is available, or if you use `-p/--profile`
and the profile exists.
- Subtitles when converting to SubRip (SRT) via SubtitleEdit will now use the `/ConvertColorsToDialog` option.
- HLS segments are now merged by discontinuity instead of all at once. The merged discontinuities are then finally
merged to one file using `ffmpeg`. Doing the final merge by byte concatenation did not work for some playlists.
- The Track is no longer passed through Event Callables. If you are able to set a function on an Even Callable, then
you should have access to the track reference to call it directly if needed.
- The Track.OnDecrypted event callable is now passed the DRM and Segment objects used to Decrypt. The segment object is
only passed from HLS downloads.
- The Track.OnDownloaded event callable is now called BEFORE decryption, right after downloading, not after decryption.
- All generated Track ID values across the codebase has moved from md5 to crc32 values as code processors complain
about its use surrounding security, and it's length is too large for our use case anyway.
- HLS segments are now downloaded multi-threaded first and then processed in sequence thereafter.
- HLS segments are no longer decrypted one-by-one, requiring a lot of shaka-packager processes to run and close.
They now merged and decrypt in groups based on their EXT-X-KEY, before being merged per discontinuity.
- The DASH and HLS downloaders now pass multiple URLs to the downloader instead of one-by-one, heavily increasing speed
and reliability as connections are kept alive and re-used.
- Downloaders now yield back progress information in the same convention used by `rich`'s `Progress.update()` method.
DASH and HLS now pass the yielded information to their progress callable instead of passing the progress callable to
the downloader.
- The aria2(c) downloader now uses the aria2(c) JSON-RPC interface to query for download progress updates instead of
parsing the stdout data in an extremely hacky way.
- The aria2(c) downloader now re-routes non-HTTP proxies via `pproxy` by a subprocess instead of the now-removed
`start_pproxy` utility. This way has proven to be easier, more reliable, and prevents pproxy from messing with rich's
terminal output in strange ways.
- All downloader function's have an altered signature but ultimately similar. `uri` to `urls`, `out` (path) was removed,
we now calculate the save path by passing an `output_dir` and `filename`. The `silent`, `segmented`, and `progress`
parameters were completely removed.
- All downloader `urls` can now be a string or a dictionary containing extra URL-specific options to use like
URL-specific headers. It can also be a list of the two types of URLs to downloading multi-threaded.
- All downloader `filenames` can be a static string, or a filename string template with a few variables to use. The
template system used is f-string, e.g., `"file_{i:03}{ext}"` (ext starts with `.` if there's an extension).
- DASH now updates the progress bar when merging segments.
- The `Widevine.decrypt()` method now also searches for shaka-packager as just `packager` as it is the default build
name. (#74)
### Removed
- The `unshackle auth` command and sub-commands due to lack of support, risk of data, and general quirks with it.
- Removed `profiles` config, you must now specify which profile you wish to use each time with `-p/--profile`. If you
use a specific profile a lot more than others, you should make it the default.
- The `saldl` downloader has been removed as their binary distribution is whack and development has seemed to stall.
It was only used as an alternative to what was at the time the only downloader, aria2(c), as it did not support any
form of Byte Range, but `saldl` did, which was crucial for resuming extremely large downloads or complex playlists.
However, now we have the requests downloader which does support the Range header.
- The `Track.needs_proxy` property was removed for a few design architectural reasons.
1) Design-wise it isn't valid to have --proxy (or via config/otherwise) set a proxy, then unpredictably have it
bypassed or disabled. If I specify `--proxy 127.0.0.1:8080`, I would expect it to use that proxy for all
communication indefinitely, not switch in and out depending on the track or service.
2) With reason 1, it's also a security problem. The only reason I implemented it in the first place was so I could
download faster on my home connection. This means I would authenticate and call APIs under a proxy, then suddenly
download manifests and segments e.t.c under my home connection. A competent service could see that as an indicator
of bad play and flag you.
3) Maintaining this setup across the codebase is extremely annoying, especially because of how proxies are setup/used
by Requests in the Session. There's no way to tell a request session to temporarily disable the proxy and turn it
back on later, without having to get the proxy from the session (in an annoying way) store it, then remove it,
make the calls, then assuming your still in the same function you can add it back. If you're not in the same
function, well, time for some spaghetti code.
- The `Range.Transfer.SDR_BT_601_625 = 5` key and value has been removed as I cannot find any official source to verify
it as the correct use. However, usually a `transfer` value of `5` would be PAL SD material so it better matches `6`,
which is (now named) `Range.Transfer.BT_601 = 6`. If you have something specifying transfer=5, just remap it to 6.
- The warning log `There's no ... Audio Tracks, likely part of an invariant playlist, continuing...` message has been
removed. So long as your playlist is expecting no audio tracks, or the audio is part of the video transport, then
this wouldn't be a problem whatsoever. Therefore, having it log this annoying warning all the time is pointless.
- The `--min-split-size` argument to the aria2(c) downloader as it was only used to disable splitting on
segmented downloads, but the newer downloader system wouldn't really need or want this to be done. If aria2 has
decided based on its other settings to have split a segment file, then it likely would benefit from doing so.
- The `--remote-time` argument from the aria2(c) downloader as it may need to do a GET and a HEAD request to
get the remote time information, slowing the download down. We don't need this information anyway as it will likely
be repacked with `ffmpeg` or multiplexed with `mkvmerge`, discarding/losing that information.
- DASH and HLS's 5-attempt retry loop as the downloaders will retry for us.
- The `start_pproxy` utility has been removed as all uses of it now call `pproxy` via subprocess instead.
- The `LANGUAGE_MUX_MAP` constant and it's usage has been removed as it is no longer necessary as of MKVToolNix v54.
### Fixed
- Uses of `__ALL__` with Class objects have been correct to `__all__` with string objects, following PEP8.
- Fixed value of URL passed to `Track.get_key_id()` as it was a tuple rather than the URL string.
- The `--skip-dl` flag now works again after breaking in v[1.3.0].
- Move WVD file to correct location on new installations in the `wvd add` command.
- Cookie data is now passed to downloaders and use URLs based on the URI it will be used for, just like a browser.
- Failure to get FPS in DASH when SegmentBase isn't used.
- An error message is now returned if a WVD file fails to load instead of raising an exception.
- Track language information within M3U playlists are now validated with langcodes before use. Some manifests use the
property for arbitrary data that their apps/players use for their own purposes.
- Attempt to fix non-UTF-8 and mixed-encoding Subtitle downloads by automatically converting to UTF-8. (#43)
Decoding is attempted in the following order: UTF-8, CP-1252, then finally chardet detection. If it's neither UTF-8
nor CP-1252 and chardet could not detect the encoding, then it is left as-is. Conversion is done per-segment if the
Subtitle is segmented, unless it's the fVTT or fTTML formats which are binary.
- Chapter Character Encoding is now explicitly set to UTF-8 when muxing to an MKV container as Windows seems to default
to latin1 or something, breaking Chapter names with any sort of special character within.
- Subtitle passed through SubtitleEdit now explicitly use UTF-8 character encoding as it usually defaulted to UTF-8
with Byte Order Marks (aka UTF-8-SIG/UTF-8-BOM).
- Subtitles passed through SubtitleEdit now use the same output format as the subtitle being processed instead of SRT.
- Fixed rare infinite loop when the Server hosting the init/header data/segment file responds with a `Content-Length`
header with a value of `0` or smaller.
- Removed empty caption lists/languages when parsing Subtitles with `Subtitle.parse()`. This stopped conversions to SRT
containing the `MULTI-LANGUAGE SRT` header when there was multiple caption lists, even though only one of them
actually contained captions.
- Text-based Subtitle formats now try to automatically convert to UTF-8 when run through `Subtitle.parse()`.
- Text-based Subtitle formats now have `&lrm;` and `&rlm;` HTML entities unescaped post-download as some rendering
libraries seems to not decode them for us. SubtitleEdit also has problems with `/ReverseRtlStartEnd` unless it's
already decoded.
- Fixed two concatenation errors surrounding DASH's BaseURL, sourceURL, and media values that start with or use `../`.
- Fixed the number values in the `Newly added to x/y Vaults` log, which now states `Cached n Key(s) to x/y Vaults`.
- File write handler now flushes after appending a new segment to the final save path or checkpoint file, reducing
memory usage by quite a bit in some scenarios.
### New Contributors
- [Shivelight](https://github.com/Shivelight)
## [2.2.0] - 2023-04-23
### Breaking Changes
Since `-q/--quality` has been reworked to support specifying multiple qualities, the type of this value is
no longer `None|int`. It is now `list[int]` and the list may be empty. It is no longer ever a `None` value.
Please make sure any Service code that uses `quality` via `ctx.parent.params` reflects this change. You may
need to go from an `if quality: ...` to `for res in quality: ...`, or such. You may still use `if quality`
to check if it has 1 or more resolution specified, but make sure that the code within that if tree supports
more than 1 value in the `quality` variable, which is now a list. Note that the list will always be in
descending order regardless of how the user specified them.
### Added
- Added the ability to specify and download multiple resolutions with `-q/--quality`. E.g., `-q 1080p,720p`.
- Added support for DASH manifests that use SegmentList with range values on the Initialization definition (#47).
- Added a check for `uuid` mp4 boxes containing `tenc` box data when getting the Track's Key ID to improve
chances of finding a Key ID.
### Changed
- The download path is no longer printed after each download. The simple reason is it felt unnecessary.
It filled up a fair amount of vertical space for information you should already know.
- The logs after a download finishes has been split into two logs. One after the actual downloading process
and the other after the multiplexing process. The downloading process has its own timer as well, so you can
see how long the downloads itself took.
- I've switched from using the official pymp4 (for now) with my fork. At the time this change was made the
original bearypig pymp4 repo was stagnant and the PyPI releases were old. I forked it, added some fixes
by TrueDread and released my own update to PyPI, so it's no longer outdated. This was needed for some
mp4 box parsing fixes. Since then the original repo is no longer stagnant, and a new release was made on
PyPI. However, my repo still has some of TrueDread's fixes that is not yet on the original repository nor
on PyPI.
### Removed
- Removed the `with_resolution` method in the Tracks class. It has been replaced with `by_resolutions`. The
new replacement method supports getting all or n amount of tracks by resolution instead of the original
always getting all tracks by resolution.
- Removed the `select_per_language` method in the Tracks class. It has been replaced with `by_language`. The
new replacement method supports getting all or n amount of tracks by language instead of the original only
able to get one track by language. It now defaults to getting all tracks by language.
### Fixed
- Prevented some duplicate Widevine tree logs under specific edge-cases.
- The Subtitle parse method no longer absorbs the syntax error message.
- Replaced all negative size values with 0 on TTML subtitles as a negative value would cause syntax errors.
- Fixed crash during decryption when shaka-packager skips decryption of a segment as it had no actual data and
was just headers.
- Fixed CCExtractor crash in some scenarios by repacking the video stream prior to extraction.
- Fixed rare crash when calculating download speed of DASH and HLS downloads where a segment immediately finished
after the previous segment. This seemed to only happen on the very last segment in rare situations.
- Fixed some failures parsing `tenc` mp4 boxes when obtaining the track's Key ID by using my own fork of pymp4
with up-to-date code and further fixes.
- Fixed crashes when parsing some `tenc` mp4 boxes by simply skipping `tenc` boxes that fail to parse. This happens
because some services seem to mix up the data of the `tenc` box with that of another type of box.
- Fixed using invalid `tenc` boxes by skipping ones with a version number greater than 1.
## [2.1.0] - 2023-03-16
### Added
- The Track get_init_segment method has been re-written to be more controllable. A specific Byte-range, URL, and
maximum size can now be specified. A manually specified URL will override the Track's current URL. The Byte-range
will override the fallback value of `0-20000` (where 20000 is the default `maximum_size`). It now also checks if the
server supports Byte-range, or it will otherwise stream the response. It also tries to get the file size length and
uses that instead of `maximum_size` unless it's bigger than `maximum_size`.
- Added new `get_key_id` method to Track to probe the track for a track-specific Encryption Key ID. This is similar to
Widevine's `from_track` method but ignores all `pssh` boxes and manifest information as the information within those
could be for a wider range of tracks or not for that track at all.
- Added a 5-attempt retry system to DASH and HLS downloads. URL downloads only uses aria2(c)'s built in retry system
which has the same amount of tries and same delay between attempts. Any errors emitted when downloading segments will
not be printed to console unless it occurred on the last attempt.
- Added a fallback way to obtain language information by taking it from the representation ID value, which may have the
language code within it. E.g., `audio_en=128000` would be an English audio track at 128kb/s. We now take the `en`
from that ID where possible.
- Added support for 13-char JS-style timestamp values to the Cacher system.
- Improved Forced Subtitle recognition by checking for both `forced-subtitle` and `forced_subtitle` (#43).
### Changed
- The `*` symbol is no longer spaced after the Widevine `KID:KEY` when denoting that it is for this specific PSSH.
This reduces wasted vertical space.
- The "aria2 will resume download if the transfer is restarted" logs that occur when aria2(c) handles the CTRL+C break,
and "If there are any errors, then see the log file" logs are now ignored and no longer logged to the console.
- DASH tracks will no longer prepare and license DRM unless it's just about to download. This is to reduce unnecessary
preparation of DRM if the track had been converted to a URL download.
- For a fix listed below, we now use a fork of https://github.com/globocom/m3u8 that fixes a glaring problem with the
EXT-X-KEY parsing system. See <https://github.com/globocom/m3u8/pull/313>.
- The return code when mkvmerge returns an error is now logged with the error message.
- SubtitleEdit has been silenced when using it for SDH stripping.
### Fixed
- Fixed URL joining and Base URL calculations on DASH manifests that use multiple Base URL values.
- URL downloads will now store the chosen DRM before preparing and licensing with the DRM.
- URL downloads will now prepare and license with the DRM if the Track has pre-existing DRM information. Previously it
would only prepare and license DRM if it did not pre-emptively have DRM information before downloading.
- The `*` symbol that indicates that the KID:KEY is for the track being downloaded now uses the new `get_key_id` method
of the track for a more accurate reading.
- License check now ensures if a KEY was returned for the Track instead of all KIDs of the Track's PSSH. This prevents
an issue where the PSSH may have Key IDs for a 720p and 1080p track, yet only a KEY for the 720p track was returned.
It would have then raised an error and stopped the download, even though you are downloading the 720p track and not
the 1080p track, therefore the error was irrelevant.
- Unnecessary duplicate license calls are now prevented in some scenarios where `--cdm-only` is used.
- Fixed accuracy and speed of preparing and licensing DRM on HLS manifests where multiple EXT-X-KEY definitions appear
in the manifest throughout the file. Using <https://github.com/globocom/m3u8/pull/313> we can now accurately get a
list of EXT-X-KEYs mapped to each segment. This is a game changer for HLS manifests that use unique keys for every
single (or most) segments as it would have otherwised needed to initialize (and possibly do network requests) for
100s of EXT-X-KEY information, per segment. This caused downloads of HLS manifests that used a unique key per segment
to slow to a binding crawl, and still not even decrypt correctly as it wouldn't be able to map the correct initialized
key to the correct segment.
- Fixed a regression that incorrectly implemented the OnMultiplex event for Audio and Subtitle tracks causing them to
never trigger. It would instead accidentally have trigger the last Video track's OnMultiplex event instead of the
Audio or Subtitle's event.
- The above fix also fixed the automatic SDH stripping subtitle. Any automatically created SDH->non-SDH subtitle from
prior downloads would not have actually had SDH captions stripped, it would instead be a duplicate subtitle.
### New Contributors
- [Hollander-1908](https://github.com/Hollander-1908)
## [2.0.1] - 2023-03-07
### Added
- Re-added logging support for shaka-packager on errors and warnings. Do note that INFO logs and the 'Insufficient bits
in bitstream for given AVC profile' warning logs are ignored and never printed.
- Added new exceptions to the Widevine DRM class, `CEKNotFound` and `EmptyLicense`.
- Added support for Byte-ranges on HLS init maps.
### Changed
- Now lists the full 'Episode #' text when listing episode titles without an episode name.
- Subprocess exceptions from a download worker no longer prints a traceback. It now only logs the return code. This is
because all subprocess errors during a download is now logged, therefore the full traceback is no longer necessary.
- Aria2(c) no longer pre-allocates file space if segmented. This is to reduce generally unnecessary upfront I/O usage.
- The Widevine DRM class's `get_content_keys` method now raises the new `CEKNotFound` and `EmptyLicense` exceptions not
`ValueError` exceptions.
- The prepare_drm code now raises exceptions where needed instead of `sys.exit(1)`. Callees do not need to make any
changes. The exception should continue to go up the call stack and get handled by the `dl` command.
### Fixed
- Fixed regression that broke support for pproxy. Do note that while pproxy has wheel's for Python 3.11+, it seems to
be broken. I recommend using Python 3.10 or older for now. See <https://github.com/qwj/python-proxy/issues/161>.
- Fixed regression and now store the chosen DRM object back to the track.drm field. Please note that using the track
DRM field in Service code is not recommended, but for some services it's simply required.
- Fixed regression since v1.4.0 where the byte-range calculation was actually slightly off one on the right-side range.
This was a one-indexed vs. zero-indexed problem. Please note that this could have affected the integrity of HLS
downloads if they used EXT-X-BYTERANGE.
- Fixed possible soft-lock in HLS if the Queue for previous segment key and init data gets stuck in an empty state over
an exception in a download thread. E.g., if a thread takes the previous segment key, throws an exception, and did not
get the chance to give it back for the next thread.
- The prepare_drm function now handles unexpected exceptions raised in the Service's license method. This code would of
otherwise been absorbed and the download would have soft-locked.
- Prevented a double-licensing call race-condition on HLS tracks by using a threading lock when preparing DRM
information. This is not required in DASH, as it prepares DRM on the main thread, once, not per-segment.
- Fixed printing of aria2(c) logs when redirecting progress information to rich progress bars.
- Explicitly mark DASH and HLS aria2(c) downloads as segmented.
- Fixed listing of episode titles without an episode name.
- Fixed centering of the project URL in the ASCII banner.
- Removed the accidental double-newline after the ASCII banner.
## [2.0.0] - 2023-03-01
This release brings a huge change to the fundamentals of unshackle's logging, UI, and UX.
### Added
- Add new dependency [rich](https://github.com/Textualize/rich) for advanced color and logging capabilities.
- Set rich console output color scheme to the [Catppuccin Mocha](https://github.com/catppuccin/palette) theme.
- Add full download cancellation support by using CTRL+C. Track downloads will now be marked as STOPPED if you press
CTRL+C to stop the download, or FAILED if any unexpected exception occurs during a download. The track will be marked
as SKIPPED if the download stopped or failed before it got a chance to begin. It will print a download cancelled
message if downloading was stopped, or a download error message if downloading failed. It will print the first
download error traceback with rich before stopping.
- Downloads will now automatically cancel if any track or segment download fails.
- Implement sub-commands `add` and `delete` to the `wvd` command for adding and deleting WVD (Widevine Device) files to
and from the configured WVDs directory (#31).
- Add new config option to disable the forced background color. You may want to disable the purple background if you're
terminal isn't able to apply it correctly, or you prefer to use your own terminal's background color.
- Create `ComfyConsole`, `ComfyLogRenderer`, and `ComfyRichHandler`. These are hacky classes to implement padding to
the left and right of all rich console output. This gives unshackle a comfortable and freeing look-and-feel.
- An ASCII banner is now displayed at the start of software execution with the version number.
- Add rich status output to various parts of the download process. It's also used when checking GEOFENCE within the
base Service class. I encourage you to follow similar procedures where possible in Service code. This will result in
cleaner log output, and overall less logs being made when finished.
- Add three rich horizontal rules to separate logs during the download process. The Service used, the Title received
from `get_titles()`, and then the Title being downloaded. This helps identify which logs are part of which process.
- Add new `tree` methods to `Series`, `Movies`, and `Album` classes to list items within the objects with Rich Tree.
This allows for more rich console output when displaying E.g., Seasons and Episodes within a Series, or Songs within
an Album.
- Add new `tree` method to the `Tracks` class to list the tracks received from `get_tracks()` with Rich Tree. Similar
to the change just above, this allows for more rich console output. It has replaced the `Tracks.print()` method.
- Add a rich progress bar to the track multiplexing operation.
- Add a log when a download finishes, how long it took, and where the final muxed file was moved to.
- Add a new track event, `OnMultiplex`. This event is run prior to multiplexing the finalized track data together. Use
this to run code once a track has finished downloading and all the post-download operations.
- Add support for mapping Netflix profiles beginning with `h264` to AVC. E.g., the new -QC profiles.
- Download progress bars now display the download speed. It displays in decimal (^1024) size. E.g., MB/s.
- If a download stops or fails, any residual file that may have been downloaded in an incomplete OR complete state will
now be deleted. Download continuation is not yet supported, and this will help to reduce leftover stale files.
### Changed
- The logging base config now has `ComfyRichHandler` as its log handler for automatic rich console output when using
the logging system.
- The standard `traceback` module has been overridden with `rich.traceback` for styled traceback output.
- Only the rich console output is now saved when using `--log`.
- All `tqdm` progress bars have been replaced with rich progress bars. The rich progress bars are now displayed under
each track tree.
- The titles are now only listed if `--list-titles` is used. Otherwise, only a brief explanation of what it received
from `get_titles()` will be returned. E.g., for Series it will list how many seasons and episodes were received.
- Similarly, all available tracks are now only listed if `--list` is used. This is to reduce unnecessary prints, and to
separate confusion between listings of available tracks, and listings of tracks that are going to be downloaded.
- Listing all available tracks with `--list` no longer continues execution. It now stops after the first list. If you
want to list available tracks for a specific title, use `-w` in combination with `--list`.
- The available tracks are now printed in a rich panel with a header denoting the tracks as such.
- The `Series`, `Movies`, and `Album` classes now have a much more simplified string representation. They now simply
state the overarching content within them. E.g., Series says the title and year of the TV Show.
- The final log when all titles are processed is now a rich log and states how long the entire process took.
- Widevine DRM license information is now printed below the tracks as a rich tree.
- The CCExtractor process, Subtitle Conversion process, and FFmpeg Repacking process were all moved out of the track
download function (and therefore the thread) to be done on the main thread after downloading. This improves download
speed as the threads can close and be freed quicker for the next track to begin.
- The CCExtractor process is now optional and will be skipped if the binary could not be found. An error is still
logged in the cases where it would have run.
- The execution point of the `OnDownloaded` event has been moved to directly run after the stream has been downloaded.
It used to run after all the post-download operations finished like CCExtractor, FFmpeg Repacking, and Subtitle
Conversion.
- The automatic SDH-stripped subtitle track now uses the new `OnMultiplex` event instead of `OnDownloaded`. This is to
account for the previous change as it requires the subtitle to be first converted to SubRip to support SDH-stripping.
- Logs during downloads now appear before the downloading track list. This way it isn't constantly interrupting view of
the progress.
- Now running aria2(c) with normal subprocess instead of through asyncio. This removes the creation of yet another
thread which is unnecessary as these calls would have already been under a non-main thread.
- Moved Widevine DRM licensing calls before the download process for normal URL track downloads.
- Segment Merging code for DASH and HLS downloads have been moved from the `dl` class to the HLS and DASH class.
### Removed
- Remove explicit dependency on `coloredlogs` and `colorama` as they are no longer used by unshackle itself.
- Remove dependency `tqdm` as it was replaced with rich progress bars.
- Remove now-unused logging constants like the custom log formats.
- Remove `Tracks.print()` function as it was replaced with the new `Tracks.tree()` function.
- Remove unnecessary sleep calls at the start of threads. This was believed to help with the download stop event check
but that was not the case. It instead added an artificial delay with downloads.
### Fixed
- Fix another crash when using unshackle without a config file. It now creates the directory of the config file before
making a new config file.
- Set the default aria2(c) file-allocation to `prealloc` like stated in the config documentation. It uses `prealloc` as
the default, as `falloc` is generally unsupported in most scenarios, so it's not a good default.
- Correct the config documentation in regard to `proxies` now being called `proxy_providers`, and `basic` actually
being a `dict` of lists, and not a `dict` of strings.
## [1.4.0] - 2023-02-25
### Added
- Add support for byte-ranged HLS and DASH segments, i.e., HLS EXT-X-BYTERANGE and DASH SegmentBase. Byte-ranged
segments will be downloaded using python-requests as aria2(c) does not support byte ranges.
- Added support for data URI scheme in ClearKey DRM, including support for the base64 extension.
### Changed
- Increase the urllib3 connection pool max size from the default 10 to 16 * 2. This is to accommodate up to 16
byte-ranged segment downloads while still giving enough room for a few other connections.
- The urllib3 connection pool now blocks and waits if it's full. This removes the Connection Pool Limit warnings when
downloading more than one byte-ranged segmented track at a time.
- Moved `--log` from the `dl` command to the entry command to allow logging of more than just the download command.
With this change, the logs now include the initial root logs, including the version number.
- Disable the urllib3 InsecureRequestWarnings as these seem to occur when using HTTP+S proxies when connecting to an
HTTPS URL. While not ideal, we can't solve this problem, and the warning logs are quite annoying.
### Removed
- Remove the `byte_range` parameter from the aria2(c) downloader that was added in v1.3.0 as it turns out it doesn't
actually work. Theoretically it should, but it seems aria2(c) doesn't honor the Range header correctly and fails.
### Fixed
- Fix the JOC check on HLS playlists to check if audio channels are defined first.
- Fix decryption of AES-encrypted segments that are not pre-padded to AES-CBC boundary size (16 bytes).
- Fix the order of segment merging on Linux machines. On Windows, the `pathlib.iterdir()` function is always in order.
However, on Linux, or at least some machines, this was not the case.
- Fix printing of the traceback when a download worker raises an unexpected exception.
- Fix initial creation of the config file if none was created yet.
## [1.3.1] - 2023-02-23
### Fixed
- Fixed a regression where the `track.path` was only updated for `Descriptor.URL` downloads if it had DRM. This caused
downloads of subtitles or DRM-free tracks using the `URL` descriptor to be broken (#33).
- Fixed a regression where `title` and `track` were not passed to the Service's functions for getting Widevine Service
Certificates and Widevine Licenses.
- Corrected the Cookie Path that was logged when adding cookies with `unshackle auth add`.
- The Config data is now defaulted to an empty dictionary when completely empty or non-existent. This fixes a crash if
you try to use `unshackle auth add` without a config file.
## [1.3.0] - 2023-02-22
## Deprecated
- Support for Python 3.8 has been dropped. Support for Windows 7 ended in January 2020.
- Although Python 3.8 is the last version with support for Windows 7, the decision was made to drop support because
the number of affected users would be low.
- You may be interested in <https://github.com/adang1345/PythonWin7>, which has newer installers with patched support.
### Added
- Segmented HLS and DASH downloads now provide useful progress information using TQDM. Previously, aria2c would print
progress information, but it was not very useful for segmented downloads due to how the information was presented.
- Segmented HLS and DASH downloads are now manually multi-threaded in a similar way to aria2c's `--j=16`.
- A class-function was added to the Widevine DRM class to obtain PSSH and KID information from init data by looking for
PSSH and TENC boxes. This is an alternative to the from_track class-function when you only have the init data and not
a track object.
- Aria2c now has the ability to silence progress output and provide extra arguments.
### Changed
- The downloading system for HLS and DASH has been completely reworked. It no longer downloads segments, merges them,
and then decrypts. Instead, it now downloads and decrypts each individual segment. It dynamically switches DRM and
Init Data per-segment where needed, fully supporting multiple EXT-X-KEY, EXT-X-MAP, and EXT-X-DISCONTINUITY tags in
HLS. You can now download DRM-encrypted and DRM-free segments from within the same manifest, as well as manifests
with unique DRM per-segment. None of this was possible with the old method of downloading.
- If a HLS manifest or segment uses an EXT-X-KEY with the method of NONE, it is assumed that the manifest or segment is
DRM-free. This behavior applies even if the manifest or segment has other EXT-X-KEY methods specified, as that would
be a mistake in the manifest.
- HLS now uses the proxy when loading AES-128 DRM as ClearKey objects, which is required for some services. It will
only be used if `Track.needs_proxy` is True.
- The Widevine and ClearKey DRM classes decrypt functions no longer ask for a track. Instead, they ask for an input
file path to which it will decrypt. It will automatically delete the input file and put the decrypted data in its
place.
### Removed
- The AtomicSQL utility was removed because it did not actually assist in making the SQL connections thread-safe. It
helped, but in an almost backwards and over-thought approach.
### Fixed
- The Cacher expiration check now uses your local datetime timestamp over the UTC timestamp, which seems to have fixed
early or late expiration if you are not at exactly UTC+00:00.
- The cookies file path is now checked to exist if supplied with the `--cookies` argument (#30).
- An error is now logged, and execution will end if none of the DRM for a HLS manifest or segment is supported.
- HLS now only loads AES-128 EXT-X-KEY methods as ClearKey DRM because it currently only supports AES-128.
- AtomicSQL was replaced with connection factory systems using thread-safe storage for SQL connections. All Vault SQL
calls are now fully thread-safe.
## [1.2.0] - 2023-02-13
### Deprecation Warning
- This release marks the end of support for Python 3.8.x.
- Although version 1.0.0 was intended to support Python 3.8.x, PyCharm failed to warn about a specific type annotation
incompatibility. As a result, I was not aware that the support was not properly implemented.
- This release adds full support for Python 3.8.x, but it will be the only release with such support.
### Added
- The `dl` command CLI now includes Bitrate Selection options: `-vb/--vbitrate` and `-ab/--abitrate`.
- The `dl` command CLI now includes an Audio Channels Selection option: `-c/--channels`.
- If a download worker fails abruptly, a full traceback will now be printed.
- The aria2c downloader has a new parameter for downloading a specific byte range.
### Changed
- The usage of `Path.with_stem` with `Path.with_suffix` has been simplified to `Path.with_name`.
- When printing audio track information, the assumption that the audio is `2.0ch` has been removed.
- If audio channels were previously set as an integer value, they are no longer transformed as e.g., `6ch` and now
follow the normal behavior of being defined as a float value, e.g., `6.0`.
- Audio channels are now explicitly parsed as float values, therefore parsing of values such as `16/JOC` (HLS) is no
longer supported. The HLS manifest parser now assumes the track to be `5.1ch` if the channels value is set to
`.../JOC`.
### Fixed
- Support for Python `>=3.8.6,<3.9.0` has been fixed.
- The final fallback FPS value is now only obtained from the SegmentBase's timescale value if it exists.
- The FutureWarning that occurred when getting Segment URLs from SegmentTemplate DASH manifests has been removed.
- The HLS manifest parser now correctly sets the audio track's `joc` parameter.
- Some Segmented WEBVTT streams may have included the WEBVTT header data when converting to SubRip SRT. This issue has
been fixed by separating the header from any previous caption before conversion.
- The DASH manifest parser now uses the final redirected URL as the manifest URI (#25).
- File move operations from or to different drives (e.g., importing a cookie from another drive in `auth add`) (#27).
### New Contributors
- [Arias800](https://github.com/Arias800)
- [varyg1001](https://github.com/varyg1001)
## [1.1.0] - 2023-02-07
### Added
- Added utility to change the video range flag between full(pc) and limited(tv).
- Added utility to test decoding of video and audio streams using FFmpeg.
- Added CHANGELOG.md
### Changed
- The services and profiles listed by `auth list` are now sorted alphabetically.
- An explicit error is now logged when adding a Cookie to a Service under a duplicate name.
### Fixed
- Corrected the organization name across the project from `unshackle` to `unshackle-dl` as `unshackle` was taken.
- Fixed startup crash if the config was not yet created or was blank.
- Fixed crash when using the `cfg` command to set a config option on new empty config files.
- Fixed crash when loading key vaults during the `dl` command.
- Fixed crash when using the `auth list` command when you do not have a `Cookies` data directory.
- Fixed crash when adding a Cookie using `auth add` to a Service that has no directory yet.
- Fixed crash when adding a Credential using `auth add` when it's the first ever credential, or first for the Service.
## [1.0.0] - 2023-02-06
Initial public release under the name unshackle.
[3.3.3]: https://github.com/unshackle-dl/unshackle/releases/tag/v3.3.3
[3.3.2]: https://github.com/unshackle-dl/unshackle/releases/tag/v3.3.2
[3.3.1]: https://github.com/unshackle-dl/unshackle/releases/tag/v3.3.1
[3.3.0]: https://github.com/unshackle-dl/unshackle/releases/tag/v3.3.0
[3.2.0]: https://github.com/unshackle-dl/unshackle/releases/tag/v3.2.0
[3.1.0]: https://github.com/unshackle-dl/unshackle/releases/tag/v3.1.0
[3.0.0]: https://github.com/unshackle-dl/unshackle/releases/tag/v3.0.0
[2.2.0]: https://github.com/unshackle-dl/unshackle/releases/tag/v2.2.0
[2.1.0]: https://github.com/unshackle-dl/unshackle/releases/tag/v2.1.0
[2.0.1]: https://github.com/unshackle-dl/unshackle/releases/tag/v2.0.1
[2.0.0]: https://github.com/unshackle-dl/unshackle/releases/tag/v2.0.0
[1.4.0]: https://github.com/unshackle-dl/unshackle/releases/tag/v1.4.0
[1.3.1]: https://github.com/unshackle-dl/unshackle/releases/tag/v1.3.1
[1.3.0]: https://github.com/unshackle-dl/unshackle/releases/tag/v1.3.0
[1.2.0]: https://github.com/unshackle-dl/unshackle/releases/tag/v1.2.0
[1.1.0]: https://github.com/unshackle-dl/unshackle/releases/tag/v1.1.0
[1.0.0]: https://github.com/unshackle-dl/unshackle/releases/tag/v1.0.0
+52
View File
@@ -0,0 +1,52 @@
<p align="center">
<img width="16" height="16" alt="no_encryption" src="https://github.com/user-attachments/assets/6ff88473-0dd2-4bbc-b1ea-c683d5d7a134" /> unshackle
<br/>
<sup><em>Movie, TV, and Music Archival Software</em></sup>
<br/>
<a href="https://discord.gg/mHYyPaCbFK">
<img src="https://img.shields.io/discord/1395571732001325127?label=&logo=discord&logoColor=ffffff&color=7289DA&labelColor=7289DA" alt="Discord">
</a>
</p>
<p align="center">
<a href="#install">Install</a> &nbsp;·&nbsp;
<a href="https://github.com/unshackle-dl/unshackle/wiki">Wiki</a> &nbsp;·&nbsp;
<a href="docs/">Docs</a> &nbsp;·&nbsp;
<a href="https://discord.gg/mHYyPaCbFK">Discord</a>
</p>
---
A modular archival tool for movies, TV, and music. Fork of [Devine](https://github.com/devine-dl/devine/) with DASH/HLS/ISM parsing, Widevine & PlayReady DRM, and a REST API.
<p align="center">
<a href="https://asciinema.org/a/ldMiqYFFTgPAOxW7">
<img src="https://asciinema.org/a/ldMiqYFFTgPAOxW7.svg" alt="unshackle demo" width="700">
</a>
</p>
## Install
```shell
uv tool install git+https://github.com/unshackle-dl/unshackle.git
unshackle --help
```
> [!TIP]
> Prefer `uv run unshackle ...` inside a clone to keep the virtual environment active.
### Requirements
External tools on your `PATH` (recommended versions):
- [Python](https://www.python.org/) - 3.10 - 3.12
- [uv](https://docs.astral.sh/uv/) - ≥ 0.5
- [FFmpeg](https://ffmpeg.org/) - ≥ 6.0
- [MKVToolNix](https://mkvtoolnix.download/) - ≥ 80
- [shaka-packager](https://github.com/shaka-project/shaka-packager/releases/tag/v2.6.1) - 2.6.1
- [Bento4](https://github.com/axiomatic-systems/Bento4) - ≥ 1.6.0-639
- [dovi_tool](https://github.com/quietvoid/dovi_tool) - ≥ 2.1
## License
[GPL-3.0](LICENSE). Do not use unshackle for content you lack the rights to. Keep the core free and open; keep service code private. Be kind.
-237
View File
@@ -1,237 +0,0 @@
/* Avoid breaking parameter names, etc. in table cells. */
.doc-contents td code {
word-break: normal !important;
}
/* No line break before first paragraph of descriptions. */
.doc-md-description,
.doc-md-description>p:first-child {
display: inline;
}
/* No text transformation from Material for MkDocs for H5 headings. */
.md-typeset h5 .doc-object-name {
text-transform: none;
}
/* Max width for docstring sections tables. */
.doc .md-typeset__table,
.doc .md-typeset__table table {
display: table !important;
width: 100%;
}
.doc .md-typeset__table tr {
display: table-row;
}
/* Defaults in Spacy table style. */
.doc-param-default,
.doc-type_param-default {
float: right;
}
/* Parameter headings must be inline, not blocks. */
.doc-heading-parameter,
.doc-heading-type_parameter {
display: inline;
}
/* Default font size for parameter headings. */
.md-typeset .doc-heading-parameter {
font-size: inherit;
}
/* Prefer space on the right, not the left of parameter permalinks. */
.doc-heading-parameter .headerlink,
.doc-heading-type_parameter .headerlink {
margin-left: 0 !important;
margin-right: 0.2rem;
}
/* Backward-compatibility: docstring section titles in bold. */
.doc-section-title {
font-weight: bold;
}
/* Backlinks crumb separator. */
.doc-backlink-crumb {
display: inline-flex;
gap: .2rem;
white-space: nowrap;
align-items: center;
vertical-align: middle;
}
.doc-backlink-crumb:not(:first-child)::before {
background-color: var(--md-default-fg-color--lighter);
content: "";
display: inline;
height: 1rem;
--md-path-icon: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M8.59 16.58 13.17 12 8.59 7.41 10 6l6 6-6 6z"/></svg>');
-webkit-mask-image: var(--md-path-icon);
mask-image: var(--md-path-icon);
width: 1rem;
}
.doc-backlink-crumb.last {
font-weight: bold;
}
/* Symbols in Navigation and ToC. */
:root, :host,
[data-md-color-scheme="default"] {
--doc-symbol-parameter-fg-color: #df50af;
--doc-symbol-type_parameter-fg-color: #df50af;
--doc-symbol-attribute-fg-color: #953800;
--doc-symbol-function-fg-color: #8250df;
--doc-symbol-method-fg-color: #8250df;
--doc-symbol-class-fg-color: #0550ae;
--doc-symbol-type_alias-fg-color: #0550ae;
--doc-symbol-module-fg-color: #5cad0f;
--doc-symbol-parameter-bg-color: #df50af1a;
--doc-symbol-type_parameter-bg-color: #df50af1a;
--doc-symbol-attribute-bg-color: #9538001a;
--doc-symbol-function-bg-color: #8250df1a;
--doc-symbol-method-bg-color: #8250df1a;
--doc-symbol-class-bg-color: #0550ae1a;
--doc-symbol-type_alias-bg-color: #0550ae1a;
--doc-symbol-module-bg-color: #5cad0f1a;
}
[data-md-color-scheme="slate"] {
--doc-symbol-parameter-fg-color: #ffa8cc;
--doc-symbol-type_parameter-fg-color: #ffa8cc;
--doc-symbol-attribute-fg-color: #ffa657;
--doc-symbol-function-fg-color: #d2a8ff;
--doc-symbol-method-fg-color: #d2a8ff;
--doc-symbol-class-fg-color: #79c0ff;
--doc-symbol-type_alias-fg-color: #79c0ff;
--doc-symbol-module-fg-color: #baff79;
--doc-symbol-parameter-bg-color: #ffa8cc1a;
--doc-symbol-type_parameter-bg-color: #ffa8cc1a;
--doc-symbol-attribute-bg-color: #ffa6571a;
--doc-symbol-function-bg-color: #d2a8ff1a;
--doc-symbol-method-bg-color: #d2a8ff1a;
--doc-symbol-class-bg-color: #79c0ff1a;
--doc-symbol-type_alias-bg-color: #79c0ff1a;
--doc-symbol-module-bg-color: #baff791a;
}
code.doc-symbol {
border-radius: .1rem;
font-size: .85em;
padding: 0 .3em;
font-weight: bold;
}
code.doc-symbol-parameter,
a code.doc-symbol-parameter {
color: var(--doc-symbol-parameter-fg-color);
background-color: var(--doc-symbol-parameter-bg-color);
}
code.doc-symbol-parameter::after {
content: "param";
}
code.doc-symbol-type_parameter,
a code.doc-symbol-type_parameter {
color: var(--doc-symbol-type_parameter-fg-color);
background-color: var(--doc-symbol-type_parameter-bg-color);
}
code.doc-symbol-type_parameter::after {
content: "type-param";
}
code.doc-symbol-attribute,
a code.doc-symbol-attribute {
color: var(--doc-symbol-attribute-fg-color);
background-color: var(--doc-symbol-attribute-bg-color);
}
code.doc-symbol-attribute::after {
content: "attr";
}
code.doc-symbol-function,
a code.doc-symbol-function {
color: var(--doc-symbol-function-fg-color);
background-color: var(--doc-symbol-function-bg-color);
}
code.doc-symbol-function::after {
content: "func";
}
code.doc-symbol-method,
a code.doc-symbol-method {
color: var(--doc-symbol-method-fg-color);
background-color: var(--doc-symbol-method-bg-color);
}
code.doc-symbol-method::after {
content: "meth";
}
code.doc-symbol-class,
a code.doc-symbol-class {
color: var(--doc-symbol-class-fg-color);
background-color: var(--doc-symbol-class-bg-color);
}
code.doc-symbol-class::after {
content: "class";
}
code.doc-symbol-type_alias,
a code.doc-symbol-type_alias {
color: var(--doc-symbol-type_alias-fg-color);
background-color: var(--doc-symbol-type_alias-bg-color);
}
code.doc-symbol-type_alias::after {
content: "type";
}
code.doc-symbol-module,
a code.doc-symbol-module {
color: var(--doc-symbol-module-fg-color);
background-color: var(--doc-symbol-module-bg-color);
}
code.doc-symbol-module::after {
content: "mod";
}
.doc-signature .autorefs {
color: inherit;
border-bottom: 1px dotted currentcolor;
}
/* Source code blocks (admonitions). */
:root {
--md-admonition-icon--mkdocstrings-source: url('data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M15.22 4.97a.75.75 0 0 1 1.06 0l6.5 6.5a.75.75 0 0 1 0 1.06l-6.5 6.5a.749.749 0 0 1-1.275-.326.75.75 0 0 1 .215-.734L21.19 12l-5.97-5.97a.75.75 0 0 1 0-1.06m-6.44 0a.75.75 0 0 1 0 1.06L2.81 12l5.97 5.97a.749.749 0 0 1-.326 1.275.75.75 0 0 1-.734-.215l-6.5-6.5a.75.75 0 0 1 0-1.06l6.5-6.5a.75.75 0 0 1 1.06 0"/></svg>')
}
.md-typeset .admonition.mkdocstrings-source,
.md-typeset details.mkdocstrings-source {
border: none;
padding: 0;
}
.md-typeset .admonition.mkdocstrings-source:focus-within,
.md-typeset details.mkdocstrings-source:focus-within {
box-shadow: none;
}
.md-typeset .mkdocstrings-source > .admonition-title,
.md-typeset .mkdocstrings-source > summary {
background-color: inherit;
}
.md-typeset .mkdocstrings-source > .admonition-title::before,
.md-typeset .mkdocstrings-source > summary::before {
background-color: var(--md-default-fg-color);
-webkit-mask-image: var(--md-admonition-icon--mkdocstrings-source);
mask-image: var(--md-admonition-icon--mkdocstrings-source);
}
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="#26a69a"><path d="m800-274-80-80v-206H514l-80-80h166v-80q0-50-34.5-85T481-840q-50 0-84 34.5T363-720v9l-73-73q22-61 75-98.5T481-920q83 0 141 58.5T680-720v80h40q33 0 56.5 23.5T800-560v286Zm20 246-62-62q-11 5-20 7.5T720-80H240q-33 0-56.5-23.5T160-160v-400q0-25 14.5-46t37.5-30L28-820l56-56L876-84l-56 56ZM686-160 539-309q-11 11-25.5 17t-31.5 6q-33 0-56.5-23.5T402-366q0-17 6-31.5t17-25.5L286-560h-46v400h446ZM486-360Zm131-97Z"/></svg>

Before

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-18
View File
@@ -1,18 +0,0 @@
/*!
* Lunr languages, `Danish` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.da=function(){this.pipeline.reset(),this.pipeline.add(e.da.trimmer,e.da.stopWordFilter,e.da.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.da.stemmer))},e.da.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.da.trimmer=e.trimmerSupport.generateTrimmer(e.da.wordCharacters),e.Pipeline.registerFunction(e.da.trimmer,"trimmer-da"),e.da.stemmer=function(){var r=e.stemmerSupport.Among,i=e.stemmerSupport.SnowballProgram,n=new function(){function e(){var e,r=f.cursor+3;if(d=f.limit,0<=r&&r<=f.limit){for(a=r;;){if(e=f.cursor,f.in_grouping(w,97,248)){f.cursor=e;break}if(f.cursor=e,e>=f.limit)return;f.cursor++}for(;!f.out_grouping(w,97,248);){if(f.cursor>=f.limit)return;f.cursor++}d=f.cursor,d<a&&(d=a)}}function n(){var e,r;if(f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(c,32),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del();break;case 2:f.in_grouping_b(p,97,229)&&f.slice_del()}}function t(){var e,r=f.limit-f.cursor;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.find_among_b(l,4)?(f.bra=f.cursor,f.limit_backward=e,f.cursor=f.limit-r,f.cursor>f.limit_backward&&(f.cursor--,f.bra=f.cursor,f.slice_del())):f.limit_backward=e)}function s(){var e,r,i,n=f.limit-f.cursor;if(f.ket=f.cursor,f.eq_s_b(2,"st")&&(f.bra=f.cursor,f.eq_s_b(2,"ig")&&f.slice_del()),f.cursor=f.limit-n,f.cursor>=d&&(r=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,e=f.find_among_b(m,5),f.limit_backward=r,e))switch(f.bra=f.cursor,e){case 1:f.slice_del(),i=f.limit-f.cursor,t(),f.cursor=f.limit-i;break;case 2:f.slice_from("løs")}}function o(){var e;f.cursor>=d&&(e=f.limit_backward,f.limit_backward=d,f.ket=f.cursor,f.out_grouping_b(w,97,248)?(f.bra=f.cursor,u=f.slice_to(u),f.limit_backward=e,f.eq_v_b(u)&&f.slice_del()):f.limit_backward=e)}var a,d,u,c=[new r("hed",-1,1),new r("ethed",0,1),new r("ered",-1,1),new r("e",-1,1),new r("erede",3,1),new r("ende",3,1),new r("erende",5,1),new r("ene",3,1),new r("erne",3,1),new r("ere",3,1),new r("en",-1,1),new r("heden",10,1),new r("eren",10,1),new r("er",-1,1),new r("heder",13,1),new r("erer",13,1),new r("s",-1,2),new r("heds",16,1),new r("es",16,1),new r("endes",18,1),new r("erendes",19,1),new r("enes",18,1),new r("ernes",18,1),new r("eres",18,1),new r("ens",16,1),new r("hedens",24,1),new r("erens",24,1),new r("ers",16,1),new r("ets",16,1),new r("erets",28,1),new r("et",-1,1),new r("eret",30,1)],l=[new r("gd",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("elig",1,1),new r("els",-1,1),new r("løst",-1,2)],w=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],p=[239,254,42,3,0,0,0,0,0,0,0,0,0,0,0,0,16],f=new i;this.setCurrent=function(e){f.setCurrent(e)},this.getCurrent=function(){return f.getCurrent()},this.stem=function(){var r=f.cursor;return e(),f.limit_backward=r,f.cursor=f.limit,n(),f.cursor=f.limit,t(),f.cursor=f.limit,s(),f.cursor=f.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return n.setCurrent(e),n.stem(),n.getCurrent()}):(n.setCurrent(e),n.stem(),n.getCurrent())}}(),e.Pipeline.registerFunction(e.da.stemmer,"stemmer-da"),e.da.stopWordFilter=e.generateStopWordFilter("ad af alle alt anden at blev blive bliver da de dem den denne der deres det dette dig din disse dog du efter eller en end er et for fra ham han hans har havde have hende hendes her hos hun hvad hvis hvor i ikke ind jeg jer jo kunne man mange med meget men mig min mine mit mod ned noget nogle nu når og også om op os over på selv sig sin sine sit skal skulle som sådan thi til ud under var vi vil ville vor være været".split(" ")),e.Pipeline.registerFunction(e.da.stopWordFilter,"stopWordFilter-da")}});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hi=function(){this.pipeline.reset(),this.pipeline.add(e.hi.trimmer,e.hi.stopWordFilter,e.hi.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.hi.stemmer))},e.hi.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿa-zA-Za-zA-Z0-90-9",e.hi.trimmer=e.trimmerSupport.generateTrimmer(e.hi.wordCharacters),e.Pipeline.registerFunction(e.hi.trimmer,"trimmer-hi"),e.hi.stopWordFilter=e.generateStopWordFilter("अत अपना अपनी अपने अभी अंदर आदि आप इत्यादि इन इनका इन्हीं इन्हें इन्हों इस इसका इसकी इसके इसमें इसी इसे उन उनका उनकी उनके उनको उन्हीं उन्हें उन्हों उस उसके उसी उसे एक एवं एस ऐसे और कई कर करता करते करना करने करें कहते कहा का काफ़ी कि कितना किन्हें किन्हों किया किर किस किसी किसे की कुछ कुल के को कोई कौन कौनसा गया घर जब जहाँ जा जितना जिन जिन्हें जिन्हों जिस जिसे जीधर जैसा जैसे जो तक तब तरह तिन तिन्हें तिन्हों तिस तिसे तो था थी थे दबारा दिया दुसरा दूसरे दो द्वारा न नके नहीं ना निहायत नीचे ने पर पहले पूरा पे फिर बनी बही बहुत बाद बाला बिलकुल भी भीतर मगर मानो मे में यदि यह यहाँ यही या यिह ये रखें रहा रहे ऱ्वासा लिए लिये लेकिन व वग़ैरह वर्ग वह वहाँ वहीं वाले वुह वे वो सकता सकते सबसे सभी साथ साबुत साभ सारा से सो संग ही हुआ हुई हुए है हैं हो होता होती होते होना होने".split(" ")),e.hi.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.hi.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var t=i.toString().toLowerCase().replace(/^\s+/,"");return r.cut(t).split("|")},e.Pipeline.registerFunction(e.hi.stemmer,"stemmer-hi"),e.Pipeline.registerFunction(e.hi.stopWordFilter,"stopWordFilter-hi")}});
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.hy=function(){this.pipeline.reset(),this.pipeline.add(e.hy.trimmer,e.hy.stopWordFilter)},e.hy.wordCharacters="[A-Za-z԰-֏ff-ﭏ]",e.hy.trimmer=e.trimmerSupport.generateTrimmer(e.hy.wordCharacters),e.Pipeline.registerFunction(e.hy.trimmer,"trimmer-hy"),e.hy.stopWordFilter=e.generateStopWordFilter("դու և եք էիր էիք հետո նաև նրանք որը վրա է որ պիտի են այս մեջ ն իր ու ի այդ որոնք այն կամ էր մի ես համար այլ իսկ էին ենք հետ ին թ էինք մենք նրա նա դուք եմ էի ըստ որպես ում".split(" ")),e.Pipeline.registerFunction(e.hy.stopWordFilter,"stopWordFilter-hy"),e.hy.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}(),e.Pipeline.registerFunction(e.hy.stemmer,"stemmer-hy")}});
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.ja=function(){this.pipeline.reset(),this.pipeline.add(e.ja.trimmer,e.ja.stopWordFilter,e.ja.stemmer),r?this.tokenizer=e.ja.tokenizer:(e.tokenizer&&(e.tokenizer=e.ja.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.ja.tokenizer))};var t=new e.TinySegmenter;e.ja.tokenizer=function(i){var n,o,s,p,a,u,m,l,c,f;if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t.toLowerCase()):t.toLowerCase()});for(o=i.toString().toLowerCase().replace(/^\s+/,""),n=o.length-1;n>=0;n--)if(/\S/.test(o.charAt(n))){o=o.substring(0,n+1);break}for(a=[],s=o.length,c=0,l=0;c<=s;c++)if(u=o.charAt(c),m=c-l,u.match(/\s/)||c==s){if(m>0)for(p=t.segment(o.slice(l,c)).filter(function(e){return!!e}),f=l,n=0;n<p.length;n++)r?a.push(new e.Token(p[n],{position:[f,p[n].length],index:a.length})):a.push(p[n]),f+=p[n].length;l=c+1}return a},e.ja.stemmer=function(){return function(e){return e}}(),e.Pipeline.registerFunction(e.ja.stemmer,"stemmer-ja"),e.ja.wordCharacters="一二三四五六七八九十百千万億兆一-龠々〆ヵヶぁ-んァ-ヴーア-ン゙a-zA-Za-zA-Z0-90-9",e.ja.trimmer=e.trimmerSupport.generateTrimmer(e.ja.wordCharacters),e.Pipeline.registerFunction(e.ja.trimmer,"trimmer-ja"),e.ja.stopWordFilter=e.generateStopWordFilter("これ それ あれ この その あの ここ そこ あそこ こちら どこ だれ なに なん 何 私 貴方 貴方方 我々 私達 あの人 あのかた 彼女 彼 です あります おります います は が の に を で え から まで より も どの と し それで しかし".split(" ")),e.Pipeline.registerFunction(e.ja.stopWordFilter,"stopWordFilter-ja"),e.jp=e.ja,e.Pipeline.registerFunction(e.jp.stemmer,"stemmer-jp"),e.Pipeline.registerFunction(e.jp.trimmer,"trimmer-jp"),e.Pipeline.registerFunction(e.jp.stopWordFilter,"stopWordFilter-jp")}});
-1
View File
@@ -1 +0,0 @@
module.exports=require("./lunr.ja");
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.kn=function(){this.pipeline.reset(),this.pipeline.add(e.kn.trimmer,e.kn.stopWordFilter,e.kn.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.kn.stemmer))},e.kn.wordCharacters="ಀ-಄ಅ-ಔಕ-ಹಾ-ೌ಼-ಽೕ-ೖೝ-ೞೠ-ೡೢ-ೣ೤೥೦-೯ೱ-ೳ",e.kn.trimmer=e.trimmerSupport.generateTrimmer(e.kn.wordCharacters),e.Pipeline.registerFunction(e.kn.trimmer,"trimmer-kn"),e.kn.stopWordFilter=e.generateStopWordFilter("ಮತ್ತು ಈ ಒಂದು ರಲ್ಲಿ ಹಾಗೂ ಎಂದು ಅಥವಾ ಇದು ರ ಅವರು ಎಂಬ ಮೇಲೆ ಅವರ ತನ್ನ ಆದರೆ ತಮ್ಮ ನಂತರ ಮೂಲಕ ಹೆಚ್ಚು ನ ಆ ಕೆಲವು ಅನೇಕ ಎರಡು ಹಾಗು ಪ್ರಮುಖ ಇದನ್ನು ಇದರ ಸುಮಾರು ಅದರ ಅದು ಮೊದಲ ಬಗ್ಗೆ ನಲ್ಲಿ ರಂದು ಇತರ ಅತ್ಯಂತ ಹೆಚ್ಚಿನ ಸಹ ಸಾಮಾನ್ಯವಾಗಿ ನೇ ಹಲವಾರು ಹೊಸ ದಿ ಕಡಿಮೆ ಯಾವುದೇ ಹೊಂದಿದೆ ದೊಡ್ಡ ಅನ್ನು ಇವರು ಪ್ರಕಾರ ಇದೆ ಮಾತ್ರ ಕೂಡ ಇಲ್ಲಿ ಎಲ್ಲಾ ವಿವಿಧ ಅದನ್ನು ಹಲವು ರಿಂದ ಕೇವಲ ದ ದಕ್ಷಿಣ ಗೆ ಅವನ ಅತಿ ನೆಯ ಬಹಳ ಕೆಲಸ ಎಲ್ಲ ಪ್ರತಿ ಇತ್ಯಾದಿ ಇವು ಬೇರೆ ಹೀಗೆ ನಡುವೆ ಇದಕ್ಕೆ ಎಸ್ ಇವರ ಮೊದಲು ಶ್ರೀ ಮಾಡುವ ಇದರಲ್ಲಿ ರೀತಿಯ ಮಾಡಿದ ಕಾಲ ಅಲ್ಲಿ ಮಾಡಲು ಅದೇ ಈಗ ಅವು ಗಳು ಎ ಎಂಬುದು ಅವನು ಅಂದರೆ ಅವರಿಗೆ ಇರುವ ವಿಶೇಷ ಮುಂದೆ ಅವುಗಳ ಮುಂತಾದ ಮೂಲ ಬಿ ಮೀ ಒಂದೇ ಇನ್ನೂ ಹೆಚ್ಚಾಗಿ ಮಾಡಿ ಅವರನ್ನು ಇದೇ ಯ ರೀತಿಯಲ್ಲಿ ಜೊತೆ ಅದರಲ್ಲಿ ಮಾಡಿದರು ನಡೆದ ಆಗ ಮತ್ತೆ ಪೂರ್ವ ಆತ ಬಂದ ಯಾವ ಒಟ್ಟು ಇತರೆ ಹಿಂದೆ ಪ್ರಮಾಣದ ಗಳನ್ನು ಕುರಿತು ಯು ಆದ್ದರಿಂದ ಅಲ್ಲದೆ ನಗರದ ಮೇಲಿನ ಏಕೆಂದರೆ ರಷ್ಟು ಎಂಬುದನ್ನು ಬಾರಿ ಎಂದರೆ ಹಿಂದಿನ ಆದರೂ ಆದ ಸಂಬಂಧಿಸಿದ ಮತ್ತೊಂದು ಸಿ ಆತನ ".split(" ")),e.kn.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.kn.tokenizer=function(t){if(!arguments.length||null==t||void 0==t)return[];if(Array.isArray(t))return t.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var n=t.toString().toLowerCase().replace(/^\s+/,"");return r.cut(n).split("|")},e.Pipeline.registerFunction(e.kn.stemmer,"stemmer-kn"),e.Pipeline.registerFunction(e.kn.stopWordFilter,"stopWordFilter-kn")}});
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){e.multiLanguage=function(){for(var t=Array.prototype.slice.call(arguments),i=t.join("-"),r="",n=[],s=[],p=0;p<t.length;++p)"en"==t[p]?(r+="\\w",n.unshift(e.stopWordFilter),n.push(e.stemmer),s.push(e.stemmer)):(r+=e[t[p]].wordCharacters,e[t[p]].stopWordFilter&&n.unshift(e[t[p]].stopWordFilter),e[t[p]].stemmer&&(n.push(e[t[p]].stemmer),s.push(e[t[p]].stemmer)));var o=e.trimmerSupport.generateTrimmer(r);return e.Pipeline.registerFunction(o,"lunr-multi-trimmer-"+i),n.unshift(o),function(){this.pipeline.reset(),this.pipeline.add.apply(this.pipeline,n),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add.apply(this.searchPipeline,s))}}}});
File diff suppressed because one or more lines are too long
-18
View File
@@ -1,18 +0,0 @@
/*!
* Lunr languages, `Norwegian` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.no=function(){this.pipeline.reset(),this.pipeline.add(e.no.trimmer,e.no.stopWordFilter,e.no.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.no.stemmer))},e.no.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.no.trimmer=e.trimmerSupport.generateTrimmer(e.no.wordCharacters),e.Pipeline.registerFunction(e.no.trimmer,"trimmer-no"),e.no.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,i=new function(){function e(){var e,r=w.cursor+3;if(a=w.limit,0<=r||r<=w.limit){for(s=r;;){if(e=w.cursor,w.in_grouping(d,97,248)){w.cursor=e;break}if(e>=w.limit)return;w.cursor=e+1}for(;!w.out_grouping(d,97,248);){if(w.cursor>=w.limit)return;w.cursor++}a=w.cursor,a<s&&(a=s)}}function i(){var e,r,n;if(w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(m,29),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:n=w.limit-w.cursor,w.in_grouping_b(c,98,122)?w.slice_del():(w.cursor=w.limit-n,w.eq_s_b(1,"k")&&w.out_grouping_b(d,97,248)&&w.slice_del());break;case 3:w.slice_from("er")}}function t(){var e,r=w.limit-w.cursor;w.cursor>=a&&(e=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,w.find_among_b(u,2)?(w.bra=w.cursor,w.limit_backward=e,w.cursor=w.limit-r,w.cursor>w.limit_backward&&(w.cursor--,w.bra=w.cursor,w.slice_del())):w.limit_backward=e)}function o(){var e,r;w.cursor>=a&&(r=w.limit_backward,w.limit_backward=a,w.ket=w.cursor,e=w.find_among_b(l,11),e?(w.bra=w.cursor,w.limit_backward=r,1==e&&w.slice_del()):w.limit_backward=r)}var s,a,m=[new r("a",-1,1),new r("e",-1,1),new r("ede",1,1),new r("ande",1,1),new r("ende",1,1),new r("ane",1,1),new r("ene",1,1),new r("hetene",6,1),new r("erte",1,3),new r("en",-1,1),new r("heten",9,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",12,1),new r("s",-1,2),new r("as",14,1),new r("es",14,1),new r("edes",16,1),new r("endes",16,1),new r("enes",16,1),new r("hetenes",19,1),new r("ens",14,1),new r("hetens",21,1),new r("ers",14,1),new r("ets",14,1),new r("et",-1,1),new r("het",25,1),new r("ert",-1,3),new r("ast",-1,1)],u=[new r("dt",-1,-1),new r("vt",-1,-1)],l=[new r("leg",-1,1),new r("eleg",0,1),new r("ig",-1,1),new r("eig",2,1),new r("lig",2,1),new r("elig",4,1),new r("els",-1,1),new r("lov",-1,1),new r("elov",7,1),new r("slov",7,1),new r("hetslov",9,1)],d=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,48,0,128],c=[119,125,149,1],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,i(),w.cursor=w.limit,t(),w.cursor=w.limit,o(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return i.setCurrent(e),i.stem(),i.getCurrent()}):(i.setCurrent(e),i.stem(),i.getCurrent())}}(),e.Pipeline.registerFunction(e.no.stemmer,"stemmer-no"),e.no.stopWordFilter=e.generateStopWordFilter("alle at av bare begge ble blei bli blir blitt både båe da de deg dei deim deira deires dem den denne der dere deres det dette di din disse ditt du dykk dykkar då eg ein eit eitt eller elles en enn er et ett etter for fordi fra før ha hadde han hans har hennar henne hennes her hjå ho hoe honom hoss hossen hun hva hvem hver hvilke hvilken hvis hvor hvordan hvorfor i ikke ikkje ikkje ingen ingi inkje inn inni ja jeg kan kom korleis korso kun kunne kva kvar kvarhelst kven kvi kvifor man mange me med medan meg meget mellom men mi min mine mitt mot mykje ned no noe noen noka noko nokon nokor nokre nå når og også om opp oss over på samme seg selv si si sia sidan siden sin sine sitt sjøl skal skulle slik so som som somme somt så sånn til um upp ut uten var vart varte ved vere verte vi vil ville vore vors vort vår være være vært å".split(" ")),e.Pipeline.registerFunction(e.no.stopWordFilter,"stopWordFilter-no")}});
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sa=function(){this.pipeline.reset(),this.pipeline.add(e.sa.trimmer,e.sa.stopWordFilter,e.sa.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sa.stemmer))},e.sa.wordCharacters="ऀ-ःऄ-एऐ-टठ-यर-िी-ॏॐ-य़ॠ-९॰-ॿ꣠-꣱ꣲ-ꣷ꣸-ꣻ꣼-ꣽꣾ-ꣿᆰ0-ᆰ9",e.sa.trimmer=e.trimmerSupport.generateTrimmer(e.sa.wordCharacters),e.Pipeline.registerFunction(e.sa.trimmer,"trimmer-sa"),e.sa.stopWordFilter=e.generateStopWordFilter('तथा अयम्‌ एकम्‌ इत्यस्मिन्‌ तथा तत्‌ वा अयम्‌ इत्यस्य ते आहूत उपरि तेषाम्‌ किन्तु तेषाम्‌ तदा इत्यनेन अधिकः इत्यस्य तत्‌ केचन बहवः द्वि तथा महत्वपूर्णः अयम्‌ अस्य विषये अयं अस्ति तत्‌ प्रथमः विषये इत्युपरि इत्युपरि इतर अधिकतमः अधिकः अपि सामान्यतया ठ इतरेतर नूतनम्‌ द न्यूनम्‌ कश्चित्‌ वा विशालः द सः अस्ति तदनुसारम् तत्र अस्ति केवलम्‌ अपि अत्र सर्वे विविधाः तत्‌ बहवः यतः इदानीम्‌ द दक्षिण इत्यस्मै तस्य उपरि नथ अतीव कार्यम्‌ सर्वे एकैकम्‌ इत्यादि। एते सन्ति उत इत्थम्‌ मध्ये एतदर्थं . स कस्य प्रथमः श्री. करोति अस्मिन् प्रकारः निर्मिता कालः तत्र कर्तुं समान अधुना ते सन्ति स एकः अस्ति सः अर्थात् तेषां कृते . स्थितम् विशेषः अग्रिम तेषाम्‌ समान स्रोतः ख म समान इदानीमपि अधिकतया करोतु ते समान इत्यस्य वीथी सह यस्मिन् कृतवान्‌ धृतः तदा पुनः पूर्वं सः आगतः किम्‌ कुल इतर पुरा मात्रा स विषये उ अतएव अपि नगरस्य उपरि यतः प्रतिशतं कतरः कालः साधनानि भूत तथापि जात सम्बन्धि अन्यत्‌ ग अतः अस्माकं स्वकीयाः अस्माकं इदानीं अन्तः इत्यादयः भवन्तः इत्यादयः एते एताः तस्य अस्य इदम् एते तेषां तेषां तेषां तान् तेषां तेषां तेषां समानः सः एकः च तादृशाः बहवः अन्ये च वदन्ति यत् कियत् कस्मै कस्मै यस्मै यस्मै यस्मै यस्मै न अतिनीचः किन्तु प्रथमं सम्पूर्णतया ततः चिरकालानन्तरं पुस्तकं सम्पूर्णतया अन्तः किन्तु अत्र वा इह इव श्रद्धाय अवशिष्यते परन्तु अन्ये वर्गाः सन्ति ते सन्ति शक्नुवन्ति सर्वे मिलित्वा सर्वे एकत्र"'.split(" ")),e.sa.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var r=e.wordcut;r.init(),e.sa.tokenizer=function(t){if(!arguments.length||null==t||void 0==t)return[];if(Array.isArray(t))return t.map(function(r){return isLunr2?new e.Token(r.toLowerCase()):r.toLowerCase()});var i=t.toString().toLowerCase().replace(/^\s+/,"");return r.cut(i).split("|")},e.Pipeline.registerFunction(e.sa.stemmer,"stemmer-sa"),e.Pipeline.registerFunction(e.sa.stopWordFilter,"stopWordFilter-sa")}});
@@ -1 +0,0 @@
!function(r,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(r.lunr)}(this,function(){return function(r){r.stemmerSupport={Among:function(r,t,i,s){if(this.toCharArray=function(r){for(var t=r.length,i=new Array(t),s=0;s<t;s++)i[s]=r.charCodeAt(s);return i},!r&&""!=r||!t&&0!=t||!i)throw"Bad Among initialisation: s:"+r+", substring_i: "+t+", result: "+i;this.s_size=r.length,this.s=this.toCharArray(r),this.substring_i=t,this.result=i,this.method=s},SnowballProgram:function(){var r;return{bra:0,ket:0,limit:0,cursor:0,limit_backward:0,setCurrent:function(t){r=t,this.cursor=0,this.limit=t.length,this.limit_backward=0,this.bra=this.cursor,this.ket=this.limit},getCurrent:function(){var t=r;return r=null,t},in_grouping:function(t,i,s){if(this.cursor<this.limit){var e=r.charCodeAt(this.cursor);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},in_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e<=s&&e>=i&&(e-=i,t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},out_grouping:function(t,i,s){if(this.cursor<this.limit){var e=r.charCodeAt(this.cursor);if(e>s||e<i)return this.cursor++,!0;if(e-=i,!(t[e>>3]&1<<(7&e)))return this.cursor++,!0}return!1},out_grouping_b:function(t,i,s){if(this.cursor>this.limit_backward){var e=r.charCodeAt(this.cursor-1);if(e>s||e<i)return this.cursor--,!0;if(e-=i,!(t[e>>3]&1<<(7&e)))return this.cursor--,!0}return!1},eq_s:function(t,i){if(this.limit-this.cursor<t)return!1;for(var s=0;s<t;s++)if(r.charCodeAt(this.cursor+s)!=i.charCodeAt(s))return!1;return this.cursor+=t,!0},eq_s_b:function(t,i){if(this.cursor-this.limit_backward<t)return!1;for(var s=0;s<t;s++)if(r.charCodeAt(this.cursor-t+s)!=i.charCodeAt(s))return!1;return this.cursor-=t,!0},find_among:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o<h?o:h,_=t[a],m=l;m<_.s_size;m++){if(n+l==u){f=-1;break}if(f=r.charCodeAt(n+l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n+_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n+_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},find_among_b:function(t,i){for(var s=0,e=i,n=this.cursor,u=this.limit_backward,o=0,h=0,c=!1;;){for(var a=s+(e-s>>1),f=0,l=o<h?o:h,_=t[a],m=_.s_size-1-l;m>=0;m--){if(n-l==u){f=-1;break}if(f=r.charCodeAt(n-1-l)-_.s[m])break;l++}if(f<0?(e=a,h=l):(s=a,o=l),e-s<=1){if(s>0||e==s||c)break;c=!0}}for(;;){var _=t[s];if(o>=_.s_size){if(this.cursor=n-_.s_size,!_.method)return _.result;var b=_.method();if(this.cursor=n-_.s_size,b)return _.result}if((s=_.substring_i)<0)return 0}},replace_s:function(t,i,s){var e=s.length-(i-t),n=r.substring(0,t),u=r.substring(i);return r=n+s+u,this.limit+=e,this.cursor>=i?this.cursor+=e:this.cursor>t&&(this.cursor=t),e},slice_check:function(){if(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>r.length)throw"faulty slice operation"},slice_from:function(r){this.slice_check(),this.replace_s(this.bra,this.ket,r)},slice_del:function(){this.slice_from("")},insert:function(r,t,i){var s=this.replace_s(r,t,i);r<=this.bra&&(this.bra+=s),r<=this.ket&&(this.ket+=s)},slice_to:function(){return this.slice_check(),r.substring(this.bra,this.ket)},eq_v_b:function(r){return this.eq_s_b(r.length,r)}}}},r.trimmerSupport={generateTrimmer:function(r){var t=new RegExp("^[^"+r+"]+"),i=new RegExp("[^"+r+"]+$");return function(r){return"function"==typeof r.update?r.update(function(r){return r.replace(t,"").replace(i,"")}):r.replace(t,"").replace(i,"")}}}}});
-18
View File
@@ -1,18 +0,0 @@
/*!
* Lunr languages, `Swedish` language
* https://github.com/MihaiValentin/lunr-languages
*
* Copyright 2014, Mihai Valentin
* http://www.mozilla.org/MPL/
*/
/*!
* based on
* Snowball JavaScript Library v0.3
* http://code.google.com/p/urim/
* http://snowball.tartarus.org/
*
* Copyright 2010, Oleg Mazko
* http://www.mozilla.org/MPL/
*/
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.sv=function(){this.pipeline.reset(),this.pipeline.add(e.sv.trimmer,e.sv.stopWordFilter,e.sv.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.sv.stemmer))},e.sv.wordCharacters="A-Za-zªºÀ-ÖØ-öø-ʸˠ-ˤᴀ-ᴥᴬ-ᵜᵢ-ᵥᵫ-ᵷᵹ-ᶾḀ-ỿⁱⁿₐ-ₜKÅℲⅎⅠ-ↈⱠ-ⱿꜢ-ꞇꞋ-ꞭꞰ-ꞷꟷ-ꟿꬰ-ꭚꭜ-ꭤff-stA-Za-z",e.sv.trimmer=e.trimmerSupport.generateTrimmer(e.sv.wordCharacters),e.Pipeline.registerFunction(e.sv.trimmer,"trimmer-sv"),e.sv.stemmer=function(){var r=e.stemmerSupport.Among,n=e.stemmerSupport.SnowballProgram,t=new function(){function e(){var e,r=w.cursor+3;if(o=w.limit,0<=r||r<=w.limit){for(a=r;;){if(e=w.cursor,w.in_grouping(l,97,246)){w.cursor=e;break}if(w.cursor=e,w.cursor>=w.limit)return;w.cursor++}for(;!w.out_grouping(l,97,246);){if(w.cursor>=w.limit)return;w.cursor++}o=w.cursor,o<a&&(o=a)}}function t(){var e,r=w.limit_backward;if(w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(u,37),w.limit_backward=r,e))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.in_grouping_b(d,98,121)&&w.slice_del()}}function i(){var e=w.limit_backward;w.cursor>=o&&(w.limit_backward=o,w.cursor=w.limit,w.find_among_b(c,7)&&(w.cursor=w.limit,w.ket=w.cursor,w.cursor>w.limit_backward&&(w.bra=--w.cursor,w.slice_del())),w.limit_backward=e)}function s(){var e,r;if(w.cursor>=o){if(r=w.limit_backward,w.limit_backward=o,w.cursor=w.limit,w.ket=w.cursor,e=w.find_among_b(m,5))switch(w.bra=w.cursor,e){case 1:w.slice_del();break;case 2:w.slice_from("lös");break;case 3:w.slice_from("full")}w.limit_backward=r}}var a,o,u=[new r("a",-1,1),new r("arna",0,1),new r("erna",0,1),new r("heterna",2,1),new r("orna",0,1),new r("ad",-1,1),new r("e",-1,1),new r("ade",6,1),new r("ande",6,1),new r("arne",6,1),new r("are",6,1),new r("aste",6,1),new r("en",-1,1),new r("anden",12,1),new r("aren",12,1),new r("heten",12,1),new r("ern",-1,1),new r("ar",-1,1),new r("er",-1,1),new r("heter",18,1),new r("or",-1,1),new r("s",-1,2),new r("as",21,1),new r("arnas",22,1),new r("ernas",22,1),new r("ornas",22,1),new r("es",21,1),new r("ades",26,1),new r("andes",26,1),new r("ens",21,1),new r("arens",29,1),new r("hetens",29,1),new r("erns",21,1),new r("at",-1,1),new r("andet",-1,1),new r("het",-1,1),new r("ast",-1,1)],c=[new r("dd",-1,-1),new r("gd",-1,-1),new r("nn",-1,-1),new r("dt",-1,-1),new r("gt",-1,-1),new r("kt",-1,-1),new r("tt",-1,-1)],m=[new r("ig",-1,1),new r("lig",0,1),new r("els",-1,1),new r("fullt",-1,3),new r("löst",-1,2)],l=[17,65,16,1,0,0,0,0,0,0,0,0,0,0,0,0,24,0,32],d=[119,127,149],w=new n;this.setCurrent=function(e){w.setCurrent(e)},this.getCurrent=function(){return w.getCurrent()},this.stem=function(){var r=w.cursor;return e(),w.limit_backward=r,w.cursor=w.limit,t(),w.cursor=w.limit,i(),w.cursor=w.limit,s(),!0}};return function(e){return"function"==typeof e.update?e.update(function(e){return t.setCurrent(e),t.stem(),t.getCurrent()}):(t.setCurrent(e),t.stem(),t.getCurrent())}}(),e.Pipeline.registerFunction(e.sv.stemmer,"stemmer-sv"),e.sv.stopWordFilter=e.generateStopWordFilter("alla allt att av blev bli blir blivit de dem den denna deras dess dessa det detta dig din dina ditt du där då efter ej eller en er era ert ett från för ha hade han hans har henne hennes hon honom hur här i icke ingen inom inte jag ju kan kunde man med mellan men mig min mina mitt mot mycket ni nu när någon något några och om oss på samma sedan sig sin sina sitta själv skulle som så sådan sådana sådant till under upp ut utan vad var vara varför varit varje vars vart vem vi vid vilka vilkas vilken vilket vår våra vårt än är åt över".split(" ")),e.Pipeline.registerFunction(e.sv.stopWordFilter,"stopWordFilter-sv")}});
-1
View File
@@ -1 +0,0 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.ta=function(){this.pipeline.reset(),this.pipeline.add(e.ta.trimmer,e.ta.stopWordFilter,e.ta.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.ta.stemmer))},e.ta.wordCharacters="஀-உஊ-ஏஐ-ஙச-ட஠-னப-யர-ஹ஺-ிீ-௉ொ-௏ௐ-௙௚-௟௠-௩௪-௯௰-௹௺-௿a-zA-Za-zA-Z0-90-9",e.ta.trimmer=e.trimmerSupport.generateTrimmer(e.ta.wordCharacters),e.Pipeline.registerFunction(e.ta.trimmer,"trimmer-ta"),e.ta.stopWordFilter=e.generateStopWordFilter("அங்கு அங்கே அது அதை அந்த அவர் அவர்கள் அவள் அவன் அவை ஆக ஆகவே ஆகையால் ஆதலால் ஆதலினால் ஆனாலும் ஆனால் இங்கு இங்கே இது இதை இந்த இப்படி இவர் இவர்கள் இவள் இவன் இவை இவ்வளவு உனக்கு உனது உன் உன்னால் எங்கு எங்கே எது எதை எந்த எப்படி எவர் எவர்கள் எவள் எவன் எவை எவ்வளவு எனக்கு எனது எனவே என் என்ன என்னால் ஏது ஏன் தனது தன்னால் தானே தான் நாங்கள் நாம் நான் நீ நீங்கள்".split(" ")),e.ta.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.ta.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.ta.stemmer,"stemmer-ta"),e.Pipeline.registerFunction(e.ta.stopWordFilter,"stopWordFilter-ta")}});
-1
View File
@@ -1 +0,0 @@
!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():t()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.te=function(){this.pipeline.reset(),this.pipeline.add(e.te.trimmer,e.te.stopWordFilter,e.te.stemmer),this.searchPipeline&&(this.searchPipeline.reset(),this.searchPipeline.add(e.te.stemmer))},e.te.wordCharacters="ఀ-ఄఅ-ఔక-హా-ౌౕ-ౖౘ-ౚౠ-ౡౢ-ౣ౦-౯౸-౿఼ఽ్ౝ౷౤౥",e.te.trimmer=e.trimmerSupport.generateTrimmer(e.te.wordCharacters),e.Pipeline.registerFunction(e.te.trimmer,"trimmer-te"),e.te.stopWordFilter=e.generateStopWordFilter("అందరూ అందుబాటులో అడగండి అడగడం అడ్డంగా అనుగుణంగా అనుమతించు అనుమతిస్తుంది అయితే ఇప్పటికే ఉన్నారు ఎక్కడైనా ఎప్పుడు ఎవరైనా ఎవరో ఏ ఏదైనా ఏమైనప్పటికి ఒక ఒకరు కనిపిస్తాయి కాదు కూడా గా గురించి చుట్టూ చేయగలిగింది తగిన తర్వాత దాదాపు దూరంగా నిజంగా పై ప్రకారం ప్రక్కన మధ్య మరియు మరొక మళ్ళీ మాత్రమే మెచ్చుకో వద్ద వెంట వేరుగా వ్యతిరేకంగా సంబంధం".split(" ")),e.te.stemmer=function(){return function(e){return"function"==typeof e.update?e.update(function(e){return e}):e}}();var t=e.wordcut;t.init(),e.te.tokenizer=function(r){if(!arguments.length||null==r||void 0==r)return[];if(Array.isArray(r))return r.map(function(t){return isLunr2?new e.Token(t.toLowerCase()):t.toLowerCase()});var i=r.toString().toLowerCase().replace(/^\s+/,"");return t.cut(i).split("|")},e.Pipeline.registerFunction(e.te.stemmer,"stemmer-te"),e.Pipeline.registerFunction(e.te.stopWordFilter,"stopWordFilter-te")}});
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var r="2"==e.version[0];e.th=function(){this.pipeline.reset(),this.pipeline.add(e.th.trimmer),r?this.tokenizer=e.th.tokenizer:(e.tokenizer&&(e.tokenizer=e.th.tokenizer),this.tokenizerFn&&(this.tokenizerFn=e.th.tokenizer))},e.th.wordCharacters="[฀-๿]",e.th.trimmer=e.trimmerSupport.generateTrimmer(e.th.wordCharacters),e.Pipeline.registerFunction(e.th.trimmer,"trimmer-th");var t=e.wordcut;t.init(),e.th.tokenizer=function(i){if(!arguments.length||null==i||void 0==i)return[];if(Array.isArray(i))return i.map(function(t){return r?new e.Token(t):t});var n=i.toString().replace(/^\s+/,"");return t.cut(n).split("|")}}});
File diff suppressed because one or more lines are too long
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r():r()(e.lunr)}(this,function(){return function(e){if(void 0===e)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===e.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");e.vi=function(){this.pipeline.reset(),this.pipeline.add(e.vi.stopWordFilter,e.vi.trimmer)},e.vi.wordCharacters="[A-Za-ẓ̀͐́͑̉̃̓ÂâÊêÔôĂ-ăĐ-đƠ-ơƯ-ư]",e.vi.trimmer=e.trimmerSupport.generateTrimmer(e.vi.wordCharacters),e.Pipeline.registerFunction(e.vi.trimmer,"trimmer-vi"),e.vi.stopWordFilter=e.generateStopWordFilter("là cái nhưng mà".split(" "))}});
-1
View File
@@ -1 +0,0 @@
!function(e,r){"function"==typeof define&&define.amd?define(r):"object"==typeof exports?module.exports=r(require("@node-rs/jieba")):r()(e.lunr)}(this,function(e){return function(r,t){if(void 0===r)throw new Error("Lunr is not present. Please include / require Lunr before this script.");if(void 0===r.stemmerSupport)throw new Error("Lunr stemmer support is not present. Please include / require Lunr stemmer support before this script.");var i="2"==r.version[0];r.zh=function(){this.pipeline.reset(),this.pipeline.add(r.zh.trimmer,r.zh.stopWordFilter,r.zh.stemmer),i?this.tokenizer=r.zh.tokenizer:(r.tokenizer&&(r.tokenizer=r.zh.tokenizer),this.tokenizerFn&&(this.tokenizerFn=r.zh.tokenizer))},r.zh.tokenizer=function(n){if(!arguments.length||null==n||void 0==n)return[];if(Array.isArray(n))return n.map(function(e){return i?new r.Token(e.toLowerCase()):e.toLowerCase()});t&&e.load(t);var o=n.toString().trim().toLowerCase(),s=[];e.cut(o,!0).forEach(function(e){s=s.concat(e.split(" "))}),s=s.filter(function(e){return!!e});var u=0;return s.map(function(e,t){if(i){var n=o.indexOf(e,u),s={};return s.position=[n,e.length],s.index=t,u=n,new r.Token(e,s)}return e})},r.zh.wordCharacters="\\w一-龥",r.zh.trimmer=r.trimmerSupport.generateTrimmer(r.zh.wordCharacters),r.Pipeline.registerFunction(r.zh.trimmer,"trimmer-zh"),r.zh.stemmer=function(){return function(e){return e}}(),r.Pipeline.registerFunction(r.zh.stemmer,"stemmer-zh"),r.zh.stopWordFilter=r.generateStopWordFilter("的 一 不 在 人 有 是 为 為 以 于 於 上 他 而 后 後 之 来 來 及 了 因 下 可 到 由 这 這 与 與 也 此 但 并 並 个 個 其 已 无 無 小 我 们 們 起 最 再 今 去 好 只 又 或 很 亦 某 把 那 你 乃 它 吧 被 比 别 趁 当 當 从 從 得 打 凡 儿 兒 尔 爾 该 該 各 给 給 跟 和 何 还 還 即 几 幾 既 看 据 據 距 靠 啦 另 么 麽 每 嘛 拿 哪 您 凭 憑 且 却 卻 让 讓 仍 啥 如 若 使 谁 誰 虽 雖 随 隨 同 所 她 哇 嗡 往 些 向 沿 哟 喲 用 咱 则 則 怎 曾 至 致 着 著 诸 諸 自".split(" ")),r.Pipeline.registerFunction(r.zh.stopWordFilter,"stopWordFilter-zh")}});
-206
View File
@@ -1,206 +0,0 @@
/**
* export the module via AMD, CommonJS or as a browser global
* Export code from https://github.com/umdjs/umd/blob/master/returnExports.js
*/
;(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(factory)
} else if (typeof exports === 'object') {
/**
* Node. Does not work with strict CommonJS, but
* only CommonJS-like environments that support module.exports,
* like Node.
*/
module.exports = factory()
} else {
// Browser globals (root is window)
factory()(root.lunr);
}
}(this, function () {
/**
* Just return a value to define the module export.
* This example returns an object, but the module
* can return a function as the exported value.
*/
return function(lunr) {
// TinySegmenter 0.1 -- Super compact Japanese tokenizer in Javascript
// (c) 2008 Taku Kudo <taku@chasen.org>
// TinySegmenter is freely distributable under the terms of a new BSD licence.
// For details, see http://chasen.org/~taku/software/TinySegmenter/LICENCE.txt
function TinySegmenter() {
var patterns = {
"[一二三四五六七八九十百千万億兆]":"M",
"[一-龠々〆ヵヶ]":"H",
"[ぁ-ん]":"I",
"[ァ-ヴーア-ン゙ー]":"K",
"[a-zA-Za-zA-Z]":"A",
"[0-90-9]":"N"
}
this.chartype_ = [];
for (var i in patterns) {
var regexp = new RegExp(i);
this.chartype_.push([regexp, patterns[i]]);
}
this.BIAS__ = -332
this.BC1__ = {"HH":6,"II":2461,"KH":406,"OH":-1378};
this.BC2__ = {"AA":-3267,"AI":2744,"AN":-878,"HH":-4070,"HM":-1711,"HN":4012,"HO":3761,"IA":1327,"IH":-1184,"II":-1332,"IK":1721,"IO":5492,"KI":3831,"KK":-8741,"MH":-3132,"MK":3334,"OO":-2920};
this.BC3__ = {"HH":996,"HI":626,"HK":-721,"HN":-1307,"HO":-836,"IH":-301,"KK":2762,"MK":1079,"MM":4034,"OA":-1652,"OH":266};
this.BP1__ = {"BB":295,"OB":304,"OO":-125,"UB":352};
this.BP2__ = {"BO":60,"OO":-1762};
this.BQ1__ = {"BHH":1150,"BHM":1521,"BII":-1158,"BIM":886,"BMH":1208,"BNH":449,"BOH":-91,"BOO":-2597,"OHI":451,"OIH":-296,"OKA":1851,"OKH":-1020,"OKK":904,"OOO":2965};
this.BQ2__ = {"BHH":118,"BHI":-1159,"BHM":466,"BIH":-919,"BKK":-1720,"BKO":864,"OHH":-1139,"OHM":-181,"OIH":153,"UHI":-1146};
this.BQ3__ = {"BHH":-792,"BHI":2664,"BII":-299,"BKI":419,"BMH":937,"BMM":8335,"BNN":998,"BOH":775,"OHH":2174,"OHM":439,"OII":280,"OKH":1798,"OKI":-793,"OKO":-2242,"OMH":-2402,"OOO":11699};
this.BQ4__ = {"BHH":-3895,"BIH":3761,"BII":-4654,"BIK":1348,"BKK":-1806,"BMI":-3385,"BOO":-12396,"OAH":926,"OHH":266,"OHK":-2036,"ONN":-973};
this.BW1__ = {",と":660,",同":727,"B1あ":1404,"B1同":542,"、と":660,"、同":727,"」と":1682,"あっ":1505,"いう":1743,"いっ":-2055,"いる":672,"うし":-4817,"うん":665,"から":3472,"がら":600,"こう":-790,"こと":2083,"こん":-1262,"さら":-4143,"さん":4573,"した":2641,"して":1104,"すで":-3399,"そこ":1977,"それ":-871,"たち":1122,"ため":601,"った":3463,"つい":-802,"てい":805,"てき":1249,"でき":1127,"です":3445,"では":844,"とい":-4915,"とみ":1922,"どこ":3887,"ない":5713,"なっ":3015,"など":7379,"なん":-1113,"にし":2468,"には":1498,"にも":1671,"に対":-912,"の一":-501,"の中":741,"ませ":2448,"まで":1711,"まま":2600,"まる":-2155,"やむ":-1947,"よっ":-2565,"れた":2369,"れで":-913,"をし":1860,"を見":731,"亡く":-1886,"京都":2558,"取り":-2784,"大き":-2604,"大阪":1497,"平方":-2314,"引き":-1336,"日本":-195,"本当":-2423,"毎日":-2113,"目指":-724,"B1あ":1404,"B1同":542,"」と":1682};
this.BW2__ = {"..":-11822,"11":-669,"――":-5730,"−−":-13175,"いう":-1609,"うか":2490,"かし":-1350,"かも":-602,"から":-7194,"かれ":4612,"がい":853,"がら":-3198,"きた":1941,"くな":-1597,"こと":-8392,"この":-4193,"させ":4533,"され":13168,"さん":-3977,"しい":-1819,"しか":-545,"した":5078,"して":972,"しな":939,"その":-3744,"たい":-1253,"たた":-662,"ただ":-3857,"たち":-786,"たと":1224,"たは":-939,"った":4589,"って":1647,"っと":-2094,"てい":6144,"てき":3640,"てく":2551,"ては":-3110,"ても":-3065,"でい":2666,"でき":-1528,"でし":-3828,"です":-4761,"でも":-4203,"とい":1890,"とこ":-1746,"とと":-2279,"との":720,"とみ":5168,"とも":-3941,"ない":-2488,"なが":-1313,"など":-6509,"なの":2614,"なん":3099,"にお":-1615,"にし":2748,"にな":2454,"によ":-7236,"に対":-14943,"に従":-4688,"に関":-11388,"のか":2093,"ので":-7059,"のに":-6041,"のの":-6125,"はい":1073,"はが":-1033,"はず":-2532,"ばれ":1813,"まし":-1316,"まで":-6621,"まれ":5409,"めて":-3153,"もい":2230,"もの":-10713,"らか":-944,"らし":-1611,"らに":-1897,"りし":651,"りま":1620,"れた":4270,"れて":849,"れば":4114,"ろう":6067,"われ":7901,"を通":-11877,"んだ":728,"んな":-4115,"一人":602,"一方":-1375,"一日":970,"一部":-1051,"上が":-4479,"会社":-1116,"出て":2163,"分の":-7758,"同党":970,"同日":-913,"大阪":-2471,"委員":-1250,"少な":-1050,"年度":-8669,"年間":-1626,"府県":-2363,"手権":-1982,"新聞":-4066,"日新":-722,"日本":-7068,"日米":3372,"曜日":-601,"朝鮮":-2355,"本人":-2697,"東京":-1543,"然と":-1384,"社会":-1276,"立て":-990,"第に":-1612,"米国":-4268,"11":-669};
this.BW3__ = {"あた":-2194,"あり":719,"ある":3846,"い.":-1185,"い。":-1185,"いい":5308,"いえ":2079,"いく":3029,"いた":2056,"いっ":1883,"いる":5600,"いわ":1527,"うち":1117,"うと":4798,"えと":1454,"か.":2857,"か。":2857,"かけ":-743,"かっ":-4098,"かに":-669,"から":6520,"かり":-2670,"が,":1816,"が、":1816,"がき":-4855,"がけ":-1127,"がっ":-913,"がら":-4977,"がり":-2064,"きた":1645,"けど":1374,"こと":7397,"この":1542,"ころ":-2757,"さい":-714,"さを":976,"し,":1557,"し、":1557,"しい":-3714,"した":3562,"して":1449,"しな":2608,"しま":1200,"す.":-1310,"す。":-1310,"する":6521,"ず,":3426,"ず、":3426,"ずに":841,"そう":428,"た.":8875,"た。":8875,"たい":-594,"たの":812,"たり":-1183,"たる":-853,"だ.":4098,"だ。":4098,"だっ":1004,"った":-4748,"って":300,"てい":6240,"てお":855,"ても":302,"です":1437,"でに":-1482,"では":2295,"とう":-1387,"とし":2266,"との":541,"とも":-3543,"どう":4664,"ない":1796,"なく":-903,"など":2135,"に,":-1021,"に、":-1021,"にし":1771,"にな":1906,"には":2644,"の,":-724,"の、":-724,"の子":-1000,"は,":1337,"は、":1337,"べき":2181,"まし":1113,"ます":6943,"まっ":-1549,"まで":6154,"まれ":-793,"らし":1479,"られ":6820,"るる":3818,"れ,":854,"れ、":854,"れた":1850,"れて":1375,"れば":-3246,"れる":1091,"われ":-605,"んだ":606,"んで":798,"カ月":990,"会議":860,"入り":1232,"大会":2217,"始め":1681,"市":965,"新聞":-5055,"日,":974,"日、":974,"社会":2024,"カ月":990};
this.TC1__ = {"AAA":1093,"HHH":1029,"HHM":580,"HII":998,"HOH":-390,"HOM":-331,"IHI":1169,"IOH":-142,"IOI":-1015,"IOM":467,"MMH":187,"OOI":-1832};
this.TC2__ = {"HHO":2088,"HII":-1023,"HMM":-1154,"IHI":-1965,"KKH":703,"OII":-2649};
this.TC3__ = {"AAA":-294,"HHH":346,"HHI":-341,"HII":-1088,"HIK":731,"HOH":-1486,"IHH":128,"IHI":-3041,"IHO":-1935,"IIH":-825,"IIM":-1035,"IOI":-542,"KHH":-1216,"KKA":491,"KKH":-1217,"KOK":-1009,"MHH":-2694,"MHM":-457,"MHO":123,"MMH":-471,"NNH":-1689,"NNO":662,"OHO":-3393};
this.TC4__ = {"HHH":-203,"HHI":1344,"HHK":365,"HHM":-122,"HHN":182,"HHO":669,"HIH":804,"HII":679,"HOH":446,"IHH":695,"IHO":-2324,"IIH":321,"III":1497,"IIO":656,"IOO":54,"KAK":4845,"KKA":3386,"KKK":3065,"MHH":-405,"MHI":201,"MMH":-241,"MMM":661,"MOM":841};
this.TQ1__ = {"BHHH":-227,"BHHI":316,"BHIH":-132,"BIHH":60,"BIII":1595,"BNHH":-744,"BOHH":225,"BOOO":-908,"OAKK":482,"OHHH":281,"OHIH":249,"OIHI":200,"OIIH":-68};
this.TQ2__ = {"BIHH":-1401,"BIII":-1033,"BKAK":-543,"BOOO":-5591};
this.TQ3__ = {"BHHH":478,"BHHM":-1073,"BHIH":222,"BHII":-504,"BIIH":-116,"BIII":-105,"BMHI":-863,"BMHM":-464,"BOMH":620,"OHHH":346,"OHHI":1729,"OHII":997,"OHMH":481,"OIHH":623,"OIIH":1344,"OKAK":2792,"OKHH":587,"OKKA":679,"OOHH":110,"OOII":-685};
this.TQ4__ = {"BHHH":-721,"BHHM":-3604,"BHII":-966,"BIIH":-607,"BIII":-2181,"OAAA":-2763,"OAKK":180,"OHHH":-294,"OHHI":2446,"OHHO":480,"OHIH":-1573,"OIHH":1935,"OIHI":-493,"OIIH":626,"OIII":-4007,"OKAK":-8156};
this.TW1__ = {"につい":-4681,"東京都":2026};
this.TW2__ = {"ある程":-2049,"いった":-1256,"ころが":-2434,"しょう":3873,"その後":-4430,"だって":-1049,"ていた":1833,"として":-4657,"ともに":-4517,"もので":1882,"一気に":-792,"初めて":-1512,"同時に":-8097,"大きな":-1255,"対して":-2721,"社会党":-3216};
this.TW3__ = {"いただ":-1734,"してい":1314,"として":-4314,"につい":-5483,"にとっ":-5989,"に当た":-6247,"ので,":-727,"ので、":-727,"のもの":-600,"れから":-3752,"十二月":-2287};
this.TW4__ = {"いう.":8576,"いう。":8576,"からな":-2348,"してい":2958,"たが,":1516,"たが、":1516,"ている":1538,"という":1349,"ました":5543,"ません":1097,"ようと":-4258,"よると":5865};
this.UC1__ = {"A":484,"K":93,"M":645,"O":-505};
this.UC2__ = {"A":819,"H":1059,"I":409,"M":3987,"N":5775,"O":646};
this.UC3__ = {"A":-1370,"I":2311};
this.UC4__ = {"A":-2643,"H":1809,"I":-1032,"K":-3450,"M":3565,"N":3876,"O":6646};
this.UC5__ = {"H":313,"I":-1238,"K":-799,"M":539,"O":-831};
this.UC6__ = {"H":-506,"I":-253,"K":87,"M":247,"O":-387};
this.UP1__ = {"O":-214};
this.UP2__ = {"B":69,"O":935};
this.UP3__ = {"B":189};
this.UQ1__ = {"BH":21,"BI":-12,"BK":-99,"BN":142,"BO":-56,"OH":-95,"OI":477,"OK":410,"OO":-2422};
this.UQ2__ = {"BH":216,"BI":113,"OK":1759};
this.UQ3__ = {"BA":-479,"BH":42,"BI":1913,"BK":-7198,"BM":3160,"BN":6427,"BO":14761,"OI":-827,"ON":-3212};
this.UW1__ = {",":156,"、":156,"「":-463,"あ":-941,"う":-127,"が":-553,"き":121,"こ":505,"で":-201,"と":-547,"ど":-123,"に":-789,"の":-185,"は":-847,"も":-466,"や":-470,"よ":182,"ら":-292,"り":208,"れ":169,"を":-446,"ん":-137,"・":-135,"主":-402,"京":-268,"区":-912,"午":871,"国":-460,"大":561,"委":729,"市":-411,"日":-141,"理":361,"生":-408,"県":-386,"都":-718,"「":-463,"・":-135};
this.UW2__ = {",":-829,"、":-829,"〇":892,"「":-645,"」":3145,"あ":-538,"い":505,"う":134,"お":-502,"か":1454,"が":-856,"く":-412,"こ":1141,"さ":878,"ざ":540,"し":1529,"す":-675,"せ":300,"そ":-1011,"た":188,"だ":1837,"つ":-949,"て":-291,"で":-268,"と":-981,"ど":1273,"な":1063,"に":-1764,"の":130,"は":-409,"ひ":-1273,"べ":1261,"ま":600,"も":-1263,"や":-402,"よ":1639,"り":-579,"る":-694,"れ":571,"を":-2516,"ん":2095,"ア":-587,"カ":306,"キ":568,"ッ":831,"三":-758,"不":-2150,"世":-302,"中":-968,"主":-861,"事":492,"人":-123,"会":978,"保":362,"入":548,"初":-3025,"副":-1566,"北":-3414,"区":-422,"大":-1769,"天":-865,"太":-483,"子":-1519,"学":760,"実":1023,"小":-2009,"市":-813,"年":-1060,"強":1067,"手":-1519,"揺":-1033,"政":1522,"文":-1355,"新":-1682,"日":-1815,"明":-1462,"最":-630,"朝":-1843,"本":-1650,"東":-931,"果":-665,"次":-2378,"民":-180,"気":-1740,"理":752,"発":529,"目":-1584,"相":-242,"県":-1165,"立":-763,"第":810,"米":509,"自":-1353,"行":838,"西":-744,"見":-3874,"調":1010,"議":1198,"込":3041,"開":1758,"間":-1257,"「":-645,"」":3145,"ッ":831,"ア":-587,"カ":306,"キ":568};
this.UW3__ = {",":4889,"1":-800,"−":-1723,"、":4889,"々":-2311,"〇":5827,"」":2670,"〓":-3573,"あ":-2696,"い":1006,"う":2342,"え":1983,"お":-4864,"か":-1163,"が":3271,"く":1004,"け":388,"げ":401,"こ":-3552,"ご":-3116,"さ":-1058,"し":-395,"す":584,"せ":3685,"そ":-5228,"た":842,"ち":-521,"っ":-1444,"つ":-1081,"て":6167,"で":2318,"と":1691,"ど":-899,"な":-2788,"に":2745,"の":4056,"は":4555,"ひ":-2171,"ふ":-1798,"へ":1199,"ほ":-5516,"ま":-4384,"み":-120,"め":1205,"も":2323,"や":-788,"よ":-202,"ら":727,"り":649,"る":5905,"れ":2773,"わ":-1207,"を":6620,"ん":-518,"ア":551,"グ":1319,"ス":874,"ッ":-1350,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278,"・":-3794,"一":-1619,"下":-1759,"世":-2087,"両":3815,"中":653,"主":-758,"予":-1193,"二":974,"人":2742,"今":792,"他":1889,"以":-1368,"低":811,"何":4265,"作":-361,"保":-2439,"元":4858,"党":3593,"全":1574,"公":-3030,"六":755,"共":-1880,"円":5807,"再":3095,"分":457,"初":2475,"別":1129,"前":2286,"副":4437,"力":365,"動":-949,"務":-1872,"化":1327,"北":-1038,"区":4646,"千":-2309,"午":-783,"協":-1006,"口":483,"右":1233,"各":3588,"合":-241,"同":3906,"和":-837,"員":4513,"国":642,"型":1389,"場":1219,"外":-241,"妻":2016,"学":-1356,"安":-423,"実":-1008,"家":1078,"小":-513,"少":-3102,"州":1155,"市":3197,"平":-1804,"年":2416,"広":-1030,"府":1605,"度":1452,"建":-2352,"当":-3885,"得":1905,"思":-1291,"性":1822,"戸":-488,"指":-3973,"政":-2013,"教":-1479,"数":3222,"文":-1489,"新":1764,"日":2099,"旧":5792,"昨":-661,"時":-1248,"曜":-951,"最":-937,"月":4125,"期":360,"李":3094,"村":364,"東":-805,"核":5156,"森":2438,"業":484,"氏":2613,"民":-1694,"決":-1073,"法":1868,"海":-495,"無":979,"物":461,"特":-3850,"生":-273,"用":914,"町":1215,"的":7313,"直":-1835,"省":792,"県":6293,"知":-1528,"私":4231,"税":401,"立":-960,"第":1201,"米":7767,"系":3066,"約":3663,"級":1384,"統":-4229,"総":1163,"線":1255,"者":6457,"能":725,"自":-2869,"英":785,"見":1044,"調":-562,"財":-733,"費":1777,"車":1835,"軍":1375,"込":-1504,"通":-1136,"選":-681,"郎":1026,"郡":4404,"部":1200,"金":2163,"長":421,"開":-1432,"間":1302,"関":-1282,"雨":2009,"電":-1045,"非":2066,"駅":1620,"1":-800,"」":2670,"・":-3794,"ッ":-1350,"ア":551,"グ":1319,"ス":874,"ト":521,"ム":1109,"ル":1591,"ロ":2201,"ン":278};
this.UW4__ = {",":3930,".":3508,"―":-4841,"、":3930,"。":3508,"〇":4999,"「":1895,"」":3798,"〓":-5156,"あ":4752,"い":-3435,"う":-640,"え":-2514,"お":2405,"か":530,"が":6006,"き":-4482,"ぎ":-3821,"く":-3788,"け":-4376,"げ":-4734,"こ":2255,"ご":1979,"さ":2864,"し":-843,"じ":-2506,"す":-731,"ず":1251,"せ":181,"そ":4091,"た":5034,"だ":5408,"ち":-3654,"っ":-5882,"つ":-1659,"て":3994,"で":7410,"と":4547,"な":5433,"に":6499,"ぬ":1853,"ね":1413,"の":7396,"は":8578,"ば":1940,"ひ":4249,"び":-4134,"ふ":1345,"へ":6665,"べ":-744,"ほ":1464,"ま":1051,"み":-2082,"む":-882,"め":-5046,"も":4169,"ゃ":-2666,"や":2795,"ょ":-1544,"よ":3351,"ら":-2922,"り":-9726,"る":-14896,"れ":-2613,"ろ":-4570,"わ":-1783,"を":13150,"ん":-2352,"カ":2145,"コ":1789,"セ":1287,"ッ":-724,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637,"・":-4371,"ー":-11870,"一":-2069,"中":2210,"予":782,"事":-190,"井":-1768,"人":1036,"以":544,"会":950,"体":-1286,"作":530,"側":4292,"先":601,"党":-2006,"共":-1212,"内":584,"円":788,"初":1347,"前":1623,"副":3879,"力":-302,"動":-740,"務":-2715,"化":776,"区":4517,"協":1013,"参":1555,"合":-1834,"和":-681,"員":-910,"器":-851,"回":1500,"国":-619,"園":-1200,"地":866,"場":-1410,"塁":-2094,"士":-1413,"多":1067,"大":571,"子":-4802,"学":-1397,"定":-1057,"寺":-809,"小":1910,"屋":-1328,"山":-1500,"島":-2056,"川":-2667,"市":2771,"年":374,"庁":-4556,"後":456,"性":553,"感":916,"所":-1566,"支":856,"改":787,"政":2182,"教":704,"文":522,"方":-856,"日":1798,"時":1829,"最":845,"月":-9066,"木":-485,"来":-442,"校":-360,"業":-1043,"氏":5388,"民":-2716,"気":-910,"沢":-939,"済":-543,"物":-735,"率":672,"球":-1267,"生":-1286,"産":-1101,"田":-2900,"町":1826,"的":2586,"目":922,"省":-3485,"県":2997,"空":-867,"立":-2112,"第":788,"米":2937,"系":786,"約":2171,"経":1146,"統":-1169,"総":940,"線":-994,"署":749,"者":2145,"能":-730,"般":-852,"行":-792,"規":792,"警":-1184,"議":-244,"谷":-1000,"賞":730,"車":-1481,"軍":1158,"輪":-1433,"込":-3370,"近":929,"道":-1291,"選":2596,"郎":-4866,"都":1192,"野":-1100,"銀":-2213,"長":357,"間":-2344,"院":-2297,"際":-2604,"電":-878,"領":-1659,"題":-792,"館":-1984,"首":1749,"高":2120,"「":1895,"」":3798,"・":-4371,"ッ":-724,"ー":-11870,"カ":2145,"コ":1789,"セ":1287,"ト":-403,"メ":-1635,"ラ":-881,"リ":-541,"ル":-856,"ン":-3637};
this.UW5__ = {",":465,".":-299,"1":-514,"E2":-32768,"]":-2762,"、":465,"。":-299,"「":363,"あ":1655,"い":331,"う":-503,"え":1199,"お":527,"か":647,"が":-421,"き":1624,"ぎ":1971,"く":312,"げ":-983,"さ":-1537,"し":-1371,"す":-852,"だ":-1186,"ち":1093,"っ":52,"つ":921,"て":-18,"で":-850,"と":-127,"ど":1682,"な":-787,"に":-1224,"の":-635,"は":-578,"べ":1001,"み":502,"め":865,"ゃ":3350,"ょ":854,"り":-208,"る":429,"れ":504,"わ":419,"を":-1264,"ん":327,"イ":241,"ル":451,"ン":-343,"中":-871,"京":722,"会":-1153,"党":-654,"務":3519,"区":-901,"告":848,"員":2104,"大":-1296,"学":-548,"定":1785,"嵐":-1304,"市":-2991,"席":921,"年":1763,"思":872,"所":-814,"挙":1618,"新":-1682,"日":218,"月":-4353,"査":932,"格":1356,"機":-1508,"氏":-1347,"田":240,"町":-3912,"的":-3149,"相":1319,"省":-1052,"県":-4003,"研":-997,"社":-278,"空":-813,"統":1955,"者":-2233,"表":663,"語":-1073,"議":1219,"選":-1018,"郎":-368,"長":786,"間":1191,"題":2368,"館":-689,"1":-514,"E2":-32768,"「":363,"イ":241,"ル":451,"ン":-343};
this.UW6__ = {",":227,".":808,"1":-270,"E1":306,"、":227,"。":808,"あ":-307,"う":189,"か":241,"が":-73,"く":-121,"こ":-200,"じ":1782,"す":383,"た":-428,"っ":573,"て":-1014,"で":101,"と":-105,"な":-253,"に":-149,"の":-417,"は":-236,"も":-206,"り":187,"る":-135,"を":195,"ル":-673,"ン":-496,"一":-277,"中":201,"件":-800,"会":624,"前":302,"区":1792,"員":-1212,"委":798,"学":-960,"市":887,"広":-695,"後":535,"業":-697,"相":753,"社":-507,"福":974,"空":-822,"者":1811,"連":463,"郎":1082,"1":-270,"E1":306,"ル":-673,"ン":-496};
return this;
}
TinySegmenter.prototype.ctype_ = function(str) {
for (var i in this.chartype_) {
if (str.match(this.chartype_[i][0])) {
return this.chartype_[i][1];
}
}
return "O";
}
TinySegmenter.prototype.ts_ = function(v) {
if (v) { return v; }
return 0;
}
TinySegmenter.prototype.segment = function(input) {
if (input == null || input == undefined || input == "") {
return [];
}
var result = [];
var seg = ["B3","B2","B1"];
var ctype = ["O","O","O"];
var o = input.split("");
for (i = 0; i < o.length; ++i) {
seg.push(o[i]);
ctype.push(this.ctype_(o[i]))
}
seg.push("E1");
seg.push("E2");
seg.push("E3");
ctype.push("O");
ctype.push("O");
ctype.push("O");
var word = seg[3];
var p1 = "U";
var p2 = "U";
var p3 = "U";
for (var i = 4; i < seg.length - 3; ++i) {
var score = this.BIAS__;
var w1 = seg[i-3];
var w2 = seg[i-2];
var w3 = seg[i-1];
var w4 = seg[i];
var w5 = seg[i+1];
var w6 = seg[i+2];
var c1 = ctype[i-3];
var c2 = ctype[i-2];
var c3 = ctype[i-1];
var c4 = ctype[i];
var c5 = ctype[i+1];
var c6 = ctype[i+2];
score += this.ts_(this.UP1__[p1]);
score += this.ts_(this.UP2__[p2]);
score += this.ts_(this.UP3__[p3]);
score += this.ts_(this.BP1__[p1 + p2]);
score += this.ts_(this.BP2__[p2 + p3]);
score += this.ts_(this.UW1__[w1]);
score += this.ts_(this.UW2__[w2]);
score += this.ts_(this.UW3__[w3]);
score += this.ts_(this.UW4__[w4]);
score += this.ts_(this.UW5__[w5]);
score += this.ts_(this.UW6__[w6]);
score += this.ts_(this.BW1__[w2 + w3]);
score += this.ts_(this.BW2__[w3 + w4]);
score += this.ts_(this.BW3__[w4 + w5]);
score += this.ts_(this.TW1__[w1 + w2 + w3]);
score += this.ts_(this.TW2__[w2 + w3 + w4]);
score += this.ts_(this.TW3__[w3 + w4 + w5]);
score += this.ts_(this.TW4__[w4 + w5 + w6]);
score += this.ts_(this.UC1__[c1]);
score += this.ts_(this.UC2__[c2]);
score += this.ts_(this.UC3__[c3]);
score += this.ts_(this.UC4__[c4]);
score += this.ts_(this.UC5__[c5]);
score += this.ts_(this.UC6__[c6]);
score += this.ts_(this.BC1__[c2 + c3]);
score += this.ts_(this.BC2__[c3 + c4]);
score += this.ts_(this.BC3__[c4 + c5]);
score += this.ts_(this.TC1__[c1 + c2 + c3]);
score += this.ts_(this.TC2__[c2 + c3 + c4]);
score += this.ts_(this.TC3__[c3 + c4 + c5]);
score += this.ts_(this.TC4__[c4 + c5 + c6]);
// score += this.ts_(this.TC5__[c4 + c5 + c6]);
score += this.ts_(this.UQ1__[p1 + c1]);
score += this.ts_(this.UQ2__[p2 + c2]);
score += this.ts_(this.UQ3__[p3 + c3]);
score += this.ts_(this.BQ1__[p2 + c2 + c3]);
score += this.ts_(this.BQ2__[p2 + c3 + c4]);
score += this.ts_(this.BQ3__[p3 + c2 + c3]);
score += this.ts_(this.BQ4__[p3 + c3 + c4]);
score += this.ts_(this.TQ1__[p2 + c1 + c2 + c3]);
score += this.ts_(this.TQ2__[p2 + c2 + c3 + c4]);
score += this.ts_(this.TQ3__[p3 + c1 + c2 + c3]);
score += this.ts_(this.TQ4__[p3 + c2 + c3 + c4]);
var p = "O";
if (score > 0) {
result.push(word);
word = "";
p = "B";
}
p1 = p2;
p2 = p3;
p3 = p;
word += seg[i];
}
result.push(word);
return result;
}
lunr.TinySegmenter = TinySegmenter;
};
}));
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
{"version":3,"sources":["src/templates/assets/stylesheets/palette/_scheme.scss","../../../../src/templates/assets/stylesheets/palette.scss","src/templates/assets/stylesheets/palette/_accent.scss","src/templates/assets/stylesheets/palette/_primary.scss","src/templates/assets/stylesheets/utilities/_break.scss"],"names":[],"mappings":"AA2BA,cAGE,6BAME,sDAAA,CACA,6DAAA,CACA,+DAAA,CACA,gEAAA,CACA,mDAAA,CACA,6DAAA,CACA,+DAAA,CACA,gEAAA,CAGA,mDAAA,CACA,gDAAA,CACA,yDAAA,CACA,4DAAA,CAGA,0BAAA,CACA,mCAAA,CAGA,iCAAA,CACA,kCAAA,CACA,mCAAA,CACA,mCAAA,CACA,kCAAA,CACA,iCAAA,CACA,+CAAA,CACA,6DAAA,CACA,gEAAA,CACA,4DAAA,CACA,4DAAA,CACA,6DAAA,CAGA,6CAAA,CAGA,+CAAA,CAGA,uDAAA,CACA,6DAAA,CACA,2DAAA,CAGA,iCAAA,CAGA,yDAAA,CACA,iEAAA,CAGA,mDAAA,CACA,mDAAA,CAGA,qDAAA,CACA,uDAAA,CAGA,8DAAA,CAKA,8DAAA,CAKA,0DAAA,CAzEA,iBCiBF,CD6DE,kHAEE,YC3DJ,CDkFE,yDACE,4BChFJ,CD+EE,2DACE,4BC7EJ,CD4EE,gEACE,4BC1EJ,CDyEE,2DACE,4BCvEJ,CDsEE,yDACE,4BCpEJ,CDmEE,0DACE,4BCjEJ,CDgEE,gEACE,4BC9DJ,CD6DE,0DACE,4BC3DJ,CD0DE,2OACE,4BC/CJ,CDsDA,+FAGE,iCCpDF,CACF,CCjDE,2BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD6CN,CCvDE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDoDN,CC9DE,8BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD2DN,CCrEE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDkEN,CC5EE,8BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDyEN,CCnFE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDgFN,CC1FE,kCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDuFN,CCjGE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD8FN,CCxGE,4BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDqGN,CC/GE,6BACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCD4GN,CCtHE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDmHN,CC7HE,4BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCD6HN,CCpIE,8BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDoIN,CC3IE,6BACE,yBAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCD2IN,CClJE,8BACE,4BAAA,CACA,2CAAA,CAIE,8BAAA,CACA,qCDkJN,CCzJE,mCACE,4BAAA,CACA,2CAAA,CAOE,yBAAA,CACA,qCDsJN,CE3JE,4BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwJN,CEnKE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgKN,CE3KE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwKN,CEnLE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgLN,CE3LE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwLN,CEnME,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgMN,CE3ME,mCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwMN,CEnNE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgNN,CE3NE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwNN,CEnOE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgON,CE3OE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwON,CEnPE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFmPN,CE3PE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCF2PN,CEnQE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCFmQN,CE3QE,+BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAIE,+BAAA,CACA,sCF2QN,CEnRE,oCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFgRN,CE3RE,8BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCFwRN,CEnSE,6BACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCAAA,CAKA,4BF4RN,CE5SE,kCACE,6BAAA,CACA,oCAAA,CACA,mCAAA,CAOE,0BAAA,CACA,sCAAA,CAKA,4BFqSN,CEtRE,sEACE,4BFyRJ,CE1RE,+DACE,4BF6RJ,CE9RE,iEACE,4BFiSJ,CElSE,gEACE,4BFqSJ,CEtSE,iEACE,4BFySJ,CEhSA,8BACE,mDAAA,CACA,4DAAA,CACA,0DAAA,CACA,oDAAA,CACA,2DAAA,CAGA,4BFiSF,CE9RE,yCACE,+BFgSJ,CE7RI,kDAEE,0CAAA,CACA,sCAAA,CAFA,mCFiSN,CG7MI,mCD1EA,+CACE,8CF0RJ,CEvRI,qDACE,8CFyRN,CEpRE,iEACE,mCFsRJ,CACF,CGxNI,sCDvDA,uCACE,oCFkRJ,CACF,CEzQA,8BACE,kDAAA,CACA,4DAAA,CACA,wDAAA,CACA,oDAAA,CACA,6DAAA,CAGA,4BF0QF,CEvQE,yCACE,+BFyQJ,CEtQI,kDAEE,0CAAA,CACA,sCAAA,CAFA,mCF0QN,CEnQE,yCACE,6CFqQJ,CG9NI,0CDhCA,8CACE,gDFiQJ,CACF,CGnOI,0CDvBA,iFACE,6CF6PJ,CACF,CG3PI,sCDKA,uCACE,6CFyPJ,CACF","file":"palette.css"}
+77
View File
@@ -0,0 +1,77 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration
[changelog]
header = """
# Changelog\n
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
This changelog is automatically generated using [git-cliff](https://git-cliff.org).\n
"""
body = """
{% if version -%}
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
{% else -%}
## [Unreleased]
{% endif -%}
{% for group, commits in commits
| filter(attribute="merge_commit", value=false)
| group_by(attribute="group") %}
### {{ group | striptags | trim | upper_first }}
{% for commit in commits | unique(attribute="message") %}
- {% if commit.scope %}*{{ commit.scope }}*: {% endif %}\
{% if commit.breaking %}[**breaking**] {% endif %}\
{{ commit.message | upper_first }}\
{% endfor %}
{% endfor %}\n
"""
footer = """
{% for release in releases -%}
{% if release.version -%}
{% if release.previous.version -%}
[{{ release.version | trim_start_matches(pat="v") }}]: \
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\
/compare/{{ release.previous.version }}..{{ release.version }}
{% endif -%}
{% else -%}
[unreleased]: https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\
/compare/{{ release.previous.version }}..HEAD
{% endif -%}
{% endfor -%}
"""
trim = true
postprocessors = []
[git]
conventional_commits = true
filter_unconventional = true
split_commits = false
commit_preprocessors = [
# Strip emoji (both UTF-8 and :shortcode: styles) from commit messages
{ pattern = ' *(:\w+:|[\p{Emoji_Presentation}\p{Extended_Pictographic}]\x{FE0F}?\x{200D}?) *', replace = "" },
# Remove trailing PR/issue numbers like (#123) from commit messages
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
]
commit_parsers = [
{ message = "^feat", group = "<!-- 0 -->Features" },
{ message = "^fix", group = "<!-- 1 -->Bug Fixes" },
{ message = "^revert", group = "<!-- 2 -->Reverts" },
{ message = "^docs", group = "<!-- 3 -->Documentation" },
{ message = "^perf", group = "<!-- 4 -->Performance Improvements" },
{ message = "^refactor", group = "<!-- 5 -->Changes" },
{ message = "^style", skip = true },
{ message = "^test", skip = true },
{ message = "^chore\\(release\\)", skip = true },
{ message = "^chore\\(deps.*\\)", skip = true },
{ message = "^chore\\(pr\\)", skip = true },
{ message = "^chore", group = "<!-- 6 -->Maintenance" },
{ message = "^ci", skip = true },
{ message = "^build", group = "<!-- 7 -->Builds" },
{ body = ".*security", group = "<!-- 8 -->Security" },
]
protect_breaking_commits = true
filter_commits = true
topo_order = false
sort_commits = "oldest"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+232
View File
@@ -0,0 +1,232 @@
# Advanced & System Configuration
This document covers advanced features, debugging, and system-level configuration options.
## serve (dict)
Configuration for the integrated server that provides CDM endpoints (Widevine/PlayReady) and a REST API for remote downloading.
Start the server with:
```bash
unshackle serve # Default: localhost:8786
unshackle serve -h 0.0.0.0 -p 8888 # Listen on all interfaces
unshackle serve --no-key # Disable authentication
unshackle serve --api-only # REST API only, no CDM endpoints
unshackle serve --remote-only # Only expose remote service session endpoints
```
### CLI Options
| Option | Default | Description |
| --- | --- | --- |
| `-h, --host` | `127.0.0.1` | Host to serve from |
| `-p, --port` | `8786` | Port to serve from |
| `--caddy` | `false` | Also serve with Caddy reverse-proxy for HTTPS |
| `--api-only` | `false` | Serve only the REST API, disable CDM endpoints |
| `--no-widevine` | `false` | Disable Widevine CDM endpoints |
| `--no-playready` | `false` | Disable PlayReady CDM endpoints |
| `--no-key` | `false` | Disable API key authentication (allows all requests) |
| `--debug-api` | `false` | Include tracebacks and stderr in API error responses |
| `--debug` | `false` | Enable debug logging for API operations |
| `--remote-only` | `false` | Only expose remote service session endpoints (health, services, search, session) |
### Configuration
- `api_secret` - Secret key for REST API authentication. Required unless `--no-key` is used. All API requests must include this key via the `X-Secret-Key` header.
- `compression_level` - Compression level for API payloads (manifests, cache, cookies). `0`=off, `1`=fast, `6`=balanced, `9`=max. Default: `1`.
- `session_ttl` - Session inactivity timeout in seconds. Each request resets the timer. Default: `300`.
- `max_sessions` - Maximum concurrent sessions before the oldest is evicted. Default: `100`.
- `services` - Optional global service allowlist. Only these service tags are exposed. If omitted, all services are available.
- `devices` - List of Widevine device files (.wvd). If not specified, auto-populated from the WVDs directory.
- `playready_devices` - List of PlayReady device files (.prd). If not specified, auto-populated from the PRDs directory.
- `users` - Dictionary mapping user secret keys to their access configuration:
- `devices` - List of Widevine devices this user can access
- `playready_devices` - List of PlayReady devices this user can access
- `username` - Internal logging name for the user (not visible to users)
- `services` - Optional per-user service allowlist. Effective access is the intersection of global and per-user allowlists.
#### Server-side `dl` defaults
Any key accepted by `/api/download` (see `docs/API.md`) can also be declared directly under `serve:` and the REST API will treat it as a default. Per-request bodies still win. Use this to raise concurrency, force `best_available`, etc. without each client repeating the values:
```yaml
serve:
api_secret: "..."
users: { ... }
downloads: 4 # parallel tracks per job
workers: 16 # threads per track
best_available: true
no_proxy_download: false
```
Layering: built-in defaults < `serve.*` overrides < service-specific defaults < request body.
For example,
```yaml
serve:
api_secret: "your-secret-key-here"
compression_level: 1
session_ttl: 300
max_sessions: 100
# services: # global allowlist (optional)
# - EXAMPLE1
# - EXAMPLE2
users:
secret_key_for_jane: # 32bit hex recommended, case-sensitive
devices: # list of allowed Widevine devices for this user
- generic_nexus_4464_l3
playready_devices: # list of allowed PlayReady devices for this user
- my_playready_device
username: jane # only for internal logging, users will not see this name
# services: # per-user allowlist (optional)
# - EXAMPLE1
secret_key_for_james:
devices:
- generic_nexus_4464_l3
username: james
# devices can be manually specified by path if you don't want to add it to
# unshackle's WVDs directory for whatever reason
# devices:
# - 'C:\Users\john\Devices\test_devices_001.wvd'
# playready_devices:
# - '/path/to/device.prd'
```
### REST API
When the server is running, interactive API documentation is available at:
- **Swagger UI**: `http://localhost:8786/api/docs/`
See [API.md](API.md) for full REST API documentation with endpoints, parameters, and examples.
---
## max_concurrent_downloads (int)
Maximum number of `/api/download` jobs the serve queue manager will execute in parallel. Each job runs the full `dl` pipeline (authenticate, fetch tracks, decrypt, mux) in its own worker subprocess. This is independent of `serve.downloads`, which controls parallel tracks **inside** a single job. Default: `2`.
```yaml
max_concurrent_downloads: 4
```
---
## download_job_retention_hours (int)
How long completed, failed, or cancelled download jobs remain queryable via `/api/download/jobs/{job_id}` before the periodic cleanup loop drops them. Default: `24`.
```yaml
download_job_retention_hours: 48
```
---
## debug (bool)
Enables comprehensive debug logging. Default: `false`
When enabled (either via config or the `-d`/`--debug` CLI flag):
- Sets console log level to DEBUG for verbose output
- Creates JSON Lines (`.jsonl`) debug log files with structured logging
- Logs detailed information about sessions, service configuration, DRM operations, and errors with full stack traces
For example,
```yaml
debug: true
```
---
## debug_keys (bool)
Controls whether actual decryption keys (CEKs) are included in debug logs. Default: `false`
When enabled:
- Content encryption keys are logged in debug output
- Only affects `content_key` and `key` fields (the actual CEKs)
- Key metadata (`kid`, `keys_count`, `key_id`) is always logged regardless of this setting
- Passwords, tokens, cookies, and session tokens remain redacted even when enabled
For example,
```yaml
debug_keys: true
```
---
## set_terminal_bg (bool)
Controls whether unshackle should set the terminal background color. Default: `false`
For example,
```yaml
set_terminal_bg: true
```
---
## update_checks (bool)
Check for updates from the GitHub repository on startup. Default: `true`.
---
## update_check_interval (int)
How often to check for updates, in hours. Default: `24`.
---
## title_cache_enabled (bool)
Enable or disable title metadata caching globally. Default: `true`.
---
## title_cache_time (int)
Title cache duration in seconds. Default: `1800` (30 minutes).
---
## title_cache_max_retention (int)
Maximum cache retention in seconds, used as fallback when the upstream API fails. Default: `86400` (24 hours).
---
## unicode_filenames (bool)
When `false`, replaces non-ASCII characters in output filenames with ASCII equivalents. Default: `false`.
---
## ipinfo_api_key (str)
Optional ipinfo.io token. When set, unshackle uses the ipinfo.io Lite endpoint for IP/geolocation lookups instead of the unauthenticated fallback.
---
## tmdb_api_key (str)
Optional TMDB API key, used for metadata enrichment and IMDb/TMDb tagging.
---
## simkl_client_id (str)
Optional Simkl client ID for metadata lookups.
---
## decrypt_labs_api_key (str)
Optional Decrypt Labs API key, used by services that integrate with the service.
---
+781
View File
@@ -0,0 +1,781 @@
# REST API Documentation
The unshackle REST API allows you to control downloads, search services, drive remote downloads from a thin client, and (optionally) co-host the pywidevine/pyplayready CDM. Start the server with `unshackle serve` and access the interactive Swagger UI at `http://localhost:8786/api/docs/`.
The server is built on **aiohttp** (not FastAPI). Implementation lives in `unshackle/commands/serve.py` and `unshackle/core/api/` (`routes.py`, `handlers.py`, `session_store.py`, `input_bridge.py`, `download_manager.py`, `download_worker.py`).
## Quick Start
```bash
# Start the server (no authentication)
unshackle serve --no-key
# Start with authentication (api_secret in unshackle.yaml)
unshackle serve
# Serve only the REST API (no pywidevine/pyplayready CDM)
unshackle serve --api-only
# Serve only the remote-dl session endpoints (CORS/Cloudflare friendly)
unshackle serve --remote-only
# Disable just one CDM
unshackle serve --no-widevine
unshackle serve --no-playready
# Verbose error responses (tracebacks/stderr in JSON)
unshackle serve --debug-api
```
`serve` flags:
| Flag | Description |
| --- | --- |
| `-h, --host` | Bind host (default `127.0.0.1`) |
| `-p, --port` | Bind port (default `8786`) |
| `--caddy` | Also launch Caddy using `Caddyfile` next to the unshackle config |
| `--api-only` | REST API only; skip the bundled pywidevine/pyplayready CDM endpoints |
| `--no-widevine` | Disable Widevine CDM endpoints |
| `--no-playready` | Disable PlayReady CDM endpoints |
| `--no-key` | Disable API key authentication entirely |
| `--debug-api` | Include tracebacks/stderr in error responses |
| `--debug` | Enable DEBUG-level logging for API operations |
| `--remote-only` | Expose only `/api/health`, `/api/services`, `/api/search`, and `/api/session/*` (implies `--api-only`) |
## Authentication
When `api_secret` is set in `unshackle.yaml`, all API requests require the **`X-Secret-Key`** header. There is no query-parameter fallback. `/api/health` is always reachable without authentication. `--no-key` disables auth entirely (not recommended for public-facing servers).
```yaml
# unshackle.yaml
serve:
api_secret: "your-master-secret" # falls back to global users map below
remote_only: false # also toggleable via --remote-only
services: ["EXAMPLE1", "EXAMPLE2"] # optional global service allowlist
users:
user-secret-1:
username: alice
devices: ["my_widevine_l3"] # Widevine WVD names this user may use
playready_devices: ["my_pr_sl2000"] # PlayReady PRD names; defaults to [] (no access)
services: ["EXAMPLE1"] # optional per-user allowlist (intersected with global)
user-secret-2:
username: bob
devices: []
playready_devices: []
```
### Service allowlists
`config.serve.services` is the global allowlist; `users.<key>.services` further narrows it per key. The effective set is the intersection. Endpoints affected: `/api/services`, `/api/search`, `/api/list-titles`, `/api/list-tracks`, `/api/download`, and all `/api/session/*` routes.
### CDM access (server-side decryption)
There is no separate "tier" flag. Whether the server can return KID:KEY for a session-mode download depends solely on the device lists configured for the calling user key:
- Empty `devices` and `playready_devices` -> server can only proxy CDM challenges; the client must run its own CDM and parse the license.
- Populated lists -> the client may set `mode: "server_cdm"` on `/api/session/{id}/license` and receive `{ "keys": { "<track_id>": { "<KID>": "<KEY>" } } }` instead of raw license bytes.
Per-service CDM type can be pinned via `config.cdm` (`widevine`/`playready`) or per-service `cdm_type`; otherwise the server picks the type the user has devices for.
### Server-side `dl` defaults
Any flag accepted by `/api/download` (see the table below) can be declared under `serve:` in `unshackle.yaml` and the API will apply it as a default. Request-body values still win. Useful for raising concurrency without changing every client call:
```yaml
serve:
api_secret: "..."
users: { ... }
downloads: 4 # parallel tracks per download job
workers: 16 # threads per track segment fetch
best_available: true
no_proxy_download: false
```
Layering order: built-in defaults < `serve.*` overrides < service-specific click defaults < request body.
---
## Endpoint Map
Standard endpoints (suppressed in `--remote-only` mode are marked R):
| Method | Path | R |
| --- | --- | :-: |
| GET | `/api/health` | ok |
| GET | `/api/services` | ok |
| POST | `/api/search` | ok |
| POST | `/api/list-titles` | hidden |
| POST | `/api/list-tracks` | hidden |
| POST | `/api/download` | hidden |
| GET | `/api/download/jobs` | hidden |
| GET | `/api/download/jobs/{job_id}` | hidden |
| DELETE | `/api/download/jobs/{job_id}` | hidden |
| POST | `/api/session/create` | ok |
| GET | `/api/session/{session_id}` | ok |
| DELETE | `/api/session/{session_id}` | ok |
| GET | `/api/session/{session_id}/titles` | ok |
| POST | `/api/session/{session_id}/tracks` | ok |
| POST | `/api/session/{session_id}/segments` | ok |
| POST | `/api/session/{session_id}/license` | ok |
| GET | `/api/session/{session_id}/prompt` | ok |
| POST | `/api/session/{session_id}/prompt` | ok |
CDM endpoints (`/{wvd}/...`, `/playready/{prd}/...`) are exposed unless `--api-only` / `--remote-only` / `--no-widevine` / `--no-playready` is set, and use pywidevine / pyplayready's own auth scheme.
---
## Endpoints
### GET /api/health
Health check with version and update information. Always reachable without auth.
```bash
curl http://localhost:8786/api/health
```
```json
{
"status": "ok",
"version": "4.0.0",
"update_check": {
"update_available": false,
"current_version": "4.0.0",
"latest_version": null
}
}
```
---
### GET /api/services
List all available streaming services (filtered by the effective allowlist for the caller).
```bash
curl -H "X-Secret-Key: $KEY" http://localhost:8786/api/services
```
Returns `{"services": [...]}`. Each entry has `tag`, `aliases`, `geofence`, `title_regex`, `url` (from `cli.short_help`), `help` (full docstring), and `cli_params` describing the service-level Click parameters.
---
### POST /api/search
Search for titles from a streaming service.
**Required parameters:**
| Parameter | Type | Description |
| --- | --- | --- |
| `service` | string | Service tag |
| `query` | string | Search query |
**Optional parameters:**
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `profile` | string | `null` | Profile for credentials/cookies |
| `proxy` | string | `null` | Proxy URI or country code |
| `no_proxy` | boolean | `false` | Disable all proxy use |
```bash
curl -X POST http://localhost:8786/api/search \
-H "X-Secret-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"service": "EXAMPLE1", "query": "example show"}'
```
```json
{
"results": [
{
"id": "abc123def456",
"title": "Example Show",
"description": null,
"label": "TV Show",
"url": "https://example.com/show/abc123def456"
}
],
"count": 1
}
```
---
### POST /api/list-titles
Get available titles (seasons/episodes/movies) for a service and title ID. Disabled in `--remote-only` mode.
**Required parameters:**
| Parameter | Type | Description |
| --- | --- | --- |
| `service` | string | Service tag |
| `title_id` | string | Title ID or URL |
```bash
curl -X POST http://localhost:8786/api/list-titles \
-H "X-Secret-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"service": "EXAMPLE1", "title_id": "abc123def456"}'
```
---
### POST /api/list-tracks
Get video, audio, and subtitle tracks for a title. Disabled in `--remote-only` mode.
**Required parameters:**
| Parameter | Type | Description |
| --- | --- | --- |
| `service` | string | Service tag |
| `title_id` | string | Title ID or URL |
**Optional parameters:**
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `wanted` | array | all | Episode filter (e.g., `["S01E01"]`) |
| `profile` | string | `null` | Profile for credentials/cookies |
| `proxy` | string | `null` | Proxy URI or country code |
| `no_proxy` | boolean | `false` | Disable all proxy use |
Returns video, audio, and subtitle tracks with codec, bitrate, resolution, language, and DRM information.
---
### POST /api/download
Start a download job. Returns immediately with a job ID (HTTP 202). Disabled in `--remote-only` mode.
**Required parameters:**
| Parameter | Type | Description |
| --- | --- | --- |
| `service` | string | Service tag |
| `title_id` | string | Title ID or URL |
**Quality and codec parameters:**
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `quality` | array[int] | best | Resolution(s) (e.g., `[1080, 2160]`) |
| `vcodec` | string or array | any | Video codec(s): `H264`, `H265`/`HEVC`, `VP9`, `AV1`, `VC1`, `VP8` |
| `acodec` | string or array | any | Audio codec(s): `AAC`, `AC3`, `EC3`, `AC4`, `OPUS`, `FLAC`, `ALAC`, `DTS`, `OGG` |
| `vbitrate` | int | highest | Video bitrate in kbps |
| `abitrate` | int | highest | Audio bitrate in kbps |
| `range` | array[string] | `["SDR"]` | Color range(s): `SDR`, `HDR10`, `HDR10+`, `HLG`, `DV`, `HYBRID` |
| `channels` | float | any | Audio channels (e.g., `5.1`, `7.1`) |
| `no_atmos` | boolean | `false` | Exclude Dolby Atmos tracks |
| `split_audio` | boolean | `null` | Create separate output per audio codec |
| `sub_format` | string | `null` | Output subtitle format: `SRT`, `VTT`, `ASS`, `SSA`, `TTML` |
**Episode selection:**
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `wanted` | array[string] | all | Episodes (e.g., `["S01E01", "S01E02-S01E05"]`) |
| `latest_episode` | boolean | `false` | Download only the most recent episode |
**Language parameters:**
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `lang` | array[string] | `["orig"]` | Language for video and audio (`orig` = original) |
| `v_lang` | array[string] | `[]` | Language override for video tracks only |
| `a_lang` | array[string] | `[]` | Language override for audio tracks only |
| `s_lang` | array[string] | `["all"]` | Language for subtitles |
| `require_subs` | array[string] | `[]` | Required subtitle languages (skip if missing) |
| `forced_subs` | boolean | `false` | Include forced subtitle tracks |
| `exact_lang` | boolean | `false` | Exact language matching (no variants) |
**Track selection:**
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `video_only` | boolean | `false` | Only download video tracks |
| `audio_only` | boolean | `false` | Only download audio tracks |
| `subs_only` | boolean | `false` | Only download subtitle tracks |
| `chapters_only` | boolean | `false` | Only download chapters |
| `no_video` | boolean | `false` | Skip video tracks |
| `no_audio` | boolean | `false` | Skip audio tracks |
| `no_subs` | boolean | `false` | Skip subtitle tracks |
| `no_chapters` | boolean | `false` | Skip chapters |
| `audio_description` | boolean | `false` | Include audio description tracks |
**Output and tagging:**
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `tag` | string | `null` | Override group tag |
| `repack` | boolean | `false` | Add REPACK tag to filename |
| `tmdb_id` | int | `null` | Use specific TMDB ID for tagging |
| `imdb_id` | string | `null` | Use specific IMDB ID (e.g., `tt1375666`) |
| `animeapi_id` | string | `null` | Anime database ID via AnimeAPI (e.g., `mal:12345`) |
| `enrich` | boolean | `false` | Override show title and year from external source |
| `no_folder` | boolean | `false` | Disable folder creation for TV shows |
| `no_source` | boolean | `false` | Remove source tag from filename |
| `no_mux` | boolean | `false` | Do not mux tracks into container |
| `output_dir` | string | `null` | Override output directory |
**Download behavior:**
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `profile` | string | `null` | Profile for credentials/cookies |
| `proxy` | string | `null` | Proxy URI or country code |
| `no_proxy` | boolean | `false` | Disable all proxy use |
| `no_proxy_download` | boolean | `false` | Bypass proxy for segment downloads only. Manifest, license, and auth still use proxy |
| `workers` | int | `null` | Max threads per track download |
| `downloads` | int | `1` | Concurrent track downloads |
| `slow` | boolean or string | `null` | Add randomized delay between titles. `true` = 60-120s, or `"MIN-MAX"` string (e.g., `"20-40"`). Min must be >= 20 |
| `best_available` | boolean | `false` | Continue if requested quality unavailable |
| `worst` | boolean | `false` | Select the lowest bitrate track within the specified quality. Requires `quality` |
| `skip_dl` | boolean | `false` | Skip download, only get decryption keys |
| `export` | boolean | `false` | Export manifest, track URLs, keys, and subtitles to JSON in the exports directory |
| `cdm_only` | boolean | `null` | Only use CDM (`true`) or only vaults (`false`) |
| `no_cache` | boolean | `false` | Bypass title cache |
| `reset_cache` | boolean | `false` | Clear title cache before fetching |
**Example:**
```bash
curl -X POST http://localhost:8786/api/download \
-H "X-Secret-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{
"service": "EXAMPLE1",
"title_id": "abc123def456",
"wanted": ["S01E01"],
"quality": [1080, 2160],
"vcodec": ["H265"],
"acodec": ["AAC", "EC3"],
"range": ["HDR10", "SDR"],
"split_audio": true,
"lang": ["en"]
}'
```
```json
{
"job_id": "504db959-80b0-446c-a764-7924b761d613",
"status": "queued",
"created_time": "2026-02-27T18:00:00.000000"
}
```
---
### GET /api/download/jobs
List all download jobs with optional filtering and sorting. Disabled in `--remote-only` mode.
**Query parameters:**
| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `status` | string | all | Filter by status: `queued`, `downloading`, `completed`, `failed`, `cancelled` |
| `service` | string | all | Filter by service tag |
| `sort_by` | string | `created_time` | Sort field: `created_time`, `started_time`, `completed_time`, `progress`, `status`, `service` |
| `sort_order` | string | `desc` | Sort order: `asc`, `desc` |
```bash
curl -H "X-Secret-Key: $KEY" "http://localhost:8786/api/download/jobs?status=completed"
```
---
### GET /api/download/jobs/{job_id}
Get detailed information about a specific download job including progress, parameters, and error details.
```json
{
"job_id": "504db959-80b0-446c-a764-7924b761d613",
"status": "completed",
"created_time": "2026-02-27T18:00:00.000000",
"service": "EXAMPLE1",
"title_id": "abc123def456",
"progress": 100.0,
"parameters": { },
"started_time": "2026-02-27T18:00:01.000000",
"completed_time": "2026-02-27T18:00:15.000000",
"output_files": [],
"error_message": null,
"error_details": null
}
```
---
### DELETE /api/download/jobs/{job_id}
Cancel a queued or running download job. Returns 400 if the job has already terminated.
---
## Remote Service Sessions
These endpoints back the `RemoteService` adapter in `unshackle/core/remote_service.py`. They let a thin `dl` client (or any consumer) authenticate against a service on the server, fetch titles/tracks/manifests, and either proxy CDM challenges or have the server resolve KID:KEY directly. The `dl` command's `RemoteService` adapter replaces the old `remote_dl` command. These endpoints are the only `/api/*` routes available in `--remote-only` mode (in addition to `health`, `services`, and `search`).
### POST /api/session/create
Authenticate against a service and open a session. Body fields:
| Field | Type | Description |
| --- | --- | --- |
| `service` | string | Service tag (required) |
| `title_id` | string | Title ID/URL (required) |
| `credentials` | object | Auth credentials forwarded to `Service.authenticate` |
| `cookies` | string | Cookie blob (Netscape or JSON) |
| `proxy` | string | Proxy URI or country code |
| `no_proxy` | bool | Force-disable proxies |
| `profile` | string | Profile name |
| `cache` | object | Optional pre-warmed title cache payload |
If the service requires interactive input during authentication, poll `GET /api/session/{id}/prompt` and submit responses via `POST /api/session/{id}/prompt` until status is `authenticated`.
**Request:**
```json
{
"service": "EXAMPLE1",
"title_id": "abc123def456",
"credentials": {"username": "alice", "password": "hunter2"},
"cookies": "# Netscape HTTP Cookie File\n...",
"proxy": "us",
"no_proxy": false,
"profile": "default",
"cache": {}
}
```
**Response (202-style; auth runs asynchronously):**
```json
{
"session_id": "f1c4a8b2-9c7e-4d2a-bf91-2d3e4f5a6b7c",
"service": "EXAMPLE1",
"status": "authenticating"
}
```
### GET /api/session/{session_id}
Returns session metadata. 404 if expired or unknown.
```json
{
"session_id": "f1c4a8b2-9c7e-4d2a-bf91-2d3e4f5a6b7c",
"service": "EXAMPLE1",
"valid": true,
"expires_in": 3600,
"track_count": 0,
"title_count": 0
}
```
### DELETE /api/session/{session_id}
Tears down the session, cancels any pending prompts, and returns any updated per-session cache files (base64-encoded, zlib-compressed) so the client can re-warm next time.
```json
{
"status": "ok",
"cache": {
"tokens": "eJzLSM3JyVcozy/KSVGo5AIAGgQEvQ=="
}
}
```
### GET /api/session/{session_id}/titles
Returns the resolved titles list.
```json
{
"session_id": "f1c4a8b2-9c7e-4d2a-bf91-2d3e4f5a6b7c",
"titles": [
{
"type": "episode",
"name": "Pilot",
"series_title": "Example Show",
"season": 1,
"number": 1,
"year": 2024,
"id": "ep-0001",
"language": "en"
},
{
"type": "movie",
"name": "Example Movie",
"year": 2024,
"id": "mov-0001",
"language": "en"
}
]
}
```
### POST /api/session/{session_id}/tracks
**Request:**
```json
{"title_id": "ep-0001"}
```
**Response:**
```json
{
"title": {
"type": "episode",
"name": "Pilot",
"series_title": "Example Show",
"season": 1,
"number": 1,
"year": 2024,
"id": "ep-0001",
"language": "en"
},
"video": [
{
"id": "v-1080p-h264",
"codec": "H264",
"codec_display": "H.264",
"bitrate": 6000,
"width": 1920,
"height": 1080,
"resolution": "1920x1080",
"fps": "23.976",
"range": "SDR",
"range_display": "SDR",
"language": "en",
"drm": [
{
"type": "widevine",
"pssh": "AAAAW3Bzc2gAAAAA7e+...",
"kids": ["abcdef0123456789abcdef0123456789"],
"license_url": "https://license.example.com/widevine"
}
],
"descriptor": "DASH",
"url": "https://cdn.example.com/manifest.mpd"
}
],
"audio": [
{
"id": "a-en-eac3",
"codec": "EC3",
"codec_display": "Dolby Digital Plus",
"bitrate": 640,
"channels": "5.1",
"language": "en",
"atmos": false,
"descriptive": false,
"drm": null,
"descriptor": "DASH",
"url": "https://cdn.example.com/manifest.mpd"
}
],
"subtitles": [
{
"id": "s-en-vtt",
"codec": "WebVTT",
"language": "en",
"forced": false,
"sdh": false,
"cc": false,
"descriptor": "DASH",
"url": "https://cdn.example.com/subs/en.vtt"
}
],
"chapters": [
{"timestamp": "00:00:00.000", "name": "Chapter 1"}
],
"attachments": [],
"manifests": [
{
"type": "dash",
"url": "https://cdn.example.com/manifest.mpd",
"data": "eJzNVk1v2zAM/Ss..."
}
],
"session_headers": {
"User-Agent": "Mozilla/5.0 ..."
},
"session_cookies": {
"session": "abc123"
},
"server_cdm_type": "widevine"
}
```
### POST /api/session/{session_id}/segments
**Request:**
```json
{"track_ids": ["v-1080p-h264", "a-en-eac3"]}
```
**Response:**
```json
{
"tracks": {
"v-1080p-h264": {
"descriptor": "DASH",
"url": "https://cdn.example.com/manifest.mpd",
"drm": [
{
"type": "widevine",
"pssh": "AAAAW3Bzc2gAAAAA7e+...",
"kids": ["abcdef0123456789abcdef0123456789"],
"license_url": "https://license.example.com/widevine"
}
],
"headers": {"User-Agent": "Mozilla/5.0 ..."},
"cookies": {"session": "abc123"},
"data": {}
},
"a-en-eac3": {
"descriptor": "DASH",
"url": "https://cdn.example.com/manifest.mpd",
"drm": null,
"headers": {"User-Agent": "Mozilla/5.0 ..."},
"cookies": {"session": "abc123"},
"data": {}
}
}
}
```
### POST /api/session/{session_id}/license
Two modes, selected by the `mode` field.
**`mode: "proxy"` (default)** -- forward a client-built CDM challenge to the service's license endpoint.
Request:
```json
{
"mode": "proxy",
"track_id": "v-1080p-h264",
"challenge": "CAESxQEK...",
"drm_type": "widevine",
"pssh": "AAAAW3Bzc2gAAAAA7e+..."
}
```
Response:
```json
{"license": "CAIS3wIK..."}
```
**`mode: "server_cdm"`** -- the server uses its own CDM to license the track and extract keys. Single-track form takes `track_id`; batch form takes `track_ids`. Requires the calling user key to have a matching device (`devices` for Widevine, `playready_devices` for PlayReady) in `unshackle.yaml`.
Request (batch):
```json
{
"mode": "server_cdm",
"track_ids": ["v-1080p-h264", "a-en-eac3"],
"drm_type": "widevine"
}
```
Response:
```json
{
"keys": {
"v-1080p-h264": {
"abcdef0123456789abcdef0123456789": "00112233445566778899aabbccddeeff"
},
"a-en-eac3": {
"abcdef0123456789abcdef0123456789": "00112233445566778899aabbccddeeff"
}
},
"drm_type": "widevine"
}
```
### GET /api/session/{session_id}/prompt
Polled by the client during interactive authentication (OTP, PIN, device codes). Backed by the `InputBridge` in `unshackle/core/api/input_bridge.py`; `Service.request_input()` blocks server-side until the client posts a response.
Pending input:
```json
{"status": "pending_input", "prompt": "Enter OTP code: "}
```
Other states:
```json
{"status": "authenticating"}
```
```json
{"status": "authenticated"}
```
```json
{"status": "failed", "error": "Invalid credentials"}
```
### POST /api/session/{session_id}/prompt
Unblocks the server-side `request_input()` call.
Request:
```json
{"response": "123456"}
```
Response:
```json
{"status": "accepted"}
```
---
## Error Responses
All endpoints return consistent error responses:
```json
{
"status": "error",
"error_code": "INVALID_PARAMETERS",
"message": "Invalid vcodec: XYZ. Must be one of: H264, H265, VP9, AV1, VC1, VP8",
"timestamp": "2026-02-27T18:00:00.000000+00:00",
"details": { }
}
```
Common error codes:
- `INVALID_INPUT` -- malformed request body
- `INVALID_PARAMETERS` -- invalid parameter values
- `MISSING_SERVICE` -- service tag not provided
- `INVALID_SERVICE` -- service not found or not in the caller's allowlist
- `SERVICE_ERROR` -- service initialization or runtime error
- `AUTH_FAILED` -- authentication failure
- `NOT_FOUND` / `TRACK_NOT_FOUND` / session not found -- job/session/track/title missing
- `INTERNAL_ERROR` -- unexpected server error
When `--debug-api` is enabled, error responses include additional `debug_info` with tracebacks and stderr output.
Authentication errors from the auth middleware are returned as `{"status": 401, "message": "..."}` (not the standard error envelope).
---
## Download Job Lifecycle
```
queued -> downloading -> completed
\-> failed
queued -> cancelled
downloading -> cancelled
```
Jobs are retained for 24 hours after completion (override via top-level `download_job_retention_hours` in `unshackle.yaml`). The server runs up to 2 concurrent download jobs by default; override via top-level `max_concurrent_downloads`. This is independent of `serve.downloads`, which controls parallel tracks **within** a single job.
Remote sessions are managed by `SessionStore` (`unshackle/core/api/session_store.py`); idle sessions and their `InputBridge` instances are cleaned up by a background loop started/stopped with the app lifecycle.
+322
View File
@@ -0,0 +1,322 @@
# Download & Processing Configuration
This document covers configuration options related to downloading and processing media content.
## downloader
unshackle ships a single unified downloader at `unshackle/core/downloaders/requests.py`. The legacy
`aria2c`, `curl_impersonate`, and `n_m3u8dl_re` backends have been removed; their config blocks no
longer have any effect.
The unified downloader:
- Works with both a standard `requests.Session` and `RnetSession` (rnet/BoringSSL TLS impersonation,
which replaces the previous `curl_cffi` backend). When a service exposes its own session via
`self.session`, TLS fingerprinting is preserved on every segment.
- Uses adaptive chunk sizing between **512 KB and 4 MB**, picked from the response `Content-Length`.
- Spawns **up to `min(16, cpu_count + 4)` worker threads** by default for segmented downloads
(override via `--workers` / `dl.workers`).
- Resumes interrupted downloads via HTTP `Range` requests (a sibling `<file>.!dev` control file
marks an in-progress download).
- Has a single-URL fast path: if the server supports byte ranges and the file is at least 64 MB,
the file is split into 16 MB parts and downloaded in parallel into a pre-allocated file.
- Is selected per-track via `track.downloader`, which defaults to this unified `requests` downloader.
There is no `downloader:` config key to set anymore. Setting one to a legacy value will emit a
`DeprecationWarning` and otherwise be ignored.
---
## dl (dict)
Pre-define default options and switches of the `dl` command.
The values will be ignored if explicitly set in the CLI call.
The Key must be the same value Python click would resolve it to as an argument.
E.g., `@click.option("-r", "--range", "range_", type=...` actually resolves as `range_` variable.
For example to set the default primary language to download to German,
```yaml
lang: de
```
You can also set multiple preferred languages using a list, e.g.,
```yaml
lang:
- en
- fr
```
to set how many tracks to download concurrently to 4 and download threads to 16,
```yaml
downloads: 4
workers: 16
```
to set `--bitrate=CVBR` for a specific service,
```yaml
lang: de
EXAMPLE:
bitrate: CVBR
```
or to change the output subtitle format from the default (original format) to WebVTT,
```yaml
sub_format: vtt
```
### All Available `dl` Keys
Below is a comprehensive list of keys that can be pre-defined in the `dl` section. Each corresponds
to a CLI option on the `dl` command. CLI arguments always take priority over config values.
**Quality and codec:**
| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `quality` | int or list | best | Resolution(s) to download (e.g., `1080`, `[1080, 2160]`) |
| `vcodec` | str or list | any | Video codec(s): `H264`, `H265`, `VP9`, `AV1`, `VC1` |
| `acodec` | str or list | any | Audio codec(s): `AAC`, `AC3`, `EC3`, `AC4`, `OPUS`, `FLAC`, `ALAC`, `DTS` |
| `vbitrate` | int | highest | Video bitrate in kbps |
| `abitrate` | int | highest | Audio bitrate in kbps |
| `vbitrate_range` | str | none | Video bitrate window in kbps, format `MIN-MAX` (e.g., `6000-7000`) |
| `abitrate_range` | str | none | Audio bitrate window in kbps, format `MIN-MAX` |
| `real_video_bitrate` | bool | `false` | Probe actual media size to compute true video bitrates, overriding the manifest's declared value (`-rvb`). See [Real bitrate probing](#real-bitrate-probing) |
| `real_audio_bitrate` | bool | `false` | Same as above for audio tracks (`-rab`). Slower than video (more renditions) |
| `range_` | str or list | `SDR` | Color range(s): `SDR`, `HDR10`, `HDR10+`, `HLG`, `DV`, `HYBRID` |
| `channels` | float | any | Audio channels (e.g., `5.1`, `7.1`) |
| `worst` | bool | `false` | Select the lowest bitrate track within the specified quality. Requires `quality` |
| `best_available` | bool | `false` | Continue if requested quality is unavailable |
**Language:**
| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `lang` | str or list | `orig` | Language for video and audio (`orig` = original language) |
| `v_lang` | list | `[]` | Language override for video tracks only |
| `a_lang` | list | `[]` | Language override for audio tracks only |
| `s_lang` | list | `["all"]` | Language for subtitles |
| `require_subs` | list | `[]` | Required subtitle languages (skip title if missing) |
| `forced_subs` | bool | `false` | Include forced subtitle tracks |
| `exact_lang` | bool | `false` | Exact language matching (no regional variants) |
| `latest_episode` | bool | `false` | Download only the single most recent episode of a series |
**Track selection:**
| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `video_only` | bool | `false` | Only download video tracks |
| `audio_only` | bool | `false` | Only download audio tracks |
| `subs_only` | bool | `false` | Only download subtitle tracks |
| `chapters_only` | bool | `false` | Only download chapters |
| `no_video` | bool | `false` | Skip video tracks |
| `no_audio` | bool | `false` | Skip audio tracks |
| `no_subs` | bool | `false` | Skip subtitle tracks |
| `no_chapters` | bool | `false` | Skip chapters |
| `no_atmos` | bool | `false` | Exclude Dolby Atmos audio tracks |
| `audio_description` | bool | `false` | Include audio description tracks |
**Output and tagging:**
| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `tag` | str | config default | Override group tag |
| `repack` | bool | `false` | Add REPACK tag to output filename |
| `sub_format` | str | original | Output subtitle format: `srt`, `vtt`, `ass`, `ssa`, `ttml` |
| `no_folder` | bool | `false` | Disable folder creation for TV shows |
| `no_source` | bool | `false` | Remove source tag from filename |
| `no_mux` | bool | `false` | Do not mux tracks into a container file |
| `split_audio` | bool | `false` | Create separate output files per audio codec |
| `export` | bool | `false` | Write a JSON sidecar with manifest URLs, subtitles, per-track KID:KEY, codec/track info |
**Metadata enrichment:**
| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `tmdb_id` | int | `null` | Use specific TMDB ID for tagging |
| `imdb_id` | str | `null` | Use specific IMDB ID (e.g., `tt1375666`) |
| `animeapi_id` | str | `null` | Anime database ID via AnimeAPI (e.g., `mal:12345`, `anilist:98765`) |
| `enrich` | bool | `false` | Override show title and year from external source. Requires `tmdb_id`, `imdb_id`, or `animeapi_id` |
**Download behavior:**
| Key | Type | Default | Description |
| --- | --- | --- | --- |
| `downloads` | int | `1` | Concurrent track downloads |
| `workers` | int | `min(16, cpu_count + 4)` | Max threads per track download (segments / ranged parts) |
| `slow` | bool or `MIN-MAX` | `false` | Randomized delay between titles. `true` uses 60-120s; pass `MIN-MAX` (e.g., `20-40`) for a custom range |
| `no_proxy_download` | bool | `false` | Bypass proxy for segment downloads only. Manifest, license, and auth still use proxy |
| `skip_dl` | bool | `false` | Skip download, only get decryption keys |
| `cdm_only` | bool | `null` | Only use CDM (`true`) or only vaults (`false`) |
### Real bitrate probing
Some services declare inaccurate `bandwidth`/`BANDWIDTH` in their manifests — often
a peak or nominal figure that is far from the real average. Because `track.bitrate`
drives the track listing, sorting, and `--vbitrate` / `--vbitrate-range` selection,
a wrong value picks the wrong track.
`-rvb` / `--real-video-bitrate` (and `-rab` / `--real-audio-bitrate` for audio)
probe the actual media size and overwrite `track.bitrate` with the measured value
(`bytes * 8 / duration`) before listing and selection. So `-rvb --list` shows the
true numbers, and `-rvb --vbitrate-range 6000-7000` selects against them. Without
the flag, behaviour is unchanged (the manifest value is used).
How it works:
- **Single-file tracks** (one whole file per rendition — e.g. DASH `SegmentBase`
or services that collapse to a `BaseURL`) are measured **exactly**: the whole
file size over the track duration.
- **Multi-segment tracks** (most HLS) are a **sampled estimate** — a spread of
segments is probed and extrapolated, typically within a few percent. Segment
bytes include container overhead, so MPEG-TS HLS reads a few percent above the
demuxed stream (this is the real *delivered* size).
- Only the top renditions per quality tier are probed (video grouped by
codec + range, audio by codec + channels + language), in parallel, then extended
downward only as far as needed to keep ranking correct. This keeps the pass fast
even when a service exposes dozens of renditions.
- Tracks whose duration cannot be determined fall back to `ffprobe`; probe failures
are non-fatal and leave the manifest bitrate in place.
Per-track before→after values are logged at debug level (run with `-d`); the
corrected values always appear in the Available Tracks panel.
You can also set per-service `dl` overrides (see [Service Integration & Authentication Configuration](SERVICE_CONFIG.md)):
```yaml
dl:
lang: en
downloads: 4
workers: 16
EXAMPLE:
bitrate: CVBR
EXAMPLE2:
worst: true
quality: 1080
```
---
## audio (dict)
Configuration for audio track selection.
- `codec_priority`
Optional list of audio codec names defining the preferred order when multiple audio
tracks share the same bitrate and language. Listed codecs are ranked in the order given.
Codecs not in the list retain their bitrate-based ordering and are placed after all
listed codecs (i.e. soft priority — nothing is dropped).
Atmos tracks still take precedence over codec priority, and audio description tracks
are still moved to the end.
Valid codec names: `AAC`, `AC3`, `EC3`, `AC4`, `OPUS`, `OGG`, `DTS`, `ALAC`, `FLAC`.
For example,
```yaml
audio:
codec_priority: [FLAC, ALAC, AC4, EC3, DTS, AC3, OPUS, AAC, OGG]
```
Or to only prefer a subset (e.g. surround codecs first, everything else falls back to
bitrate order):
```yaml
audio:
codec_priority: [EC3, DTS, AC3, AAC]
```
When unset, audio tracks are sorted by bitrate alone (with Atmos/descriptive rules still
applied).
---
## subtitle (dict)
Configuration for subtitle processing and conversion.
- `conversion_method`
Method to use for converting subtitles between formats. Default: `"auto"`
- `"auto"` — Smart routing: uses subby for WebVTT/SAMI, pycaption for others.
- `"subby"` — Always use subby with advanced processing.
- `"pycaption"` — Use only pycaption library (no SubtitleEdit, no subby).
- `"subtitleedit"` — Prefer SubtitleEdit when available, fall back to pycaption.
- `"pysubs2"` — Use pysubs2 library (supports SRT/SSA/ASS/WebVTT/TTML/SAMI/MicroDVD/MPL2/TMP).
- `sdh_method`
Method to use for SDH (hearing impaired) stripping. Default: `"auto"`
- `"auto"` — Try subby (SRT only), then SubtitleEdit (if available), then subtitle-filter.
- `"subby"` — Use subby library (SRT only).
- `"subtitleedit"` — Use SubtitleEdit tool (Windows only, falls back to subtitle-filter).
- `"filter-subs"` — Use subtitle-filter library directly.
- `strip_sdh`
Automatically create stripped (non-SDH) versions of SDH subtitles. Default: `true`
- `convert_before_strip`
Auto-convert VTT/other formats to SRT before using subtitle-filter for SDH stripping.
Ensures compatibility when subtitle-filter is used as fallback. Default: `true`
- `preserve_formatting`
Preserve original subtitle formatting (tags, positioning, styling).
When `true`, skips pycaption processing for WebVTT files to keep tags like `<i>`, `<b>`,
positioning intact. Combined with no `sub_format` setting, ensures subtitles remain in
their original format. Default: `true`
- `output_mode`
Output mode for subtitles. Default: `"mux"`
- `"mux"` — Embed subtitles in MKV container only.
- `"sidecar"` — Save subtitles as separate files only.
- `"both"` — Embed in MKV and save as sidecar files.
- `sidecar_format`
Format for sidecar subtitle files when `output_mode` is `"sidecar"` or `"both"`. Default: `"srt"`
Options: `srt`, `vtt`, `ass`, `original` (keep current format).
For example,
```yaml
subtitle:
conversion_method: auto
sdh_method: auto
strip_sdh: true
convert_before_strip: true
preserve_formatting: true
output_mode: mux
sidecar_format: srt
```
---
## decryption (str | dict)
Choose what software to use to decrypt DRM-protected content throughout unshackle where needed.
You may provide a single decryption method globally or a mapping of service tags to
decryption methods.
Options:
- `shaka` (default) - Shaka Packager - <https://github.com/shaka-project/shaka-packager>
- `mp4decrypt` - mp4decrypt from Bento4 - <https://github.com/axiomatic-systems/Bento4>
Note that Shaka Packager is the traditional method and works with most services. mp4decrypt
is an alternative that may work better with certain services that have specific encryption formats.
Example mapping:
```yaml
decryption:
EXAMPLE: mp4decrypt
EXAMPLE2: shaka
default: shaka
```
The `default` entry is optional. If omitted, `shaka` will be used for services not listed.
Simple configuration (single method for all services):
```yaml
decryption: mp4decrypt
```
---
+481
View File
@@ -0,0 +1,481 @@
# DRM & CDM Configuration
This document covers Digital Rights Management (DRM) and Content Decryption Module (CDM) configuration options.
## cdm (dict)
Pre-define which Widevine or PlayReady device to use for each Service by Service Tag as Key (case-sensitive).
The value should be a WVD or PRD filename without the file extension. When
loading the device, unshackle will look in both the `WVDs` and `PRDs` directories
for a matching file.
For example,
```yaml
EXAMPLE: chromecdm_903_l3
EXAMPLE2: nexus_6_l1
```
You may also specify this device based on the profile used.
For example,
```yaml
EXAMPLE: chromecdm_903_l3
EXAMPLE2: nexus_6_l1
EXAMPLE3:
john_sd: chromecdm_903_l3
jane_uhd: nexus_5_l1
```
You can also specify a fallback value to predefine if a match was not made.
This can be done using `default` key. This can help reduce redundancy in your specifications.
For example, the following has the same result as the previous example, as well as all other
services and profiles being pre-defined to use `chromecdm_903_l3`.
```yaml
EXAMPLE2: nexus_6_l1
EXAMPLE3:
jane_uhd: nexus_5_l1
default: chromecdm_903_l3
```
You can also select CDMs based on video resolution using comparison operators (`>=`, `>`, `<=`, `<`)
or exact match on the resolution height.
For example,
```yaml
EXAMPLE:
"<=1080": generic_android_l3 # Use L3 for 1080p and below
">1080": nexus_5_l1 # Use L1 for above 1080p (1440p, 2160p)
default: generic_android_l3 # Fallback if no quality match
```
You can mix profiles and quality thresholds in the same service:
```yaml
EXAMPLE:
john: example_l3_profile # Profile-based selection
"<=720": example_mobile_l3 # Quality-based selection
"1080": example_standard_l3 # Exact match for 1080p
">=1440": example_premium_l1 # Quality-based selection
default: example_standard_l3 # Fallback
```
---
## remote_cdm (list\[dict])
Configure remote CDM (Content Decryption Module) APIs to use for decrypting DRM-protected content.
Remote CDMs allow you to use high-security CDMs (L1/L2 for Widevine, SL2000/SL3000 for PlayReady) without
having the physical device files locally.
unshackle supports multiple types of remote CDM providers:
1. **DecryptLabs CDM** - Official DecryptLabs KeyXtractor API with intelligent caching
2. **Custom API CDM** - Highly configurable adapter for any third-party CDM API
3. **Legacy PyWidevine Serve** - Standard pywidevine serve-compliant APIs
The name of each defined remote CDM can be referenced in the `cdm` configuration as if it was a local device file.
### DecryptLabs Remote CDM
DecryptLabs provides a professional CDM API service with support for multiple device types and intelligent key caching.
**Supported Devices:**
- **Widevine**: `ChromeCDM` (L3), `L1` (Security Level 1), `L2` (Security Level 2)
- **PlayReady**: `SL2` (SL2000), `SL3` (SL3000)
**Configuration:**
```yaml
remote_cdm:
# Widevine L1 Device
- name: decrypt_labs_l1
type: decrypt_labs # Required: identifies as DecryptLabs CDM
device_name: L1 # Required: must match exactly (L1, L2, ChromeCDM, SL2, SL3)
host: https://keyxtractor.decryptlabs.com
secret: YOUR_API_KEY # Your DecryptLabs API key
# Widevine L2 Device
- name: decrypt_labs_l2
type: decrypt_labs
device_name: L2
host: https://keyxtractor.decryptlabs.com
secret: YOUR_API_KEY
# Chrome CDM (L3)
- name: decrypt_labs_chrome
type: decrypt_labs
device_name: ChromeCDM
host: https://keyxtractor.decryptlabs.com
secret: YOUR_API_KEY
# PlayReady SL2000
- name: decrypt_labs_playready_sl2
type: decrypt_labs
device_name: SL2
device_type: PLAYREADY # Required for PlayReady
host: https://keyxtractor.decryptlabs.com
secret: YOUR_API_KEY
# PlayReady SL3000
- name: decrypt_labs_playready_sl3
type: decrypt_labs
device_name: SL3
device_type: PLAYREADY
host: https://keyxtractor.decryptlabs.com
secret: YOUR_API_KEY
```
**Features:**
- Intelligent key caching system (reduces API calls)
- Automatic integration with unshackle's vault system
- Support for both Widevine and PlayReady
- Multiple security levels (L1, L2, L3, SL2000, SL3000)
**Note:** The `device_type` field determines whether the CDM operates in PlayReady or Widevine mode.
Setting `device_type: PLAYREADY` (or using `device_name: SL2` / `SL3`) activates PlayReady mode.
The `security_level` field is auto-computed from `device_name` when not specified (e.g., SL2 defaults
to 2000, SL3 to 3000, and Widevine devices default to 3). You can override these if needed.
### Custom API Remote CDM
A highly configurable CDM adapter that can work with virtually any third-party CDM API through YAML configuration.
This allows you to integrate custom CDM services without writing code.
**Basic Example:**
```yaml
remote_cdm:
- name: custom_chrome_cdm
type: custom_api # Required: identifies as Custom API CDM
host: https://your-cdm-api.com
timeout: 30 # Optional: request timeout in seconds
device:
name: ChromeCDM
type: CHROME # CHROME, ANDROID, PLAYREADY
system_id: 27175
security_level: 3
auth:
type: bearer # bearer, header, basic, body
key: YOUR_API_TOKEN
endpoints:
get_request:
path: /get-challenge
method: POST
decrypt_response:
path: /get-keys
method: POST
caching:
enabled: true # Enable key caching
use_vaults: true # Integrate with vault system
```
**Advanced Example with Field Mapping:**
```yaml
remote_cdm:
- name: advanced_custom_api
type: custom_api
host: https://api.example.com
device:
name: L1
type: ANDROID
security_level: 1
# Authentication configuration
auth:
type: header
header_name: X-API-Key
key: YOUR_SECRET_KEY
custom_headers:
User-Agent: Unshackle/3.1.0
X-Client-Version: "1.0"
# Endpoint configuration
endpoints:
get_request:
path: /v2/challenge
method: POST
timeout: 30
decrypt_response:
path: /v2/decrypt
method: POST
timeout: 30
# Request parameter mapping
request_mapping:
get_request:
param_names:
init_data: pssh # Rename 'init_data' to 'pssh'
scheme: device_type # Rename 'scheme' to 'device_type'
static_params:
api_version: "2.0" # Add static parameter
decrypt_response:
param_names:
license_request: challenge
license_response: license
# Response field mapping
response_mapping:
get_request:
fields:
challenge: data.challenge # Deep field access
session_id: session.id
success_conditions:
- status == 'ok' # Validate response
decrypt_response:
fields:
keys: data.keys
key_fields:
kid: key_id # Map 'kid' field
key: content_key # Map 'key' field
caching:
enabled: true
use_vaults: true
check_cached_first: true # Check cache before API calls
```
**Supported Authentication Types:**
- `bearer` - Bearer token authentication
- `header` - Custom header authentication
- `basic` - HTTP Basic authentication
- `body` - Credentials in request body
- `query` - Authentication added to query string parameters
### Legacy PyWidevine Serve Format
Standard [pywidevine] serve-compliant remote CDM configuration (backwards compatibility).
```yaml
remote_cdm:
- name: legacy_chrome_cdm
device_name: chrome
device_type: CHROME
system_id: 27175
security_level: 3
host: https://domain.com/api
secret: secret_key
```
**Note:** If the `type` field is not specified, the entry is treated as a legacy pywidevine serve CDM.
[pywidevine]: https://github.com/rlaphoenix/pywidevine
---
## decrypt_labs_api_key (str)
API key for DecryptLabs CDM service integration.
When set, enables the use of DecryptLabs remote CDM services in your `remote_cdm` configuration.
This is used specifically for `type: "decrypt_labs"` entries in the remote CDM list.
For example,
```yaml
decrypt_labs_api_key: "your_api_key_here"
```
**Note**: This is different from the per-CDM `secret` field in `remote_cdm` entries. This provides a global
API key that can be referenced across multiple DecryptLabs CDM configurations. If a `remote_cdm` entry with
`type: "decrypt_labs"` does not have a `secret` field specified, the global `decrypt_labs_api_key` will be
used as a fallback.
---
## decryption (str|dict)
Configure which decryption tool to use for DRM-protected content. Default: `shaka`.
Supported values:
- `shaka` - Shaka Packager (default)
- `mp4decrypt` - Bento4 mp4decrypt
You can specify a single decrypter for all services:
```yaml
decryption: shaka
```
Or configure per-service with a `DEFAULT` fallback:
```yaml
decryption:
DEFAULT: shaka
EXAMPLE: mp4decrypt
EXAMPLE2: shaka
```
Service keys are case-insensitive (normalized to uppercase internally).
---
## MonaLisa DRM
MonaLisa is a WASM-based DRM system that uses local key extraction and two-stage segment decryption.
Unlike Widevine and PlayReady, MonaLisa does not use a challenge/response flow with a license server.
Instead, the PSSH value (ticket) is provided directly by the service API, and keys are extracted
locally via a WASM module.
### Requirements
- **ML-Worker binary**: Must be available on your system `PATH` (discovered via `binaries.ML_Worker`).
This is the binary that performs stage-1 decryption.
### Decryption stages
1. **ML-Worker binary**: Removes MonaLisa encryption layer (bbts -> ents). The key is passed via command-line argument.
2. **AES-ECB decryption**: Final decryption with service-provided key.
MonaLisa uses per-segment decryption during download (not post-download like Widevine/PlayReady),
so segments are decrypted as they are downloaded.
**Note:** MonaLisa is configured per-service rather than through global config options. Services
that use MonaLisa handle ticket/key retrieval and CDM initialization internally.
---
## key_vaults (list\[dict])
Key Vaults store your obtained Content Encryption Keys (CEKs) and Key IDs per-service.
This can help reduce unnecessary License calls even during the first download. This is because a Service may
provide the same Key ID and CEK for both Video and Audio, as well as for multiple resolutions or bitrates.
You can have as many Key Vaults as you would like. It's nice to share Key Vaults or use a unified Vault on
Teams as sharing CEKs immediately can help reduce License calls drastically.
Four types of Vaults are in the Core codebase: API, SQLite, MySQL, and HTTP. API and HTTP make HTTP requests to a RESTful API,
whereas SQLite and MySQL directly connect to an SQLite or MySQL Database.
Note: SQLite and MySQL vaults have to connect directly to the Host/IP. It cannot be in front of a PHP API or such.
Beware that some Hosting Providers do not let you access the MySQL server outside their intranet and may not be
accessible outside their hosting platform.
Additional behavior:
- `no_push` (bool): Optional per-vault flag. When `true`, the vault will not receive pushed keys (writes) but
will still be queried and can provide keys for lookups. Useful for read-only/backup vaults.
### Using an API Vault
API vaults use a specific HTTP request format, therefore API or HTTP Key Vault APIs from other projects or services may
not work in unshackle. The API format can be seen in the [API Vault Code](unshackle/vaults/API.py).
```yaml
- type: API
name: "John#0001's Vault" # arbitrary vault name
uri: "https://key-vault.example.com" # api base uri (can also be an IP or IP:Port)
# uri: "127.0.0.1:80/key-vault"
# uri: "https://api.example.com/key-vault"
token: "random secret key" # authorization token
# no_push: true # optional; make this API vault read-only (lookups only)
```
### Using a MySQL Vault
MySQL vaults can be either MySQL or MariaDB servers. I recommend MariaDB.
A MySQL Vault can be on a local or remote network, but I recommend SQLite for local Vaults.
```yaml
- type: MySQL
name: "John#0001's Vault" # arbitrary vault name
host: "127.0.0.1" # host/ip
# port: 3306 # port (defaults to 3306)
database: vault # database used for unshackle
username: jane11
password: Doe123
# no_push: false # optional; defaults to false
```
I recommend giving only a trustable user (or yourself) CREATE permission and then use unshackle to cache at least one CEK
per Service to have it create the tables. If you don't give any user permissions to create tables, you will need to
make tables yourself.
- Use a password on all user accounts.
- Never use the root account with unshackle (even if it's you).
- Do not give multiple users the same username and/or password.
- Only give users access to the database used for unshackle.
- You may give trusted users CREATE permission so unshackle can create tables if needed.
- Other uses should only be given SELECT and INSERT permissions.
### Using an SQLite Vault
SQLite Vaults are usually only used for locally stored vaults. This vault may be stored on a mounted Cloud storage
drive, but I recommend using SQLite exclusively as an offline-only vault. Effectively this is your backup vault in
case something happens to your MySQL Vault.
```yaml
- type: SQLite
name: "My Local Vault" # arbitrary vault name
path: "C:/Users/Jane11/Documents/unshackle/data/key_vault.db"
# no_push: true # optional; commonly true for local backup vaults
```
**Note**: You do not need to create the file at the specified path.
SQLite will create a new SQLite database at that path if one does not exist.
Try not to accidentally move the `db` file once created without reflecting the change in the config, or you will end
up with multiple databases.
If you work on a Team I recommend every team member having their own SQLite Vault even if you all use a MySQL vault
together.
### Using an HTTP Vault
HTTP Vaults provide flexible HTTP-based key storage with support for multiple API modes. This vault type
is useful for integrating with various third-party key vault APIs.
```yaml
- type: HTTP
name: "My HTTP Vault"
host: "https://vault-api.example.com"
api_key: "your_api_key" # or use 'password' field
api_mode: "json" # query, json, or decrypt_labs
# username: "user" # required for query mode only
# no_push: false # optional; defaults to false
```
**Supported API Modes:**
- `query` - Uses GET requests with query parameters. Requires `username` field.
- `json` - Uses POST requests with JSON payloads. Token-based authentication.
- `decrypt_labs` - DecryptLabs API format. Read-only mode (`no_push` is forced to `true`).
**Example configurations:**
```yaml
# Query mode (requires username)
- type: HTTP
name: "Query Vault"
host: "https://api.example.com/keys"
username: "myuser"
password: "mypassword"
api_mode: "query"
# JSON mode
- type: HTTP
name: "JSON Vault"
host: "https://api.example.com/vault"
api_key: "secret_token"
api_mode: "json"
# DecryptLabs mode (read-only)
- type: HTTP
name: "DecryptLabs Cache"
host: "https://keyxtractor.decryptlabs.com/cache"
api_key: "your_decrypt_labs_api_key"
api_mode: "decrypt_labs"
```
**Note**: The `decrypt_labs` mode is always read-only and cannot receive pushed keys.
---
+243
View File
@@ -0,0 +1,243 @@
# Gluetun VPN Proxy
Gluetun provides Docker-managed VPN proxies supporting 50+ VPN providers.
## Prerequisites
**Docker must be installed and running.**
```bash
# Linux
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER # Then log out/in
# Windows/Mac
# Install Docker Desktop: https://www.docker.com/products/docker-desktop/
```
## Quick Start
### 1. Configuration
Add to `~/.config/unshackle/unshackle.yaml`:
```yaml
proxy_providers:
gluetun:
providers:
windscribe:
vpn_type: openvpn
credentials:
username: "YOUR_OPENVPN_USERNAME"
password: "YOUR_OPENVPN_PASSWORD"
```
### 2. Usage
Use 2-letter country codes directly:
```bash
unshackle dl SERVICE CONTENT --proxy gluetun:windscribe:us
unshackle dl SERVICE CONTENT --proxy gluetun:windscribe:uk
```
Format: `gluetun:provider:region`
## Provider Credential Requirements
**OpenVPN (Recommended)**: Most providers support OpenVPN with just `username` and `password` - the simplest setup.
**WireGuard**: Requires private keys and varies by provider. See the [Gluetun Wiki](https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers) for provider-specific requirements. Note that `vpn_type` defaults to `wireguard` if not specified.
## Getting Your Credentials
### Windscribe (OpenVPN)
1. Go to [windscribe.com/getconfig/openvpn](https://windscribe.com/getconfig/openvpn)
2. Log in with your Windscribe account
3. Select any location and click "Get Config"
4. Copy the username and password shown
### NordVPN (OpenVPN)
1. Go to [NordVPN Service Credentials](https://my.nordaccount.com/dashboard/nordvpn/manual-configuration/service-credentials/)
2. Log in with your NordVPN account
3. Generate or view your service credentials
4. Copy the username and password
> **Note**: Use service credentials, NOT your account email/password.
### WireGuard Credentials (Advanced)
WireGuard requires private keys instead of username/password. See the [Gluetun Wiki](https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers) for provider-specific WireGuard setup.
## Configuration Examples
**OpenVPN (Recommended)**
Most providers support OpenVPN with just username and password:
```yaml
providers:
windscribe:
vpn_type: openvpn
credentials:
username: YOUR_OPENVPN_USERNAME
password: YOUR_OPENVPN_PASSWORD
nordvpn:
vpn_type: openvpn
credentials:
username: YOUR_SERVICE_USERNAME
password: YOUR_SERVICE_PASSWORD
```
**WireGuard (Advanced)**
WireGuard can be faster but requires more complex credential setup:
```yaml
# NordVPN/ProtonVPN (only private_key needed)
providers:
nordvpn:
vpn_type: wireguard
credentials:
private_key: YOUR_PRIVATE_KEY
# Surfshark/Mullvad/IVPN (private_key AND addresses required)
surfshark:
vpn_type: wireguard
credentials:
private_key: YOUR_PRIVATE_KEY
addresses: 10.x.x.x/32
# Windscribe (all three credentials required)
windscribe:
vpn_type: wireguard
credentials:
private_key: YOUR_PRIVATE_KEY
addresses: 10.x.x.x/32
preshared_key: YOUR_PRESHARED_KEY
```
## Server Selection
Most providers use `SERVER_COUNTRIES`, but some use `SERVER_REGIONS`:
| Variable | Providers |
|----------|-----------|
| `SERVER_COUNTRIES` | NordVPN, ProtonVPN, Surfshark, Mullvad, ExpressVPN, and most others |
| `SERVER_REGIONS` | Windscribe, VyprVPN, VPN Secure |
Unshackle handles this automatically - just use 2-letter country codes.
### Per-Provider Server Mapping
You can explicitly map region codes to country names, cities, or hostnames per provider:
```yaml
providers:
nordvpn:
vpn_type: openvpn
credentials:
username: YOUR_USERNAME
password: YOUR_PASSWORD
server_countries:
us: "United States"
uk: "United Kingdom"
server_cities:
us: "New York"
server_hostnames:
us: "us1239.nordvpn.com"
```
### Specific Server Selection
Use a `<country><number>` region (e.g. `us1239`) to target a specific server. Unshackle builds the
hostname automatically per provider:
| Provider | Hostname format |
|----------|-----------------|
| NordVPN | `us1239.nordvpn.com` |
| Surfshark | `us-1239.prod.surfshark.com` |
| ExpressVPN | `us-1239.expressvpn.com` |
| CyberGhost | `us-s1239.cg-dialup.net` |
| Other | `us1239` (passed as-is to `SERVER_HOSTNAMES`) |
### Extra Environment Variables
You can pass additional Gluetun environment variables per provider using `extra_env`:
```yaml
providers:
nordvpn:
vpn_type: openvpn
credentials:
username: YOUR_USERNAME
password: YOUR_PASSWORD
extra_env:
LOG_LEVEL: debug
```
## Global Settings
```yaml
proxy_providers:
gluetun:
providers: {...}
base_port: 8888 # Starting port (default: 8888)
auto_cleanup: true # Remove containers on exit (default: true)
verify_ip: true # Verify IP matches region (default: true)
container_prefix: "unshackle-gluetun" # Docker container name prefix (default: "unshackle-gluetun")
auth_user: username # Proxy auth (optional)
auth_password: password # Proxy auth (optional)
```
## Features
- **Container Reuse**: First request takes 10-30s; subsequent requests are instant. Containers created by other unshackle processes are auto-detected via `docker inspect` and reused.
- **Ready Detection**: Waits up to 60s for both the HTTP proxy to listen (`[http proxy] listening`) and the VPN tunnel to come up (`initialization sequence completed` or `public ip address is`) before returning the proxy URI. Bails early on `fatal` or `invalid credentials` log lines.
- **IP Verification**: When `verify_ip: true` (default), looks up the exit IP via `ipinfo.io` through the proxy and compares country code to the requested region. Retries 3 times with exponential backoff (1s, 2s, 4s).
- **Concurrent Sessions**: Multiple downloads share the same container; ports are allocated thread-safely starting at `base_port`.
- **Specific Servers**: Use `--proxy gluetun:nordvpn:us1239` for specific server selection (see table above).
- **Automatic Image Pull**: The Gluetun Docker image (`qmcgaw/gluetun:latest`) is pulled automatically on first use (5 min timeout).
- **Secure Credentials**: Credentials are passed via temporary env files (mode 0600), then zero-overwritten and unlinked after `docker run`. They never appear in process listings.
- **Auto Cleanup**: Containers are removed via `atexit` (Ctrl+C still works normally). Disable with `auto_cleanup: false` to leave them stopped instead.
## Container Management
```bash
# View containers
docker ps | grep unshackle-gluetun
# Check logs
docker logs unshackle-gluetun-nordvpn-us
# Remove all containers
docker ps -a | grep unshackle-gluetun | awk '{print $1}' | xargs docker rm -f
```
## Troubleshooting
### Docker Permission Denied (Linux)
```bash
sudo usermod -aG docker $USER
# Then log out and log back in
```
### VPN Connection Failed
Check container logs for specific errors:
```bash
docker logs unshackle-gluetun-nordvpn-us
```
Common issues:
- Invalid/missing credentials
- Windscribe WireGuard requires `preshared_key` (can be empty string, but must be set in credentials)
- VPN provider server issues
- Container startup timeout (default 60 seconds)
## Resources
- [Gluetun Wiki](https://github.com/qdm12/gluetun-wiki) - Official provider documentation
- [Gluetun GitHub](https://github.com/qdm12/gluetun)
+178
View File
@@ -0,0 +1,178 @@
# Network & Proxy Configuration
This document covers network and proxy configuration options for bypassing geofencing and managing connections.
## proxy_providers (dict)
Enable external proxy provider services. These proxies will be used automatically where needed as defined by the
Service's GEOFENCE class property, but can also be explicitly used with `--proxy`. You can specify which provider
to use by prefixing it with the provider key name, e.g., `--proxy basic:de` or `--proxy nordvpn:de`. Some providers
support specific query formats for selecting a country/server.
### basic (dict[str, str|list])
Define a mapping of country to proxy to use where required.
The keys are region Alpha 2 Country Codes. Alpha 2 Country Codes are `[a-z]{2}` codes, e.g., `us`, `gb`, and `jp`.
Don't get this mixed up with language codes like `en` vs. `gb`, or `ja` vs. `jp`.
Do note that each key's value can be a list of strings, or a string. For example,
```yaml
us:
- "http://john%40email.tld:password123@proxy-us.domain.tld:8080"
- "http://jane%40email.tld:password456@proxy-us.domain2.tld:8080"
de: "https://127.0.0.1:8080"
```
Note that if multiple proxies are defined for a region, then by default one will be randomly chosen.
You can choose a specific one by specifying it's number, e.g., `--proxy basic:us2` will choose the
second proxy of the US list.
### nordvpn (dict)
Set your NordVPN Service credentials with `username` and `password` keys to automate the use of NordVPN as a Proxy
system where required.
You can also specify specific servers to use per-region with the `server_map` key.
Sometimes a specific server works best for a service than others, so hard-coding one for a day or two helps.
You can also select servers by city using the format `--proxy nordvpn:us:seattle` or `--proxy nordvpn:ca:calgary`.
For example,
```yaml
username: zxqsR7C5CyGwmGb6KSvk8qsZ # example of the login format
password: wXVHmht22hhRKUEQ32PQVjCZ
server_map:
us: 12 # force US server #12 for US proxies
```
The username and password should NOT be your normal NordVPN Account Credentials.
They should be the `Service credentials` which can be found on your Nord Account Dashboard.
Once set, you can also specifically opt in to use a NordVPN proxy by specifying `--proxy nordvpn:gb` or such.
You can even set a specific server number this way, e.g., `--proxy nordvpn:gb2366`.
Note that `gb` is used instead of `uk` to be more consistent across regional systems.
### surfsharkvpn (dict)
Enable Surfshark VPN proxy service using Surfshark Service credentials (not your login password).
You may pin specific server IDs per region using `server_map`.
You can also select servers by city using the format `--proxy surfsharkvpn:us:seattle`.
```yaml
username: your_surfshark_service_username # https://my.surfshark.com/vpn/manual-setup/main/openvpn
password: your_surfshark_service_password # service credentials, not account password
server_map:
us: 3844 # force US server #3844
gb: 2697 # force GB server #2697
au: 4621 # force AU server #4621
```
### hola
Enable Hola VPN proxy service. Requires the `hola-proxy` binary to be installed and available in your PATH.
No configuration is needed under `proxy_providers`. Hola is loaded automatically when the `hola-proxy` binary
is detected.
Once available, use `--proxy hola:us` or similar to connect through Hola.
### windscribevpn (dict)
Enable Windscribe VPN proxy service using static OpenVPN service credentials.
Use the service credentials from https://windscribe.com/getconfig/openvpn (not your account login credentials).
```yaml
proxy_providers:
windscribevpn:
username: openvpn_username # From https://windscribe.com/getconfig/openvpn
password: openvpn_password # Service credentials, NOT your account password
```
#### Server Mapping
You can optionally pin specific servers using `server_map`:
```yaml
proxy_providers:
windscribevpn:
username: openvpn_username
password: openvpn_password
server_map:
us: us-central-096.totallyacdn.com # Force specific US server
gb: uk-london-001.totallyacdn.com # Force specific UK server
```
Once configured, use `--proxy windscribevpn:us` or `--proxy windscribevpn:gb` etc. to connect through Windscribe.
You can also select specific servers by number (e.g., `--proxy windscribevpn:sg007`) or filter by city
(e.g., `--proxy windscribevpn:ca:toronto`).
### gluetun (dict)
Docker-managed VPN proxy supporting 50+ VPN providers via Gluetun. See [GLUETUN.md](GLUETUN.md) for full
configuration and usage details.
```yaml
proxy_providers:
gluetun:
providers:
windscribe:
vpn_type: openvpn
credentials:
username: "YOUR_OPENVPN_USERNAME"
password: "YOUR_OPENVPN_PASSWORD"
```
Usage: `--proxy gluetun:windscribe:us`
---
## headers (dict)
Case-Insensitive dictionary of headers that all Services begin their Request Session state with.
All requests will use these unless changed explicitly or implicitly via a Server response.
These should be sane defaults and anything that would only be useful for some Services should not
be put here.
Avoid headers like 'Accept-Encoding' as that would be a compatibility header that the underlying
HTTP backend (rnet) will set for you as part of its browser impersonation profile.
I recommend using,
```yaml
Accept-Language: "en-US,en;q=0.8"
User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36"
```
---
## HTTP Session Backend
unshackle uses [`rnet`](https://github.com/0x676e67/rnet) (Rust + BoringSSL) for HTTP with TLS
fingerprinting. `RnetSession` is a drop-in `requests.Session` replacement and is what
`self.session` exposes to services. It supports:
- Browser/app impersonation via named `rnet.Impersonate` presets (Chrome, Edge, Firefox, Safari,
OkHttp, etc.) — picks JA3, ALPN, HTTP/2 SETTINGS and header order to match the chosen client.
- Native rnet proxy support (HTTP, HTTPS, SOCKS5) — used by all proxy providers below.
- Cookie-jar and `requests`-style `data=` / `json=` / `headers=` kwargs for compatibility.
The legacy `curl_cffi` backend has been removed. The config key is still spelled
`curl_impersonate` for backward compatibility, but its value now selects an rnet preset.
### curl_impersonate (dict)
```yaml
curl_impersonate:
browser: Chrome131 # exact rnet.Impersonate preset name
```
`browser` must be an exact `rnet.Impersonate` preset name (e.g. `Chrome131`, `Chrome124`,
`Edge101`, `Firefox133`, `Safari18`, `OkHttp4_12`). See the rnet README for the full list.
Default when unset: `Chrome131`.
---
+287
View File
@@ -0,0 +1,287 @@
# Output & Naming Configuration
This document covers output file organization and naming configuration options.
## filenames (dict)
Override the default filenames used across unshackle.
The filenames use various variables that are replaced during runtime.
The following filenames are available and may be overridden:
- `log` - Log filenames. Uses `{name}` and `{time}` variables.
- `debug_log` - Debug log filenames. Uses `{service}` and `{time}` variables.
- `config` - Service configuration filenames.
- `root_config` - Root configuration filename.
- `chapters` - Chapter export filenames. Uses `{title}` and `{random}` variables.
- `subtitle` - Subtitle export filenames. Uses `{id}` and `{language}` variables.
For example,
```yaml
filenames:
log: "unshackle_{name}_{time}.log"
debug_log: "unshackle_debug_{service}_{time}.jsonl"
config: "config.yaml"
root_config: "unshackle.yaml"
chapters: "Chapters_{title}_{random}.txt"
subtitle: "Subtitle_{id}_{language}.srt"
```
---
## output_template (dict)
Configure custom output filename templates for movies, series, and songs.
This is **required** in your `unshackle.yaml` — a warning is shown if not configured.
Available variables: `{title}`, `{year}`, `{season}`, `{episode}`, `{season_episode}`, `{episode_name}`,
`{quality}`, `{resolution}`, `{source}`, `{audio}`, `{audio_channels}`, `{audio_full}`,
`{video}`, `{hdr}`, `{hfr}`, `{atmos}`, `{dual}`, `{multi}`, `{tag}`, `{edition}`, `{repack}`,
`{lang_tag}`, `{track_number}`, `{artist}`, `{album}`, `{disc}`
Add `?` suffix to make a variable conditional (omitted when empty): `{year?}`, `{hdr?}`, `{repack?}`
```yaml
output_template:
# Scene-style (dot-separated)
movies: '{title}.{year}.{repack?}.{edition?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}'
series: '{title}.{year?}.{season_episode}.{episode_name?}.{repack?}.{edition?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}'
songs: '{track_number}.{title}.{repack?}.{edition?}.{source?}.WEB-DL.{audio_full}.{atmos?}-{tag}'
# Plex-friendly (space-separated)
# movies: '{title} ({year}) {quality}'
# series: '{title} {season_episode} {episode_name?}'
# songs: '{track_number}. {title}'
```
Example outputs:
- Scene movies: `Example.Movie.2024.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG`
- Scene movies (REPACK): `Example.Movie.2024.REPACK.2160p.EXAMPLE.WEB-DL.DDP5.1.H.265-TAG`
- Scene series: `Example.Show.2024.S01E01.Pilot.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG`
- Plex movies: `Example Movie (2024) 1080p`
### folder (optional)
Controls the folder name for downloaded content. Uses the same template variables as the file templates above.
If not configured, the default folder naming is used:
- Movies: `Title (Year)`
- Series: Derived from the `series` template with episode-specific variables removed
- Songs: `Artist - Album (Year)`
`folder` accepts either a single string (applies to all title kinds) or a mapping with per-kind
templates keyed by `movies`, `series`, and/or `songs`. Unknown keys are warned about and ignored.
```yaml
output_template:
movies: '{title}.{year}.{repack?}.{edition?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}'
series: '{title}.{year?}.{season_episode}.{episode_name?}.{repack?}.{edition?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}'
songs: '{track_number}.{title}.{repack?}.{edition?}.{source?}.WEB-DL.{audio_full}.{atmos?}-{tag}'
# Scene-style folder (single template, applies to all kinds)
folder: '{title}.{year?}.{repack?}.{edition?}.{lang_tag?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}'
# Plex-friendly folder
# folder: '{title} ({year?})'
# Per-kind folder templates
# folder:
# movies: '{title} ({year})'
# series: '{title} ({year?})'
# songs: '{artist} - {album} ({year?})'
```
Example outputs:
- Scene folder: `Example.Show.2024.S01.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG/`
- Plex folder: `Example Show (2024)/`
---
---
## language_tags (dict)
Automatically adds language-based identifiers (e.g., `DANiSH`, `NORDiC`, `DKsubs`) to output filenames
based on audio and subtitle track languages. Use `{lang_tag?}` in your `output_template` to place the tag.
Rules are evaluated in order; the first matching rule wins. All conditions within a single rule
must match (AND logic). If no rules match, `{lang_tag?}` is cleanly removed from the filename.
### Conditions
| Condition | Type | Description |
|-----------|------|-------------|
| `audio` | string | Matches if any selected audio track has this language |
| `subs_contain` | string | Matches if any selected subtitle has this language |
| `subs_contain_all` | list | Matches if subtitles include ALL listed languages |
Language matching uses fuzzy matching (e.g., `en` matches `en-US`, `en-GB`).
### Example: Nordic tagging
```yaml
language_tags:
rules:
- audio: da
tag: DANiSH
- audio: sv
tag: SWEDiSH
- audio: nb
tag: NORWEGiAN
- audio: en
subs_contain_all: [da, sv, nb]
tag: NORDiC
- audio: en
subs_contain: da
tag: DKsubs
output_template:
movies: '{title}.{year?}.{lang_tag?}.{quality}.{source}.WEB-DL.{audio_full}.{video}-{tag}'
```
Example outputs:
- Danish audio: `Example.Show.S01E01.DANiSH.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG`
- English audio + multiple Nordic subs: `Example.Show.S01E01.NORDiC.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG`
- English audio + Danish subs only: `Example.Show.S01E01.DKsubs.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG`
- No matching languages: `Example.Show.S01E01.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG`
### Example: Other regional tags
```yaml
language_tags:
rules:
- audio: nl
tag: DUTCH
- audio: de
tag: GERMAN
- audio: fr
subs_contain: en
tag: ENGFR
- audio: fr
tag: FRENCH
```
---
## unicode_filenames (bool)
Allow Unicode characters in output filenames. When `false`, Unicode characters are transliterated
to ASCII equivalents. Default: `false`.
---
## tag (str)
Group or Username to postfix to the end of download filenames following a dash.
Use `{tag}` in your output template to include it.
For example, `tag: "J0HN"` will have `-J0HN` at the end of all download filenames.
---
## tag_group_name (bool)
Enable/disable tagging downloads with your group name when `tag` is set. Default: `true`.
---
## tag_imdb_tmdb (bool)
Enable/disable tagging downloaded files with IMDB/TMDB/TVDB identifiers (when available). Default: `true`.
---
## muxing (dict)
- `set_title`
Set the container title to `Show SXXEXX Episode Name` or `Movie (Year)`. Default: `true`
- `merge_audio`
Merge all audio tracks into each output file. Default: `true`
- `true`: All selected audio tracks are muxed into one MKV per quality.
- `false`: Separate MKV per (quality, audio_codec) combination.
For example: `Title.1080p.AAC.mkv`, `Title.1080p.EC3.mkv`.
Note: The `--split-audio` CLI flag overrides this setting. When `--split-audio` is passed,
`merge_audio` is effectively set to `false` for that run.
- `default_language` (dict)
Override which track is flagged as the default in the muxed MKV, regardless
of the title's original language. Useful when you always want your player to
open on a specific language (e.g. always default to Polish audio even on
English originals). Only affects the MKV `--default-track` flag — track
selection (`-l`, `--alang`, etc.) is unchanged. All keys are optional; each
track type falls back to its previous default rule when the configured
language isn't present in the manifest.
- `audio`: BCP-47 tag (e.g. `pl`, `en`, `pt-BR`). Wins over `is_original_lang`.
The `--original-flag` continues to mark the true original-audio track.
- `video`: BCP-47 tag. Wins over the title-language / first-track rule.
- `subtitle`: BCP-47 tag. Wins over the "forced sub matching audio" rule.
Languages are matched with the same close-match logic used elsewhere
(`pt` matches `pt-BR`, etc.). Supports per-service overrides like the rest
of `muxing`.
```yaml
muxing:
default_language:
audio: pl
video: pl
subtitle: pl
```
---
## chapter_fallback_name (str)
The Chapter Name to use when exporting a Chapter without a Name.
The default is no fallback name at all and no Chapter name will be set.
The fallback name can use the following variables in f-string style:
- `{i}`: The Chapter number starting at 1.
E.g., `"Chapter {i}"`: "Chapter 1", "Intro", "Chapter 3".
- `{j}`: A number starting at 1 that increments any time a Chapter has no title.
E.g., `"Chapter {j}"`: "Chapter 1", "Intro", "Chapter 2".
These are formatted with f-strings, directives are supported.
For example, `"Chapter {i:02}"` will result in `"Chapter 01"`.
---
## directories (dict)
Override the default directories used across unshackle.
The directories are set to common values by default.
The following directories are available and may be overridden,
- `commands` - CLI Command Classes.
- `services` - Service Classes.
- `vaults` - Vault Classes.
- `fonts` - Font files (ttf or otf).
- `downloads` - Downloads.
- `temp` - Temporary files or conversions during download.
- `cache` - Expiring data like Authorization tokens, or other misc data.
- `cookies` - Expiring Cookie data.
- `logs` - Logs.
- `exports` - JSON sidecar exports written when `--export` is used on `dl`.
- `wvds` - Widevine Devices.
- `prds` - PlayReady Devices.
- `dcsl` - Device Certificate Status List.
Notes:
- `services` accepts either a single directory or a list of directories to search for service modules.
For example,
```yaml
directories:
downloads: "D:/Downloads/unshackle"
temp: "D:/Temp/unshackle"
```
There are directories not listed that cannot be modified as they are crucial to the operation of unshackle.
---
+246
View File
@@ -0,0 +1,246 @@
# Service Integration & Authentication Configuration
This document covers service-specific configuration, authentication, and metadata integration options.
## services (dict)
Configuration data for each Service. The Service will have the data within this section merged into the per-service
`config.yaml` (located in the service's directory) before being provided to the Service class.
Think of this config to be used for more sensitive configuration data, like user or device-specific API keys, IDs,
device attributes, and so on. A per-service `config.yaml` file is typically shared and not meant to be modified,
so use this for any sensitive configuration data.
The Key is the Service Tag, but can take any arbitrary form for its value. It's expected to begin as either a list or
a dictionary.
For example,
```yaml
EXAMPLE:
client:
auth_scheme: MESSO
# ... more sensitive data
```
### Per-Service Configuration Overrides
You can override many global configuration options on a per-service basis by nesting them under the
service tag in the `services` section. Supported override keys include: `dl`, `subtitle`, `muxing`,
`headers`, `proxy_map`, `title_map`, and more.
Overrides are merged with global config (not replaced) -- only specified keys are overridden, others
use global defaults. CLI arguments always take priority over service-specific config.
For example,
```yaml
services:
RATE_LIMITED_SERVICE:
dl:
downloads: 2 # Limit concurrent track downloads
workers: 4 # Reduce workers to avoid rate limits
headers:
User-Agent: "..." # Service-specific UA override
```
Note: unshackle uses a single unified `requests`-based downloader. The legacy `aria2c`,
`n_m3u8dl_re`, and `curl_impersonate` override sections have been removed.
### title_map (dict)
Rewrites service-provided titles before naming and output. Some services name a title differently
from how you want it stored, which can break library matching (e.g. a regional variant reusing the
international name). Keys are the exact title string the service returns; values are the desired
output title.
```yaml
services:
EXAMPLE:
title_map:
Service Title: Desired Title
```
Episodes are matched on their show title, Movies and Songs on their name. The remap is applied
after the title cache (so edits take effect without a cache reset) and before any `--enrich`
override (so an explicit enrich still wins).
It applies on the local `dl` path, the `import` command, and the remote client (`dl --remote`).
For remote services the **client's** `title_map` is applied to the titles returned by the server,
so you can rename titles for services you don't have installed locally. The server sends raw
titles and does not remap, leaving the final name fully under the client's control.
### Service Class Conventions
Each service directory under `unshackle/services/` exports a class extending
`unshackle.core.service.Service`. The class name must match the directory name (the service tag).
Key class variables (defined on `Service` or by service-level idiom):
- `ALIASES: tuple[str, ...]` — alternative tags accepted on the CLI. Empty by default.
- `GEOFENCE: tuple[str, ...]` — ISO country codes the service is available in. Empty == no geofence.
- `TITLE_RE: str` — regex (with named groups, e.g. `(?P<id>...)`, `(?P<type>...)`) used by the
service to parse the CLI title argument. Service-level idiom, not declared on the base class.
- `NO_SUBTITLES: bool` — service-level idiom indicating the service has no subtitle tracks.
`self.*` helpers available after `super().__init__(ctx)`:
- `self.session` — pre-configured HTTP session (`requests.Session`, or `RnetSession` when TLS
impersonation is active). Cookies, headers, proxies pre-applied.
- `self.config` — merged service config (per-service `config.yaml` plus the `services.<TAG>` block
from `unshackle.yaml`).
- `self.log` — `logging.Logger` named for the service class.
- `self.cache` — generic `Cacher` for arbitrary key/value persistence.
- `self.title_cache` — specialized `TitleCacher` for title metadata.
- `self.track_request` — `TrackRequest` built from CLI flags. Fields: `codecs: list[Video.Codec]`,
`ranges: list[Video.Range]` (defaults to `[SDR]`), `best_available: bool`. Services may
read or rewrite these (e.g. force HEVC for HDR ranges).
- `self.credential` — set during `authenticate()`; `None` if cookies-only.
- `self.current_region` — lowercase ISO country code from proxy/geolocation, or `None`.
- `self.request_input(prompt: str) -> str` — interactive prompt. Falls through to `input()`
locally; under `serve`, the attached `InputBridge` relays the prompt to the remote client.
Driving CLI flags (parsed into `self.track_request`):
- `-v` / `--vcodec` — comma-separated `Video.Codec` list (e.g. `H264,H265`).
- `-a` / `--acodec` — comma-separated audio codec list.
- `-r` / `--range` — comma-separated `Video.Range` list (`SDR`, `HDR10`, `HDR10+`, `DV`,
`HYBRID`). Defaults to `[SDR]`.
- `-q` / `--quality` — resolution list.
- `--vbitrate-range` / `--abitrate-range` — `MIN-MAX` kbps windows.
---
## credentials (dict[str, str|list|dict])
Specify login credentials to use for each Service, and optionally per-profile.
For example,
```yaml
EXAMPLE: jane@example.tld:LoremIpsum100 # directly
EXAMPLE2: # or per-profile, optionally with a default
default: jane@example.tld:LoremIpsum99 # <-- used by default if -p/--profile is not used
james: james@example.tld:TheFriend97
john: john@example.tld:LoremIpsum98
EXAMPLE3: # the `default` key is not necessary, but no credential will be used by default
john: john@example.tld:SecretPassword123
```
The value should be in string form, i.e. `john@example.tld:password123` or `john:password123`.
Any arbitrary values can be used on the left (username/password/phone) and right (password/secret).
You can also specify these in list form, i.e., `["john@example.tld", ":PasswordWithAColon"]`.
If you specify multiple credentials with keys like the `EXAMPLE2` and `EXAMPLE3` example above, then you should
use a `default` key or no credential will be loaded automatically unless you use `-p/--profile`. You
do not have to use a `default` key at all.
Please be aware that this information is sensitive and to keep it safe. Do not share your config.
---
## tmdb_api_key (str)
API key for The Movie Database (TMDB). This is used for tagging downloaded files with TMDB,
IMDB and TVDB identifiers. Leave empty to disable automatic lookups.
To obtain a TMDB API key:
1. Create an account at <https://www.themoviedb.org/>
2. Go to <https://www.themoviedb.org/settings/api> to register for API access
3. Fill out the API application form with your project details
4. Once approved, you'll receive your API key
For example,
```yaml
tmdb_api_key: cf66bf18956kca5311ada3bebb84eb9a # Not a real key
```
**Note**: Keep your API key secure and do not share it publicly. This key is used by the `core/providers/tmdb.py` metadata provider to fetch metadata from TMDB for proper file tagging and ID enrichment.
---
## simkl_client_id (str)
Client ID for SIMKL API integration. SIMKL is used as a metadata source for improved title matching and tagging,
especially when a TMDB API key is not configured.
To obtain a SIMKL Client ID:
1. Create an account at <https://simkl.com/>
2. Go to <https://simkl.com/settings/developer/>
3. Register a new application to receive your Client ID
For example,
```yaml
simkl_client_id: "your_client_id_here"
```
**Note**: While optional, having a SIMKL Client ID improves metadata lookup reliability. SIMKL serves as an alternative or fallback metadata source to TMDB. This is used by the `core/providers/simkl.py` metadata provider.
---
## ipinfo_api_key (str)
Optional API token for [ipinfo.io](https://ipinfo.io). When set, unshackle uses the free authenticated **Lite** endpoint (`https://api.ipinfo.io/lite/me`), which has substantially higher rate limits than the anonymous endpoint and returns richer fields (ASN, organization name, continent). Leave empty to use the anonymous ipinfo.io endpoint, with [ip-api.in](https://ip-api.in) as a final fallback.
To obtain an ipinfo.io token:
1. Sign up for a free account at <https://ipinfo.io/signup>
2. Copy the token from your dashboard
For example,
```yaml
ipinfo_api_key: "12a3b45cd678ef" # Not a real key
```
**Note**: The token is only ever sent to `api.ipinfo.io` as a per-request `Authorization` header — it is never attached to your session for service requests. Used by `core/utils/ip_info.py` for region detection and proxy verification.
---
## title_cache_enabled (bool)
Enable/disable caching of title metadata to reduce redundant API calls. Default: `true`.
---
## title_cache_time (int)
Cache duration in seconds for title metadata. Default: `1800` (30 minutes).
---
## title_cache_max_retention (int)
Maximum retention time in seconds for serving slightly stale cached title metadata when API calls fail.
Default: `86400` (24 hours). Effective retention is `min(title_cache_time + grace, title_cache_max_retention)`.
---
## debug (bool)
Enable structured JSON debug logging for troubleshooting and service development. Default: `false`.
When enabled (via config or the `--debug` CLI flag):
- Creates JSON Lines (`.jsonl`) log files with complete debugging context
- Logs: session info, CLI params, service config, CDM details, authentication, titles, tracks metadata,
DRM operations, vault queries, errors with stack traces
- File location: `logs/unshackle_debug_{service}_{timestamp}.jsonl`
---
## debug_keys (bool)
Log decryption keys in debug logs. Default: `false`.
When `true`, actual content encryption keys (CEKs) are included in debug log output. Useful for
debugging key retrieval and decryption issues.
**Security note:** Passwords, tokens, cookies, and session tokens are always redacted regardless
of this setting. Only content keys (`content_key`, `key` fields) are affected. Key IDs (`kid`),
key counts, and other metadata are always logged.
---
+73
View File
@@ -0,0 +1,73 @@
# Subtitle Processing Configuration
This document covers subtitle processing and formatting options under the top-level `subtitle:` key in `unshackle.yaml`.
For the canonical example, see `unshackle/unshackle-example.yaml`.
## subtitle (dict)
Control subtitle conversion, SDH (hearing-impaired) stripping, formatting preservation, and output behavior.
- `conversion_method`: How to convert subtitles between formats. Default: `auto`.
- `auto`: Smart routing - subby for WebVTT/fVTT/SAMI; for SSA/ASS/MicroDVD/MPL2/TMP use SubtitleEdit when available, otherwise pysubs2; standard pycaption/SubtitleEdit pipeline for everything else.
- `subby`: Always use subby with `CommonIssuesFixer` (falls back to standard if the source codec isn't supported by subby).
- `subtitleedit`: Prefer SubtitleEdit when available; otherwise fall back to the standard pycaption pipeline.
- `pycaption`: Use only the pycaption library (no SubtitleEdit, no subby). Limited to SRT, TTML, and WebVTT outputs.
- `pysubs2`: Use pysubs2 (supports SRT, SSA, ASS, WebVTT, TTML, SAMI, MicroDVD, MPL2, TMP).
- `sdh_method`: How to strip SDH cues. Default: `auto`.
- `auto`: Try subby for SRT first, then SubtitleEdit (when `conversion_method` is `auto`/`subtitleedit` and the binary is available), then subtitle-filter as the final fallback.
- `subby`: Use subby's `SDHStripper`. **Only operates on SRT**; for other codecs the call returns without stripping.
- `subtitleedit`: Use SubtitleEdit's `/RemoveTextForHI` when the binary is available; otherwise falls through to subtitle-filter.
- `filter-subs`: Use the `subtitle-filter` library directly (`rm_fonts`, `rm_ast`, `rm_music`, `rm_effects`, `rm_names`, `rm_author`).
- `strip_sdh`: Enable/disable automatic SDH stripping for tracks flagged as SDH. Default: `true`.
- `convert_before_strip`: When falling through to the subtitle-filter path, auto-convert non-SRT subtitles to SRT first for better compatibility. Default: `true`. Has no effect when SubtitleEdit handles stripping directly.
- `preserve_formatting`: Keep original subtitle tags and positioning during WebVTT processing. When `true`, sanitized WebVTT is written back without round-tripping through pycaption, preserving tags like `<i>`, `<b>`, and `line:` positioning. Default: `true`.
- `output_mode`: Controls how subtitles are included in the output. Default: `mux`.
- `mux`: Embed subtitles in the MKV container only.
- `sidecar`: Save subtitles as separate files only (not muxed).
- `both`: Embed in the MKV container and save as sidecar files.
- `sidecar_format`: Format for sidecar subtitle files (used when `output_mode` is `sidecar` or `both`). Default: `srt`.
- `srt`: SubRip.
- `vtt`: WebVTT.
- `ass`: Advanced SubStation Alpha.
- `original`: Keep the subtitle in its current format without conversion.
Example:
```yaml
subtitle:
conversion_method: auto
sdh_method: auto
strip_sdh: true
convert_before_strip: true
preserve_formatting: true
output_mode: mux
sidecar_format: srt
```
## WebVTT Sanitization (automatic, not configurable)
After download, WebVTT and segmented WebVTT (`fVTT`/`WVTT`) tracks pass through a fixed sanitization pipeline before any conversion or muxing:
1. **Segment merge** — segmented DASH/HLS WebVTT is stitched via `merge_segmented_webvtt` (uses pysubs2 for lenient parsing when `conversion_method` is `auto` or `pysubs2`, otherwise pycaption directly).
2. **Negative timestamps** — `sanitize_webvtt_timestamps` rewrites `-HH:MM:SS.mmm` cues to `00:00:00.000`.
3. **Cue identifiers** — `sanitize_webvtt_cue_identifiers` strips letter+digit IDs (e.g. `Q0`, `S12`) on their own line before a timing line, which otherwise confuse parsers like pysubs2.
4. **Overlapping cues** — `merge_overlapping_webvtt_cues` collapses cues with start times within 50 ms and matching end times into a single multi-line cue, ordered by `line:` percentage (lower % = higher on screen = first line).
5. **Fallback hardening** — when `preserve_formatting` is `false` and the first pycaption parse fails, `sanitize_webvtt` retries with a `WEBVTT` header guard, hour-padded timings, and another negative-timestamp pass; if that still fails, the sanitized text is written as-is.
`sanitize_broken_webvtt` and `space_webvtt_headers` additionally run inside `Subtitle.parse()` to drop malformed `-->` lines and reflow merged-segment headers. `merge_same_cues` and `filter_unwanted_cues` (drops `&nbsp;`/whitespace-only cues) run only on the pycaption path.
These behaviors are intentional and have no config knobs — they apply to every WebVTT track regardless of `conversion_method`.
## Related
- Filename sanitization (e.g. parenthesis handling, unidecode bracket artifacts from PR #105) lives in `unshackle/core/utilities.py::sanitize_filename` and is governed by `output_template`, not the `subtitle:` config block.
- Subtitle codec support and the conversion matrix are defined in `unshackle/core/tracks/subtitle.py`.
---
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-2270
View File
File diff suppressed because it is too large Load Diff
+61
View File
@@ -0,0 +1,61 @@
@echo off
setlocal EnableExtensions EnableDelayedExpansion
echo.
echo === Unshackle setup (Windows) ===
echo.
where uv >nul 2>&1
if %errorlevel% equ 0 (
echo [OK] uv is already installed.
goto install_deps
)
echo [..] uv not found. Installing...
powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://astral.sh/uv/install.ps1 | iex"
if %errorlevel% neq 0 (
echo [ERR] Failed to install uv.
echo PowerShell may be blocking scripts. Try:
echo Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
echo or install manually: https://docs.astral.sh/uv/getting-started/installation/
pause
exit /b 1
)
set "UV_BIN="
for %%D in ("%USERPROFILE%\.local\bin" "%LOCALAPPDATA%\Programs\uv\bin" "%USERPROFILE%\.cargo\bin") do (
if exist "%%~fD\uv.exe" set "UV_BIN=%%~fD"
)
if not defined UV_BIN (
echo [WARN] Could not locate uv.exe. You may need to reopen your terminal.
) else (
set "PATH=%UV_BIN%;%PATH%"
)
:: Verify
uv --version >nul 2>&1
if %errorlevel% neq 0 (
echo [ERR] uv still not reachable in this shell. Open a new terminal and re-run this script.
pause
exit /b 1
)
echo [OK] uv installed and reachable.
:install_deps
echo.
uv sync
if %errorlevel% neq 0 (
echo [ERR] Dependency install failed. See errors above.
pause
exit /b 1
)
echo.
echo Installation completed successfully!
echo Try:
echo uv run unshackle --help
echo.
pause
endlocal
BIN
View File
Binary file not shown.
+157
View File
@@ -0,0 +1,157 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "unshackle"
version = "5.1.0"
description = "Modular Movie, TV, and Music Archival Software."
authors = [{ name = "unshackle team" }]
requires-python = ">=3.10,<3.13"
readme = "README.md"
license = "GPL-3.0-only"
keywords = [
"python",
"downloader",
"drm",
"widevine",
]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Console",
"Intended Audience :: End Users/Desktop",
"Natural Language :: English",
"Operating System :: OS Independent",
"Topic :: Multimedia :: Video",
"Topic :: Security :: Cryptography",
]
dependencies = [
"appdirs>=1.4.4,<2",
"Brotli>=1.1.0,<2",
"click>=8.1.8,<9",
"construct>=2.8.8,<3",
"crccheck>=1.3.0,<2",
"filelock>=3.20.3,<4",
"fonttools>=4.60.2,<5",
"jsonpickle>=3.0.4,<5",
"langcodes>=3.4.0,<4",
"lxml>=5.2.1,<7",
"protobuf>=4.25.3,<7",
"pycaption>=2.2.6,<3",
"pycryptodomex>=3.20.0,<4",
"pyjwt>=2.12.0,<3",
"pymediainfo>=6.1.0,<8",
"pymp4>=1.4.0,<2",
"pymysql>=1.1.0,<2",
"pywidevine[serve]>=1.8.0,<2",
"PyYAML>=6.0.1,<7",
"requests[socks]>=2.32.5,<3",
"rich>=13.7.1,<15",
"rlaphoenix.m3u8>=3.4.0,<4",
"ruamel.yaml>=0.18.6,<0.19",
"sortedcontainers>=2.4.0,<3",
"subtitle-filter>=1.4.9,<2",
"Unidecode>=1.3.8,<2",
"urllib3>=2.6.3,<3",
"chardet>=5.2.0,<6",
"pyplayready>=0.8.3,<0.9",
"cryptography>=45.0.0,<47",
"subby",
"aiohttp>=3.13.4,<4",
"aiohttp-swagger3>=0.9.0,<1",
"pysubs2>=1.7.0,<2",
"PyExecJS>=1.5.1,<2",
"pycountry>=24.6.1",
"language-data>=1.4.0",
"wasmtime>=41.0.0",
"animeapi-py>=0.6.0",
"rnet>=2.4.2",
"bandit>=1.9.4",
]
[project.urls]
Homepage = "https://github.com/unshackle-dl/unshackle"
Repository = "https://github.com/unshackle-dl/unshackle"
Issues = "https://github.com/unshackle-dl/unshackle/issues"
Discussions = "https://github.com/unshackle-dl/unshackle/discussions"
Changelog = "https://github.com/unshackle-dl/unshackle/blob/master/CHANGELOG.md"
[project.scripts]
unshackle = "unshackle.core.__main__:main"
[dependency-groups]
dev = [
"pre-commit>=3.7.0,<5",
"virtualenv>=20.36.1,<22",
"mypy>=1.9.0,<2",
"mypy-protobuf>=3.6.0,<4",
"types-protobuf>=4.24.0.20240408,<7",
"types-PyMySQL>=1.1.0.1,<2",
"types-requests>=2.31.0.20240406,<3",
"isort>=5.13.2,<8",
"ruff>=0.3.7,<0.15",
]
test = [
"pytest>=8.3.0,<9",
"pytest-asyncio>=0.24.0,<1",
"pytest-aiohttp>=1.0.5,<2",
"responses>=0.25.0,<1",
"PyYAML>=6.0.1,<7",
]
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
markers = [
"unit: fast, mocked tests (default)",
"live: end-to-end tests against a running serve (opt-in via --live)",
"slow: tests that hit real services and may take >10s",
]
filterwarnings = [
"ignore::DeprecationWarning:click.*",
"ignore::DeprecationWarning:unshackle.core.services",
"ignore::DeprecationWarning:ast",
]
addopts = "-ra --strict-markers"
[tool.hatch.build.targets.wheel]
packages = ["unshackle"]
[tool.hatch.build.targets.sdist]
include = [
"CHANGELOG.md",
"README.md",
"LICENSE",
]
[tool.ruff]
force-exclude = true
line-length = 120
[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "W"]
[tool.isort]
line_length = 118
[tool.mypy]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_untyped_defs = true
follow_imports = "silent"
ignore_missing_imports = true
no_implicit_optional = true
[tool.bandit]
exclude_dirs = ["tests", ".venv"]
skips = [
"B101", # assert used legitimately in non-test code
"B324", # MD5/SHA1 used for identifiers and cache keys, not security
"B413", # false positive: pycryptodome uses Crypto namespace, not deprecated pyCrypto
"B314", # XML from DRM protocol headers, not untrusted user input
"B608", # SQL table names from internal service tags, not user input; parameterized values
]
[tool.uv.sources]
unshackle = { workspace = true }
subby = { git = "https://github.com/vevv/subby.git", rev = "1ea6a52028c5bea8177c8abc91716d74e4d097e1" }
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More