mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-14 04:55:07 +00:00
Merge branch 'dev' into feat/downloader-speed
Catch the branch up with dev (45 commits) so testers can run the perf work against current dev. Conflict resolutions: - downloaders/requests.py: keep the branch's racing-gated read1, adaptive controller speed reporting and byte-range slice guards; take dev's content-encoding checks (raw reads are skipped on an encoded body) and its rolling SpeedWindow for the no-controller speed fallback. The single-URL ranged-parallel gate now requires no speed limit and no caller Range. - service.py / session.py: keep the branch's TimeoutHTTPAdapter and retry config, raise POOL_MAX_SIZE to 64 so dev's pool-starvation fix survives (pool_block=True stalls threads on a pool smaller than the in-flight count). - docs: dev split configuration.md into per-area pages, so the branch's rnet network option rows moved to reference/configuration/network.md.
This commit is contained in:
@@ -96,17 +96,24 @@ unshackle dl [OPTIONS] SERVICE [SERVICE ARGS...]
|
||||
| `-sl`, `--s-lang` | `all` | Subtitle language(s). |
|
||||
| `--require-subs` | - | Required subtitle langs; keeps **all** subs only if these exist. **Cannot combine with `--s-lang`.** |
|
||||
| `-fs`, `--forced-subs` | off | Include forced subtitle tracks. |
|
||||
| `--exact-lang` | off | Exact matching only: `-l es-419` matches `es-419`, not `es-ES`. |
|
||||
| `-fsl`, `--forced-s-lang` | none | Language(s) wanted for forced subtitles; implies `-fs`. |
|
||||
| `--exact-lang` | off | Exact matching only: `-l es-419` matches `es-419`, not `es-ES`. Applies to selection and to sort order. |
|
||||
| `--sub-format` | - | Output subtitle format (`SRT`/`srt`, `VTT`/`webvtt`, `ASS`/`ssa`, `TTML`, `SMI`, ...), or `original` to keep the source format. |
|
||||
|
||||
!!! tip "These flags select languages, they do not order them"
|
||||
Naming languages with `-l` or `-sl` removes the others. To keep every language but put
|
||||
your preferred ones first, use `audio.language_priority` and
|
||||
[`subtitle.language_priority`](../reference/configuration/download.md#subtitle) in your
|
||||
configuration file.
|
||||
|
||||
The special language tokens `orig`, `all`, and `best` are honoured everywhere a language is expected.
|
||||
|
||||
### Title & episode selection
|
||||
|
||||
| Flag | Description |
|
||||
|---|---|
|
||||
| `-w`, `--wanted` | Wanted episodes, e.g. `S01-S05,S07`, `S01E01-S02E03`. Supports exclusions with a leading `-` (e.g. `-S03`). |
|
||||
| `--select-titles` | Interactively select titles (series only). **Cannot combine with `-w`.** |
|
||||
| `-w`, `--wanted` | Wanted episodes, e.g. `S01-S05,S07`, `S01E01-S02E03`. Supports exclusions with a leading `-` (e.g. `-S03`). For a [split episode](downloading.md#split-episodes), `.N` picks one part (`S01E01.2`), a range must stay inside the episode (`S01E01.1-S01E01.3`), and `S01E01` on its own takes every part. |
|
||||
| `--select-titles` | Interactively select what to download: episodes of a series, or films when a title has more than one. **Cannot combine with `-w`.** |
|
||||
| `--latest-episode` | Download only the single most recent episode. |
|
||||
| `--list-titles` | List titles only; do not download. |
|
||||
|
||||
@@ -145,8 +152,22 @@ Keep only certain track types, or skip certain track types. Attachments are alwa
|
||||
|---|---|
|
||||
| `--tmdb` | TMDB ID (integer). |
|
||||
| `--imdb` | IMDb ID, e.g. `tt1375666`. |
|
||||
| `--animeapi` | AnimeAPI ID, e.g. `mal:12345` or `anilist:98765` (defaults to MAL). Back-fills TMDB/IMDb. |
|
||||
| `--tvdb` | TVDB ID (integer). Skips the series lookup that `--tvdb-order` would otherwise do. |
|
||||
| `--animeapi` | AnimeAPI ID, e.g. `mal:12345` or `anilist:98765` (defaults to MAL). Back-fills TMDB/IMDb/TVDB. |
|
||||
| `--enrich` | Override show title/year from the external source. **Requires** one of `--tmdb`, `--imdb`, or `--animeapi`. |
|
||||
| `--tvdb-order` | Renumber episodes to a TVDB season order: `official` (aired), `dvd`, `absolute`, `alternate`, or `regional`. Needs `tvdb_api_key`. |
|
||||
|
||||
#### Episode ordering
|
||||
|
||||
A service does not always number a series the way TVDB's aired order does. Some services list
|
||||
Futurama in TVDB's `alternate` (Streaming) order, for example. `--tvdb-order` works out which
|
||||
order the service used, then renumbers the episodes into the order you asked for.
|
||||
|
||||
!!! note "Orders that do not cover the whole series"
|
||||
An order can omit episodes the service carries; TVDB's `dvd` order for Futurama leaves out
|
||||
the four movies. Those episodes keep their original numbering. If that would give two
|
||||
episodes the same season/episode slot, and so the same filename, unshackle logs an error
|
||||
and keeps the service's numbering unchanged. Pick an order that covers the whole series.
|
||||
|
||||
### DRM, keys & decryption
|
||||
|
||||
@@ -162,7 +183,7 @@ Keep only certain track types, or skip certain track types. Attachments are alwa
|
||||
|---|---|
|
||||
| `--proxy` | Proxy URI, a 2-letter country code resolved from configured providers, or `provider:region` (e.g. `nordvpn:ca`, `gluetun:us`, `protonvpn:de:berlin`). |
|
||||
| `--no-proxy` | Force-disable all proxy use. |
|
||||
| `--no-proxy-download` | Bypass the proxy for **segment downloads only** (manifest, licence, and auth stay proxied). |
|
||||
| `--no-proxy-download` | Bypass the proxy for **all downloads** (manifest, licence, and auth stay proxied). |
|
||||
| `--remote` | Use a remote unshackle server. |
|
||||
| `--server` | Name a remote server from the `remote_services` config. |
|
||||
|
||||
@@ -174,6 +195,7 @@ Keep only certain track types, or skip certain track types. Attachments are alwa
|
||||
| `--adaptive-workers` | off | Scale per-track segment workers dynamically (up to the `--workers` cap) based on measured CDN throughput and errors. |
|
||||
| `--download-processes` | `1` | Split large segment batches (24+) across this many download processes to exceed the single-process throughput cap on multi-gigabit connections. |
|
||||
| `--downloads` | `1` | Number of tracks downloaded concurrently. |
|
||||
| `--speed-limit` | unlimited | Cap total download speed across all threads and tracks, e.g. `500k`, `5M`, `1.5G` or plain bytes/sec. Values are bytes, not bits (`5M` = 5.0 MB/s). `off` disables a configured limit. |
|
||||
| `--no-cache` | off | Bypass the title cache. |
|
||||
| `--reset-cache` | off | Clear the title cache. |
|
||||
| `--list` | off | List available/would-be-downloaded tracks; do not download. |
|
||||
|
||||
+119
-22
@@ -4,7 +4,7 @@
|
||||
streaming service, selects exactly the tracks you asked for, acquires and applies
|
||||
decryption keys, and muxes everything into a finished Matroska file. The flags below
|
||||
cover quality and codec selection, language and subtitle handling, episode ranges, and
|
||||
the download flow itself, with examples throughout.
|
||||
the download flow itself.
|
||||
|
||||
!!! note "Where the full flag list lives"
|
||||
This page covers the flags you will use most and explains how they interact. It is
|
||||
@@ -64,8 +64,9 @@ helps explain why, for example, `--list` stops early and why keys are fetched ev
|
||||
9. **Post-process**: extract closed captions, convert subtitles, repack, mux with
|
||||
`mkvmerge`, and move the finished file to your downloads directory.
|
||||
|
||||
`--list` and `--list-titles` stop after selection/filtering and print what *would* be
|
||||
downloaded. `--skip-dl` runs the license step but skips the actual segment download.
|
||||
`--list` prints the tracks a title exposes and stops before selection. `--list-titles`
|
||||
prints every title the service returned and stops before `--wanted`/`--latest-episode`
|
||||
filtering. `--skip-dl` runs the license step but skips the actual segment download.
|
||||
|
||||
## Choosing quality
|
||||
|
||||
@@ -141,9 +142,11 @@ unshackle dl -q 2160 -r HDR10,DV EXAMPLE '...'
|
||||
|
||||
!!! warning "HYBRID requires dovi_tool"
|
||||
`-r HYBRID` produces a single hybrid stream by injecting the Dolby Vision RPU
|
||||
metadata onto an HDR10/HDR10+ base layer using **dovi_tool**. It requires the
|
||||
`dovi_tool` binary on your PATH and needs both an HDR10(+) *and* a DV track to be
|
||||
available, otherwise the title fails (unless `--best-available` lets it fall back).
|
||||
metadata onto an HDR10/HDR10+ base layer using **dovi_tool**.
|
||||
It requires the `dovi_tool` binary, resolved from unshackle's `binaries/` folder or
|
||||
your `PATH`. The normal case is a DV track plus an HDR10 or HDR10+ base. With HDR10+
|
||||
and no DV, unshackle converts the HDR10+ metadata to DV instead, which also needs
|
||||
`hdr10plus_tool`. With no DV and no HDR10+, the title fails.
|
||||
When HDR10+ is present it is preferred over HDR10 as the base layer.
|
||||
|
||||
To keep an HDR10+ deliverable *alongside* the hybrid, request both ranges:
|
||||
@@ -171,8 +174,8 @@ Manifest-declared bitrates are sometimes rounded or wrong. Services often advert
|
||||
**peak** or **nominal** bandwidth that is far from the stream's real average. This matters
|
||||
because a track's bitrate drives the track listing, the sort order, *and* the
|
||||
`--vbitrate`/`--vbitrate-range` selection above. A bogus declared value therefore makes
|
||||
unshackle pick the wrong track. These flags remove that risk by probing the actual media
|
||||
size to compute a true bitrate for the top renditions, overriding the manifest value:
|
||||
unshackle pick the wrong track. These flags probe the actual media size to compute a true
|
||||
bitrate for the top renditions, overriding the manifest value:
|
||||
|
||||
- `-rvb` / `--real-video-bitrate`: probe real video bitrates (per codec/range).
|
||||
- `-rab` / `--real-audio-bitrate`: probe real audio bitrates (per codec/channels/
|
||||
@@ -186,12 +189,13 @@ size to compute a true bitrate for the top renditions, overriding the manifest v
|
||||
That is the real *delivered* size, not an over-report or a bug.
|
||||
|
||||
!!! tip "Why not every rendition is probed"
|
||||
Probing does not touch every rendition. Only the top rendition of each quality tier is
|
||||
probed in parallel (video grouped by codec+range, audio by codec+channels+language),
|
||||
and the pass then reaches downward only as far as it must to keep the ranking correct.
|
||||
This keeps probing fast even when a service exposes dozens of renditions. Tracks whose
|
||||
duration can't be determined fall back to `ffprobe`, and a probe failure is non-fatal:
|
||||
the manifest value is simply left in place.
|
||||
Probing does not touch every rendition. Only the five highest declared-bitrate
|
||||
renditions of each quality tier are probed in parallel (video grouped by codec and
|
||||
range, audio by codec, channels, language, and descriptive flag), and unshackle
|
||||
extends a group downward while a lower unprobed rendition could still outrank a
|
||||
probed one. This keeps probing fast even when a service exposes dozens of renditions.
|
||||
Tracks whose duration can't be determined fall back to `ffprobe`, and a probe failure
|
||||
is non-fatal: the manifest value is simply left in place.
|
||||
|
||||
## Audio codec, channels, and Atmos
|
||||
|
||||
@@ -250,6 +254,26 @@ it is used. You can override each stream type independently:
|
||||
unshackle dl -al en EXAMPLE 81234567
|
||||
```
|
||||
|
||||
### Sort order
|
||||
|
||||
`-l` and `-sl` **select** languages: naming some removes the rest. To keep every language
|
||||
but decide which ones come first, set a priority list in your configuration file:
|
||||
|
||||
```yaml title="unshackle.yaml"
|
||||
audio:
|
||||
language_priority: [orig, en]
|
||||
subtitle:
|
||||
group_by: language
|
||||
language_priority: [en, es, fr]
|
||||
```
|
||||
|
||||
Subtitles then start with English, Spanish, and French. The languages you leave out follow
|
||||
alphabetically, after the title's original language. Nothing is removed. Audio works the
|
||||
same way, but the languages you leave out keep their bitrate and codec order instead of an
|
||||
alphabetical one.
|
||||
|
||||
See [`subtitle.language_priority`](../reference/configuration/download.md#subtitle) for the details.
|
||||
|
||||
### Exact vs fuzzy matching
|
||||
|
||||
By default language matching is fuzzy: `-l en` also accepts `en-US`, `en-GB`, etc. (up
|
||||
@@ -285,6 +309,9 @@ download on the presence of a specific subtitle track.
|
||||
|
||||
- `-fs` / `--forced-subs`: include forced subtitle tracks (signs/foreign dialogue).
|
||||
Without this flag, forced subs are dropped from selection.
|
||||
- `-fsl` / `--forced-s-lang`: keep forced subtitles only in these languages (implies
|
||||
`-fs`). Works independently of `--s-lang`, so `-sl all -fsl en` grabs every full
|
||||
subtitle but only the English forced track.
|
||||
- `--sub-format`: set the output subtitle format, converting only when necessary.
|
||||
Accepts codec names/values and common aliases (`srt`, `vtt`, `ass`, `ssa`, `ttml`,
|
||||
etc.), or the literal `original` to keep the source format.
|
||||
@@ -341,13 +368,52 @@ with `-` to exclude it.
|
||||
unshackle dl -w S01-S05,-S03 EXAMPLE 81234567
|
||||
```
|
||||
|
||||
=== "Split episodes"
|
||||
|
||||
```shell
|
||||
# Part 2 of episode 1 only
|
||||
unshackle dl -w S01E01.2 EXAMPLE 81234567
|
||||
|
||||
# Parts 1 through 3 of episode 1
|
||||
unshackle dl -w S01E01.1-S01E01.3 EXAMPLE 81234567
|
||||
|
||||
# All of season 1 except episode 1 part 2
|
||||
unshackle dl -w S01,-S01E01.2 EXAMPLE 81234567
|
||||
```
|
||||
|
||||
### Split episodes
|
||||
|
||||
A few services split one episode into several separately playable videos. Where a service
|
||||
reports that, add `.N` after the episode to pick one part.
|
||||
|
||||
| Token | Selects |
|
||||
| --- | --- |
|
||||
| `S01E01` | All parts of episode 1. |
|
||||
| `S01E01.2` | Part 2 of episode 1, and nothing else. |
|
||||
| `S01E01.1-S01E01.3` | Parts 1 to 3 of episode 1. |
|
||||
| `S01,-S01E01.2` | All of season 1 except episode 1 part 2. Parts 1 and 3 are kept. |
|
||||
|
||||
A season token such as `S01` covers every episode and every part, so you only need `.N`
|
||||
when you want a part on its own.
|
||||
|
||||
!!! warning "A part range stays inside one episode"
|
||||
`S01E01.1-S01E01.3` is valid. `S01E01.1-S01E02.3` is rejected, because the parser
|
||||
cannot know how many parts episode 1 has and so cannot work out where the range ends.
|
||||
To span episodes, list the parts you want as separate tokens:
|
||||
`-w S01E01.2,S01E02.1`.
|
||||
|
||||
!!! note "A part of an unsplit episode selects nothing"
|
||||
`-w S01E02.2` on an episode that was never split is deliberately empty: it tells you
|
||||
the episode has no parts, rather than quietly handing you the whole episode. Run
|
||||
`--list-titles` to see which episodes carry parts.
|
||||
|
||||
### Other selection flags
|
||||
|
||||
| Flag | Behaviour |
|
||||
| --- | --- |
|
||||
| `--select-titles` | Interactively pick episodes from a list (Series only). **Cannot combine with `-w`.** |
|
||||
| `--select-titles` | Interactively pick episodes of a series, or films when a title has more than one. **Cannot combine with `-w`.** |
|
||||
| `--latest-episode` | Download only the single most recent episode. |
|
||||
| `--list-titles` | List the titles that would be downloaded, then stop. |
|
||||
| `--list-titles` | List every title the service returned, then stop. `-w`/`--latest-episode` are not applied to this listing. |
|
||||
|
||||
```shell title="Grab just the newest episode"
|
||||
unshackle dl --latest-episode EXAMPLE 81234567
|
||||
@@ -398,8 +464,8 @@ Before committing to a long download, inspect what unshackle *would* do:
|
||||
|
||||
| Flag | Effect |
|
||||
| --- | --- |
|
||||
| `--list` | List the available tracks and which would be selected, then stop. No download. |
|
||||
| `--list-titles` | List the titles (episodes) that would be downloaded, then stop. |
|
||||
| `--list` | List the tracks the service exposes for each title, then stop. No selection, no download. |
|
||||
| `--list-titles` | List every title the service returned, then stop. `-w`/`--latest-episode` are not applied to this listing. |
|
||||
| `--skip-dl` | Skip downloading but still acquire the decryption keys. |
|
||||
|
||||
```shell title="See the track selection without downloading"
|
||||
@@ -428,7 +494,7 @@ flags change how the output is assembled:
|
||||
| --- | --- | --- |
|
||||
| `--no-mux` | Do not mux; keep the individual track files. | - |
|
||||
| `--split-audio` | Write a separate output file per audio codec instead of merging all audio. | config `muxing.merge_audio` (on) |
|
||||
| `--merge-video` | Mux all selected video tracks into one file. | config `muxing.merge_video` (off) |
|
||||
| `--merge-video` | Mux video tracks that share a height, range, and codec into one file, so only language varies inside a file. | config `muxing.merge_video` (off) |
|
||||
|
||||
### Naming tags
|
||||
|
||||
@@ -453,7 +519,7 @@ unshackle dl --proxy 'http://user:pass@host:8080' EXAMPLE 81234567
|
||||
Two related flags:
|
||||
|
||||
- `--no-proxy`: force-disable all proxy use for this run.
|
||||
- `--no-proxy-download`: bypass the proxy for **segment downloads only**. The manifest,
|
||||
- `--no-proxy-download`: bypass the proxy for **all downloads**. The manifest,
|
||||
license, and authentication requests still go through the proxy. This is useful when the
|
||||
proxy is only needed to satisfy geo-checks, not to move the bulk of the data.
|
||||
|
||||
@@ -501,6 +567,13 @@ unshackle dl --skip-dl --export EXAMPLE 81234567
|
||||
When `--proxy` is used, the export records the region so an import can reproduce the
|
||||
correct geofence. Without a proxy, no region is stored.
|
||||
|
||||
!!! warning "Some DASH and Smooth exports need a title language"
|
||||
An import re-fetches the DASH or ISM manifest and parses it again. Most manifests label
|
||||
their own streams, and those import fine. When a manifest labels nothing, the parse falls
|
||||
back to the title's original language, which comes from `Title.language` on the exporting
|
||||
service. If a service never sets it, importing that export fails with a message naming
|
||||
the service. Neither end guesses a language for you, so the fix belongs in the service.
|
||||
|
||||
## Metadata and tagging
|
||||
|
||||
unshackle looks up metadata automatically, but you can override the identifiers used for
|
||||
@@ -510,13 +583,35 @@ tagging and naming:
|
||||
| --- | --- | --- |
|
||||
| `--tmdb` | `--tmdb 27205` | Use this TMDB ID instead of automatic lookup. |
|
||||
| `--imdb` | `--imdb tt1375666` | Use this IMDb ID. |
|
||||
| `--tvdb` | `--tvdb 73871` | Use this TVDB ID instead of looking the series up. |
|
||||
| `--animeapi` | `--animeapi mal:12345` | Resolve via AnimeAPI (`mal:`/`anilist:` prefix; defaults to MAL). |
|
||||
| `--enrich` | - | Override the show title and year from an external source. **Requires** one of `--tmdb`, `--imdb`, or `--animeapi`. |
|
||||
| `--tvdb-order` | `--tvdb-order dvd` | Renumber episodes to a TVDB season order. Needs `tvdb_api_key`. |
|
||||
|
||||
```shell title="Force the right IMDb match and enrich the title"
|
||||
unshackle dl --imdb tt1375666 --enrich EXAMPLE 81234567
|
||||
```
|
||||
|
||||
### Episode ordering
|
||||
|
||||
A service does not always number a series the way TVDB's aired order does. Some services list
|
||||
Futurama in TVDB's `alternate` (Streaming) order, for example, while TVDB's `official` order
|
||||
holds back four season-one episodes to the start of season two. `--tvdb-order` works out which
|
||||
order the service used, then renumbers the episodes into the order you asked for:
|
||||
|
||||
```shell title="Download Futurama in DVD order"
|
||||
unshackle dl --tvdb-order dvd EXAMPLE 81234567
|
||||
```
|
||||
|
||||
Available orders are `official` (aired), `dvd`, `absolute`, `alternate`, and `regional`. Set
|
||||
`tvdb_order` in your config to apply one by default.
|
||||
|
||||
!!! warning "Orders that do not cover the whole series"
|
||||
An order can leave out episodes the service carries. TVDB's `dvd` order does not list
|
||||
Futurama's four movies. Those episodes keep their original numbering. If that would give
|
||||
two episodes the same season/episode slot, and so the same filename, unshackle logs an
|
||||
error and leaves the numbering untouched. Pick an order that covers the whole series.
|
||||
|
||||
## Configuration defaults
|
||||
|
||||
Every flag on this page can be set as a default in your configuration file so you don't
|
||||
@@ -547,9 +642,10 @@ authoritative list.
|
||||
| `--v-lang` | `-vl` | Video-only language override. |
|
||||
| `--s-lang` | `-sl` | Subtitle language(s); default `all`. |
|
||||
| `--forced-subs` | `-fs` | Include forced subtitles. |
|
||||
| `--forced-s-lang` | `-fsl` | Forced subtitle language(s); implies `-fs`. |
|
||||
| `--sub-format` | | Output subtitle format. |
|
||||
| `--wanted` | `-w` | Episode/season range. |
|
||||
| `--select-titles` | | Interactively pick episodes. |
|
||||
| `--select-titles` | | Interactively pick episodes or films. |
|
||||
| `--latest-episode` | | Only the newest episode. |
|
||||
| `--video-only` / `--audio-only` / `--subs-only` | `-V` / `-A` / `-S` | Restrict track types. |
|
||||
| `--no-video` / `--no-audio` / `--no-subs` / `--no-chapters` | `-nv` / `-na` / `-ns` / `-nc` | Skip track types. |
|
||||
@@ -562,4 +658,5 @@ authoritative list.
|
||||
| `--list` / `--list-titles` / `--skip-dl` | | Dry runs. |
|
||||
| `--cdm-only` / `--vaults-only` | | Key source control. |
|
||||
| `--export` | | Export track info and keys to JSON. |
|
||||
| `--tmdb` / `--imdb` / `--animeapi` / `--enrich` | | Metadata overrides. |
|
||||
| `--tmdb` / `--imdb` / `--tvdb` / `--animeapi` / `--enrich` | | Metadata overrides. |
|
||||
| `--tvdb-order` | | Renumber episodes to a TVDB season order. |
|
||||
|
||||
@@ -516,5 +516,5 @@ debug_requests: true
|
||||
|
||||
- [Downloading](downloading.md): the `dl` command and its DRM-related flags.
|
||||
- [Key vaults](vaults.md): caching content keys across downloads.
|
||||
- [Configuration](../reference/configuration.md): the full `unshackle.yaml` reference.
|
||||
- [Configuration](../reference/configuration/index.md): the full `unshackle.yaml` reference.
|
||||
- [Dependencies](../getting-started/installation.md): installing shaka-packager and mp4decrypt.
|
||||
|
||||
@@ -71,7 +71,7 @@ The recognized kinds are:
|
||||
|
||||
### Separator style (dots vs. spaces)
|
||||
|
||||
unshackle looks at the characters you place *between* variables to decide the filename's separator style. If spaces outnumber dots in your template, it sanitizes with spaces; otherwise it uses dots (the scene-style default). This is also applied to the auto-generated date separators for daily/dated content.
|
||||
unshackle looks at the characters you place *between* variables to decide the filename's separator style. If spaces outnumber dots in your template, it sanitizes with spaces; otherwise it uses dots (the scene-style default). This is also applied to the auto-generated date separators for daily/dated content, and to every segment of a nested folder template.
|
||||
|
||||
=== "Scene style (dots)"
|
||||
|
||||
@@ -102,12 +102,13 @@ Every variable below is valid in both output and folder templates. Values are de
|
||||
| Variable | Meaning | Example |
|
||||
|---|---|---|
|
||||
| `title` | Title name (movie/show/song name; `$` is rendered as `S`) | `The Show` |
|
||||
| `title_type` | Media kind of the title | `movie`, `series`, `music` |
|
||||
| `year` | Release year | `2024` |
|
||||
| `source` | Service tag / class name (empty with `--no-source`) | `EXAMPLE` |
|
||||
| `quality` | Resolution with scan suffix | `1080p`, `2160p`, `576i` |
|
||||
| `resolution` | Resolution number only | `1080` |
|
||||
| `video` | Video codec | `H.264`, `H.265` |
|
||||
| `hdr` | Dynamic-range label | `HDR`, `HDR10P`, `DV`, `DV.HDR10`, `HLG` |
|
||||
| `hdr` | Dynamic-range label | `HDR`, `HDR10P`, `DV`, `DV.HDR`, `DV.HDR10P`, `HLG` |
|
||||
| `hfr` | High frame rate marker (frame rate > 30) | `HFR` |
|
||||
| `edition` | Edition label from the track, if any | `Directors Cut` |
|
||||
| `tag` | Release-group tag (from `config.tag` or `--tag`) | `TAG` |
|
||||
@@ -125,11 +126,38 @@ Every variable below is valid in both output and folder templates. Values are de
|
||||
| `episode` | Zero-padded episode (`E%02d`) | `E05` |
|
||||
| `season_episode` | Combined | `S01E05` |
|
||||
| `episode_name` | Episode title | `Pilot` |
|
||||
| `part` | Part index of a [split episode](#split-episodes), empty otherwise | `2` |
|
||||
| `date` | ISO air date for daily/dated content | `2024-06-01` |
|
||||
|
||||
!!! note "Daily & sports content"
|
||||
When an episode has an air date, unshackle switches to date-based naming automatically: `season` and `season_episode` become the formatted air date, `episode` and `year` are cleared, and `{date}` holds the ISO date. The date's internal separator (dots or spaces) follows your `series` template style.
|
||||
|
||||
### Split episodes {#split-episodes}
|
||||
|
||||
A few services split one episode into several separately playable videos. Where a service reports that, the part index is folded into `{episode}` and `{season_episode}`, so the stock `series` template names it with no change to your config.
|
||||
|
||||
```text
|
||||
Show.Name.S01E01.Part.2.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG.mkv
|
||||
```
|
||||
|
||||
The part token sits immediately after the episode token, before the quality tags, and the group tag stays last. Its separator follows your template's own style, the same way dated content does, so a spaced template gives `S01E01 Part 2` instead.
|
||||
|
||||
The part is left out of the folder name, so every part of an episode lands in the same season folder:
|
||||
|
||||
```text
|
||||
The.Show.S01.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG/
|
||||
├─ The.Show.S01E01.Part.1.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG.mkv
|
||||
├─ The.Show.S01E01.Part.2.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG.mkv
|
||||
└─ The.Show.S01E02.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG.mkv
|
||||
```
|
||||
|
||||
The same holds for a folder template of your own: `{season}` never picks up the part, and `{season_episode}` drops it inside a folder template. An episode with no part is named like any other episode.
|
||||
|
||||
!!! note "What media servers do with the part token"
|
||||
The form above follows scene practice for genuinely split episodes. Kodi stacks the parts back into one playable episode automatically. Jellyfin lists two versions of S01E01, still correct and playable, because its stacking pattern expects the part marker at the end of the name. Plex's scanner is closed-source, so its handling of text after the part token is unverified.
|
||||
|
||||
For custom templates there is also a standalone [`{part}`](../reference/configuration/output.md#output_template) variable. You rarely want it, see the caveat on that page.
|
||||
|
||||
### Audio
|
||||
|
||||
| Variable | Meaning | Example |
|
||||
@@ -138,8 +166,9 @@ Every variable below is valid in both output and folder templates. Values are de
|
||||
| `audio_channels` | Channel layout | `5.1`, `2.0` |
|
||||
| `audio_full` | Codec + channels combined | `DDP5.1` |
|
||||
| `atmos` | `Atmos` when any track carries JOC | `Atmos` |
|
||||
| `dual` | `DUAL` for exactly two distinct audio languages | `DUAL` |
|
||||
| `dual` | `DUAL` for two audio languages (see [`dual_multi_mode`](../reference/configuration/download.md#dual_multi_mode)) | `DUAL` |
|
||||
| `multi` | `MULTi` for more than two audio languages | `MULTi` |
|
||||
| `dubbed` | `DUBBED` for a single audio language that is not the original (strict mode only) | `DUBBED` |
|
||||
|
||||
### Music-specific
|
||||
|
||||
@@ -183,7 +212,7 @@ Folder templates are nested under a special `folder` key inside `output_template
|
||||
albums: "{artist} - {album} ({year})"
|
||||
```
|
||||
|
||||
The per-kind folder keys are `movies`, `series`, `songs`, and `albums`. Any other key produces a startup warning. Path separators (`/` or `\`) are allowed in folder templates. Each segment is formatted independently, so you can build nested directory structures like `Show/Season 01`.
|
||||
The per-kind folder keys are `movies`, `series`, `songs`, and `albums`. Any other key produces a startup warning. Path separators (`/` or `\`) are allowed in folder templates. Each segment is formatted independently, so you can build nested directory structures like `Show/Season 01`. unshackle reads the separator style from the whole template, so every segment is spaced alike.
|
||||
|
||||
**Fallback behavior:**
|
||||
|
||||
@@ -233,6 +262,10 @@ The `--no-mux` flag skips muxing entirely and writes the individual track files,
|
||||
## Tags & group naming
|
||||
|
||||
The release-group tag is the `-TAG` portion at the end of scene-style names, produced by the `{tag}` variable.
|
||||
It can be swapped per release with [`tag_rules`](../reference/configuration/output.md#tag_rules): ordered rules that
|
||||
match on the release-attribute variables only (`quality`, `resolution`, `hdr`, `source`, `lang_tag`,
|
||||
`title_type`, and so on), first match wins. The title's own fields, such as `title` and `season_episode`,
|
||||
cannot be matched. See the [reference page](../reference/configuration/output.md#tag_rules) for the exact list.
|
||||
|
||||
```yaml title="unshackle.yaml"
|
||||
tag: "MYGROUP"
|
||||
@@ -244,7 +277,11 @@ tag_imdb_tmdb: true
|
||||
|---|---|---|---|
|
||||
| `tag` | str | `""` | The release-group tag used for `{tag}`. |
|
||||
| `tag_group_name` | bool | `true` | Write the group name (`config.tag`) into the MKV `Group` metadata tag. |
|
||||
| `tag_imdb_tmdb` | bool | `true` | Look up and embed IMDb / TMDB / TVDB external-ID tags in the MKV metadata (uses `tmdb_api_key` / `simkl_client_id` when available). |
|
||||
| `tag_imdb_tmdb` | bool | `true` | Look up and embed IMDb / TMDB / TVDB external-ID tags in the MKV metadata (uses `tmdb_api_key` / `tvdb_api_key` / `simkl_client_id` when available). |
|
||||
|
||||
The IDs are written as the Matroska `IMDB`, `TMDB`, and `TVDB2` tags. `TVDB2` values carry the
|
||||
entity prefix the [Matroska tagging spec](https://www.matroska.org/technical/tagging.html)
|
||||
requires: `series/73871` for a show, `movies/113` for a film.
|
||||
|
||||
You can override the tag per run without editing config:
|
||||
|
||||
@@ -339,6 +376,9 @@ The fallback supports two placeholders: `{i}` (chapter number, starting at 1) an
|
||||
|
||||
## Worked examples
|
||||
|
||||
Each tab shows a template and what it produces for a few different downloads, so you can see
|
||||
how the optional (`?`) variables appear and disappear.
|
||||
|
||||
=== "Movies"
|
||||
|
||||
```yaml
|
||||
@@ -347,9 +387,27 @@ The fallback supports two placeholders: `{i}` (chapter number, starting at 1) an
|
||||
folder:
|
||||
movies: "{title} ({year})"
|
||||
```
|
||||
|
||||
A 4K Atmos HDR download fills every placeholder:
|
||||
|
||||
```text
|
||||
The Movie (2024)/
|
||||
└─ The.Movie.2024.1080p.EXAMPLE.WEB-DL.DDP5.1.Atmos.H.265.HDR-TAG.mkv
|
||||
└─ The.Movie.2024.2160p.EXAMPLE.WEB-DL.DDP5.1.Atmos.H.265.HDR-TAG.mkv
|
||||
```
|
||||
|
||||
The same template on a plain 1080p SDR stereo download, where `{edition?}`, `{atmos?}`
|
||||
and `{hdr?}` collapse along with their dots:
|
||||
|
||||
```text
|
||||
The Movie (2024)/
|
||||
└─ The.Movie.2024.1080p.EXAMPLE.WEB-DL.AAC2.0.H.264-TAG.mkv
|
||||
```
|
||||
|
||||
A director's cut fills `{edition?}`:
|
||||
|
||||
```text
|
||||
The Movie (2024)/
|
||||
└─ The.Movie.2024.Directors.Cut.2160p.EXAMPLE.WEB-DL.DDP5.1.Atmos.H.265.DV.HDR-TAG.mkv
|
||||
```
|
||||
|
||||
=== "TV series"
|
||||
@@ -365,6 +423,76 @@ The fallback supports two placeholders: `{i}` (chapter number, starting at 1) an
|
||||
└─ The.Show.S02E04.The.Reckoning.2160p.EXAMPLE.WEB-DL.DDP5.1.H.265-TAG.mkv
|
||||
```
|
||||
|
||||
unshackle decides the separator style once for the whole folder template, and every
|
||||
segment then follows it. Spaces outnumber dots in `{title} ({year})/Season {season}`,
|
||||
so `Season {season}` keeps its space even though it holds only one variable.
|
||||
|
||||
When an episode has no on-screen name, `{episode_name?}` disappears cleanly:
|
||||
|
||||
```text
|
||||
The Show (2023)/Season S02/
|
||||
└─ The.Show.S02E05.2160p.EXAMPLE.WEB-DL.DDP5.1.H.265-TAG.mkv
|
||||
```
|
||||
|
||||
=== "Multi-language"
|
||||
|
||||
Place `{dual?}`, `{multi?}` and `{dubbed?}` next to each other. At most one of them is
|
||||
ever set (see [`dual_multi_mode`](../reference/configuration/download.md#dual_multi_mode)), and the
|
||||
empty ones collapse:
|
||||
|
||||
```yaml
|
||||
output_template:
|
||||
movies: "{title}.{year}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{dubbed?}.{audio_full}.{video}-{tag}"
|
||||
```
|
||||
|
||||
Original English audio plus a French dub (`-l en,fr`):
|
||||
|
||||
```text
|
||||
The.Movie.2024.1080p.EXAMPLE.WEB-DL.DUAL.DDP5.1.H.264-TAG.mkv
|
||||
```
|
||||
|
||||
Original plus several dubs (`-l en,fr,de,es`):
|
||||
|
||||
```text
|
||||
The.Movie.2024.1080p.EXAMPLE.WEB-DL.MULTi.DDP5.1.H.264-TAG.mkv
|
||||
```
|
||||
|
||||
Only a dub, original audio left out (`-l de` on an English-original title):
|
||||
|
||||
```text
|
||||
The.Movie.2024.1080p.EXAMPLE.WEB-DL.DUBBED.DDP5.1.H.264-TAG.mkv
|
||||
```
|
||||
|
||||
Two dialects of one language (`en-US` + `en-GB`) count as a single language, so none of the
|
||||
three variables is set:
|
||||
|
||||
```text
|
||||
The.Movie.2024.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG.mkv
|
||||
```
|
||||
|
||||
For finer control (e.g. a `SUBBED` tag driven by subtitle languages), use `{lang_tag?}`
|
||||
with [`language_tags` rules](../reference/configuration/download.md#language_tags) instead.
|
||||
|
||||
=== "Daily shows"
|
||||
|
||||
Dated content needs no special template. When an episode carries an air date,
|
||||
`{season_episode}` becomes the date automatically and `{episode}` and `{year}` clear:
|
||||
|
||||
```yaml
|
||||
output_template:
|
||||
series: "{title}.{season_episode}.{episode_name?}.{quality}.{source}.WEB-DL.{audio_full}.{video}-{tag}"
|
||||
```
|
||||
|
||||
A normal episode and a dated one, same template:
|
||||
|
||||
```text
|
||||
The.Daily.Show.S28E101.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG.mkv
|
||||
The.Daily.Show.2024.06.01.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG.mkv
|
||||
```
|
||||
|
||||
The date separator follows your template style: dots here, spaces if your `series`
|
||||
template uses spaces.
|
||||
|
||||
=== "Music"
|
||||
|
||||
```yaml
|
||||
@@ -375,7 +503,25 @@ The fallback supports two placeholders: `{i}` (chapter number, starting at 1) an
|
||||
```
|
||||
```text
|
||||
The Artist - The Album (2024)/
|
||||
└─ 01. Opening Track.mka
|
||||
└─ 01.Opening.Track.mka
|
||||
```
|
||||
|
||||
In `"{track_number}. {title}"` dots and spaces are tied, and ties go to dot style; add
|
||||
more spaces between variables (as below) if you want space-separated names.
|
||||
|
||||
A multi-disc album with explicit tracks, using more of the music variables. `{disc}` is
|
||||
empty on disc 1, so `{disc?}` and its separator only appear from disc 2 onward:
|
||||
|
||||
```yaml
|
||||
output_template:
|
||||
songs: "{disc?}-{track_number}. {title} {explicit?}"
|
||||
folder:
|
||||
albums: "{album_artist} - {album} ({year}) [{label?}]"
|
||||
```
|
||||
```text
|
||||
The Artist - The Album (2024) [The Label]/
|
||||
├─ 01. Opening Track.mka
|
||||
└─ 02-03. Closing Track Explicit.mka
|
||||
```
|
||||
|
||||
## See also
|
||||
|
||||
@@ -28,7 +28,7 @@ Proxying is controlled at download time by three options on the `dl` command:
|
||||
|---|---|
|
||||
| `--proxy` | The proxy to use. Either an explicit URI, or a query that unshackle resolves against your configured providers. |
|
||||
| `--no-proxy` | Force **all** proxy use off. No providers are initialised and no proxy query is resolved. |
|
||||
| `--no-proxy-download` | Use the proxy for the manifest, licence, and authentication, but bypass it for the **segment downloads** themselves. |
|
||||
| `--no-proxy-download` | Use the proxy for the manifest, licence, and authentication, but bypass it for the **downloads** themselves. |
|
||||
|
||||
```shell title="Explicit proxy URI"
|
||||
unshackle dl --proxy http://user:pass@1.2.3.4:8080 EXAMPLE 81234567
|
||||
@@ -52,7 +52,7 @@ unshackle dl --proxy nordvpn:us EXAMPLE 81234567
|
||||
Segment downloads are usually the largest share of traffic, and a residential-grade VPN
|
||||
proxy is often the slowest link in the chain. `--no-proxy-download` lets you keep the
|
||||
proxy where it matters (passing the geo-check for the manifest and the licence) while
|
||||
pulling the actual audio and video segments over your normal connection for full speed.
|
||||
pulling the files themselves over your normal connection at full speed.
|
||||
|
||||
```shell title="Proxy the manifest and licence, download segments direct"
|
||||
unshackle dl --proxy gb --no-proxy-download EXAMPLE 10a1234
|
||||
|
||||
+25
-3
@@ -70,6 +70,7 @@ Subtitle selection happens through `dl` flags. The most important is language se
|
||||
| `-sl`, `--s-lang` | Language(s) wanted for subtitles. Defaults to `all`. |
|
||||
| `--require-subs` | Require these languages to exist; if present, download **all** subtitles. Cannot be combined with `--s-lang`. |
|
||||
| `-fs`, `--forced-subs` | Include forced subtitle tracks (excluded by default). |
|
||||
| `-fsl`, `--forced-s-lang` | Language(s) wanted for forced subtitles; implies `-fs`. |
|
||||
| `--exact-lang` | Exact language matching, with no regional variants. |
|
||||
| `-S`, `--subs-only` | Download only subtitle tracks. |
|
||||
| `-ns`, `--no-subs` | Do not download subtitle tracks at all. |
|
||||
@@ -121,6 +122,18 @@ default**. Add `-fs` / `--forced-subs` to include them:
|
||||
unshackle dl --s-lang en --forced-subs EXAMPLE 81234567
|
||||
```
|
||||
|
||||
`-fs` keeps forced tracks in every selected language. If you only want forced tracks
|
||||
in certain languages, use `-fsl` / `--forced-s-lang` instead. It implies `-fs`, so
|
||||
you do not need both:
|
||||
|
||||
```shell title="All full subtitles, but only the English forced track"
|
||||
unshackle dl --s-lang all --forced-s-lang en EXAMPLE 81234567
|
||||
```
|
||||
|
||||
If you pass both flags, `-fsl` wins and keeps only forced tracks in its languages.
|
||||
It also understands `orig` (the title's original language) and `all` (every forced
|
||||
track, same as plain `-fs`).
|
||||
|
||||
## Track types: forced, SDH, and CC
|
||||
|
||||
Every subtitle track carries flags describing what kind of captions it holds. These
|
||||
@@ -134,9 +147,16 @@ flags drive selection, sort order, output filenames, and the Matroska track flag
|
||||
often uppercase with `>>>` speaker markers and sound cues.
|
||||
|
||||
!!! note "SDH and CC are treated together for sorting and stripping"
|
||||
Within a language, unshackle orders subtitles from fewest to most captions:
|
||||
**Forced → Normal → SDH/CC**. For the purposes of hearing-impaired handling, SDH and
|
||||
CC tracks are grouped as the "most captions" variant.
|
||||
unshackle orders subtitles from fewest to most captions: **Forced → Normal → SDH/CC**.
|
||||
For the purposes of hearing-impaired handling, SDH and CC tracks are grouped as the
|
||||
"most captions" variant.
|
||||
|
||||
By default this type order is the primary sort, so all forced tracks come first, then
|
||||
all normal tracks, then all SDH/CC tracks, each block sorted by language. To keep each
|
||||
language next to its own variants instead, set
|
||||
[`subtitle.group_by`](../reference/configuration/download.md#subtitle) to `language`. Use
|
||||
[`subtitle.type_priority`](../reference/configuration/download.md#subtitle) to change the
|
||||
Forced → Normal → SDH/CC order itself.
|
||||
|
||||
A single track cannot be both CC and SDH, and a forced track cannot also be flagged
|
||||
CC or SDH. These combinations are rejected as invalid.
|
||||
@@ -356,6 +376,8 @@ subtitle:
|
||||
| `convert_before_strip` | `true` | `true` / `false` | Convert a subtitle to SRT before stripping SDH (for non-SubtitleEdit engines). |
|
||||
| `output_mode` | `mux` | `mux`, `sidecar`, `both` | Whether subtitles are embedded, written as sidecars, or both. |
|
||||
| `sidecar_format` | `srt` | a format name (e.g. `srt`, `vtt`) or `original` | Format used for sidecar files. |
|
||||
| `group_by` | `type` | `type`, `language` | Whether all tracks of one type stay together, or each language stays next to its own variants. |
|
||||
| `language_priority` | *(unset)* | a list of languages | Languages to sort to the top. The rest keep the default order. Nothing is removed. |
|
||||
|
||||
!!! tip "SubtitleEdit is optional but recommended"
|
||||
Several high-fidelity paths (the best-quality conversions and the SubtitleEdit SDH
|
||||
|
||||
@@ -74,9 +74,11 @@ For example, `unshackle_debug_EXAMPLE_20260703-142530.jsonl`. The `<service>` ta
|
||||
Every line is a self-contained JSON object. The first line of a session records the environment:
|
||||
|
||||
```json
|
||||
{"timestamp":"2026-07-03T14:25:30.123456+00:00","session_id":"a1b2c3d4","level":"INFO","operation":"session_start","message":"Debug logging session started","context":{"unshackle_version":"5.3.0","python_version":"3.12.4 ...","platform":"Linux-6.18.5-x86_64","platform_system":"Linux","platform_release":"6.18.5"}}
|
||||
{"timestamp":"2026-07-03T14:25:30.123456+00:00","session_id":"a1b2c3d4","level":"INFO","operation":"session_start","message":"Debug logging session started","context":{"unshackle_version":"5.3.0","unshackle_code_hash":"1d22a1e","python_version":"3.12.4 ...","platform":"Linux-6.18.5-x86_64","platform_system":"Linux","platform_release":"6.18.5"}}
|
||||
```
|
||||
|
||||
Quote `unshackle_code_hash` in a bug report. It fingerprints the framework source you are running, so it tells us the exact code state even when you did not install from git. A hash that matches no release means the source was edited locally.
|
||||
|
||||
Subsequent lines describe operations: service calls, DRM licence requests, vault lookups, downloader progress, and errors. Common fields include:
|
||||
|
||||
| Field | Meaning |
|
||||
|
||||
@@ -208,6 +208,9 @@ key_vaults:
|
||||
name: myapi
|
||||
uri: https://vault.example.com/api
|
||||
token: "your-bearer-token"
|
||||
headers:
|
||||
CF-Access-Client-Id: "CLIENT_ID_HERE"
|
||||
CF-Access-Client-Secret: "CLIENT_SECRET_HERE"
|
||||
```
|
||||
|
||||
| Field | Required | Description |
|
||||
@@ -218,6 +221,7 @@ key_vaults:
|
||||
| `token` | yes | Bearer token, sent as `Authorization: Bearer <token>`. |
|
||||
| `no_push` | no | If `true`, keys are read but never written. |
|
||||
| `timeout` | no | Request timeout in seconds; defaults to `vault_timeout`. |
|
||||
| `headers` | no | Extra HTTP `headers` sent with every request, for example the Cloudflare Access service-token headers `CF-Access-Client-Id` and `CF-Access-Client-Secret`. |
|
||||
|
||||
The backend talks to endpoints under `uri` (for example `GET {uri}/{service}/{kid}` to look up a single key) and interprets a numeric `code` field in each JSON response to detect errors such as an invalid token, rate limiting, or an invalid service tag.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user