Merge branch 'dev' into feat/downloader-speed

# Conflicts:
#	unshackle/core/api/download_manager.py
#	unshackle/core/downloaders/requests.py
#	unshackle/core/manifests/dash.py
#	unshackle/core/session.py
#	unshackle/core/utils/webvtt.py
This commit is contained in:
imSp4rky
2026-08-16 12:43:01 -06:00
165 changed files with 6590 additions and 2076 deletions
+17 -5
View File
@@ -18,7 +18,7 @@ after the flag's internal destination rather than its visible name; set these ex
| `--list` | `list` |
| `--tmdb` | `tmdb_id` |
| `--imdb` | `imdb_id` |
| `--animeapi` | `animeapi_id` |
| `--anilist` | `anilist_id` |
| `-naa` / `--noatmos` | `no_atmos` |
| `-o` / `--output` | `output_dir` |
| `--cdm-only` / `--vaults-only` | `cdm_only` |
@@ -31,9 +31,9 @@ Common keys (a useful subset; every `dl` flag works):
| Key | Type | Default | Sets |
|-----|------|---------|------|
| `lang` | list | `["orig"]` | Video/audio language(s); `orig` = original language. |
| `a_lang` / `v_lang` | list | `[]` | Audio- / video-only language override. |
| `s_lang` | list | `["all"]` | Subtitle language(s). |
| `lang` | list | `["orig"]` | Video/audio language(s); `orig` = original language. A `-` prefix excludes, e.g. `[all, -es]`. |
| `a_lang` / `v_lang` | list | `[]` | Audio- / video-only language override. A `-` prefix excludes. |
| `s_lang` | list | `["all"]` | Subtitle language(s). A `-` prefix excludes, e.g. `[all, -es]`. |
| `quality` | list | `[]` (best) | Resolution(s), e.g. `[1080]`. |
| `vcodec` | list | `[]` (any) | Video codec(s), e.g. `[H265]`. |
| `acodec` | list | `[]` (any) | Audio codec(s), e.g. `[EC3]`. |
@@ -41,7 +41,7 @@ Common keys (a useful subset; every `dl` flag works):
| `channels` | float | *(unset)* | Audio channels, e.g. `6` for 5.1. |
| `sub_format` | str | *(unset)* | Convert subtitles to this format (`srt`, `vtt`, `original`, ...). |
| `forced_subs` | bool | `false` | Include forced subtitle tracks. |
| `forced_s_lang` | list | `[]` | Forced subtitle language(s); implies `forced_subs`. |
| `forced_s_lang` | list | `[]` | Forced subtitle language(s); implies `forced_subs`. A `-` prefix excludes. |
| `no_subs` / `no_audio` / `no_chapters` | bool | `false` | Skip that track type. |
| `downloads` | int | `1` | Tracks downloaded concurrently. |
| `workers` | int | *(downloader default)* | Threads per track. |
@@ -61,6 +61,18 @@ dl:
downloads: 2
```
!!! note "Language exclusions in YAML"
The language keys accept the same `-` exclusion tokens as the command line. Give them
as a list or as one comma-separated string.
```yaml
dl:
s_lang: [all, -es]
a_lang: "orig,-es"
```
See [Excluding languages](../../guide/downloading.md#excluding-languages) for the rules.
!!! note "Per-service overrides"
A `dl` block nested under a service in [`services`](services.md) takes the same keys and
applies to that service only.
+2 -1
View File
@@ -36,7 +36,8 @@ Where the file lives, how unshackle discovers it, and how to edit it:
| [DRM & CDM](drm.md) | `cdm`, `remote_cdm`, `decryption` |
| [Network & proxy](network.md) | `network`, `headers`, `proxy_providers` |
| [Key vaults](vaults.md) | `key_vaults`, `vault_timeout` |
| [External API keys](misc.md#external-api-keys) | `imdb_api_enabled`, `omdb_api_key`, `tmdb_api_key`, `tvdb_api_key`, `tvdb_pin`, `tvdb_order`, `simkl_client_id`, `metadata_providers`, `decrypt_labs_api_key`, `ipinfo_api_key` |
| [External API keys](misc.md#external-api-keys) | `omdb_api_key`, `tmdb_api_key`, `tvdb_api_key`, `tvdb_pin`, `tvdb_order`, `simkl_client_id`, `metadata_providers`, `anilist_title_language`, `disable_metadata`, `decrypt_labs_api_key`, `ipinfo_api_key` |
| [Caching & updates](misc.md#caching-updates) | `title_cache_enabled`, `title_cache_time`, `title_cache_max_retention`, `update_checks`, `update_check_interval` |
| [Appearance](misc.md#appearance) | `theme` |
| [Logging, privacy & debug](misc.md#logging-privacy-debug) | `redact_paths`, `debug`, `debug_keys`, `debug_requests`, `set_terminal_bg` |
| [Deprecated & removed](misc.md#deprecated-removed-keys) | `curl_impersonate`, `downloader`, `scene_naming` |
+86 -9
View File
@@ -4,18 +4,21 @@ Smaller configuration areas: external metadata API keys, caching and update chec
## External API keys { #external-api-keys }
All are unset by default and enable optional metadata/geolocation features.
All are unset by default and enable optional metadata/geolocation features. For which fields
each metadata provider can actually answer with, see
[What each metadata provider supplies](../../guide/downloading.md#what-each-metadata-provider-supplies).
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `imdb_api_enabled` | bool | `false` | Use the free IMDxAPI (api.tiffara.com) metadata provider, which needs no API key. Off by default since the site has been unreliable. |
| `omdb_api_key` | str | `""` | [OMDb](https://www.omdbapi.com/) API key for IMDb metadata lookups; a more reliable alternative to IMDxAPI. Free keys are available on the OMDb site. |
| `omdb_api_key` | str | `""` | [OMDb](https://www.omdbapi.com/) API key for IMDb metadata lookups; a fallback for when the keyless `imdb` provider has no answer. Free keys are available on the OMDb site. |
| `tmdb_api_key` | str | `""` | TMDB API key for metadata enrichment and external-ID tags. |
| `simkl_client_id` | str | `""` | SIMKL client ID for metadata lookups; an alternative/fallback source to TMDB. |
| `tvdb_api_key` | str | `""` | [TheTVDB v4](https://thetvdb.com/api-information) API key; a fallback source to TMDB, strongest on TV series. Free keys are available on the TVDB site. |
| `tvdb_api_key` | str | `""` | [TheTVDB v4](https://thetvdb.com/api-information) API key; a fallback source to TMDB, strongest on TV series. Also the source `--enrich --tvdb` reads. Free keys are available on the TVDB site. |
| `tvdb_pin` | str | `""` | Subscriber PIN, only needed for a user-supported TVDB key. Leave empty for a normal project key. |
| `tvdb_order` | str | `""` | Default for `--tvdb-order`: renumber episodes to a TVDB season order (`official`, `dvd`, `absolute`, `alternate`, `regional`). Empty keeps the numbering the service gives. |
| `metadata_providers` | list | *(see below)* | Metadata providers to use, in the order they are tried. |
| `metadata_providers` | list or map | *(see below)* | Metadata providers to use, in the order they are tried. |
| `anilist_title_language` | str | `english` | Which AniList title variant to use: `english`, `romaji`, or `native`. |
| `disable_metadata` | bool | `false` | Set to `true` to stop all automatic metadata lookups. An ID you give yourself is still looked up. |
| `decrypt_labs_api_key` | str | `""` | Global Decrypt Labs API key (used by remote CDM / vault). |
| `ipinfo_api_key` | str | `""` | ipinfo.io API key for IP/region lookups. |
@@ -31,12 +34,77 @@ All are unset by default and enable optional metadata/geolocation features.
ignored with a warning. Leave it unset for the default order:
```yaml
metadata_providers: [imdbapi, omdb, simkl, tmdb, tvdb]
metadata_providers: [imdb, omdb, simkl, tmdb, tvdb, anilist]
```
Whatever the order, a provider is skipped when its API key is missing (or, for
`imdbapi`, when `imdb_api_enabled` is `false`). Available names are
`imdbapi`, `omdb`, `simkl`, `tmdb`, and `tvdb`.
Whatever the order, unshackle skips a provider that has no API key. `imdb` and
`anilist` need no key, so they are always available. Available names are `imdb`, `omdb`,
`simkl`, `tmdb`, `tvdb`, and `anilist`. The old name `imdbapi` still works and is read as
`imdb`.
`anilist` is last in the default order. It only answers for anime and returns nothing for
anything else, so it is harmless there. Move it earlier if you mostly download anime.
The order applies to a title search and to a direct lookup of an ID you give with
`--tmdb`, `--imdb`, `--tvdb`, or `--anilist`. A supplied ID always goes to the providers
that read that kind of ID, and it never falls back to a title search.
!!! note "Which AniList title `anilist_title_language` picks"
AniList holds up to three titles for a work: the English one, the romaji transliteration,
and the native one. This option says which of them unshackle uses:
```yaml
anilist_title_language: romaji
```
A work does not always have all three. When the one you asked for is unset on AniList's
side, unshackle falls back in the order english → romaji → native. It takes the first one
that is set, so you always get a title.
!!! note "What `disable_metadata` turns off"
With `disable_metadata: true`, unshackle never contacts a metadata provider on its own.
The title search is dead, and that includes the keyless `imdb` provider. Titles, years and
languages then come only from the service.
```yaml
disable_metadata: true
```
An ID you give with `--tmdb`, `--imdb`, `--tvdb`, or `--anilist` is your permission to use
that ID. That lookup still works. unshackle looks the ID up directly, cross references the other
IDs from it, and `--enrich` still reads that source. Only the lookups you did not ask for
are stopped.
!!! note "The `imdb` provider needs no key"
It replaces the earlier `imdbapi` provider, which read a third-party mirror
(api.tiffara.com) and needed the `imdb_api_enabled` switch. That option is gone: delete it
from your config, where it is now ignored.
!!! tip "A different order per title kind"
Give a map instead of one list when the best source differs by kind. TVDB is strongest on
series, TMDB on films. There are two kinds, `tv` and `movie`, and this example sets both
of them and names every provider, so nothing is left to the default:
```yaml
metadata_providers:
tv: [tvdb, tmdb, simkl, imdb, omdb]
movie: [tmdb, imdb, omdb, simkl, tvdb]
```
Each list is independent: it both orders and filters that kind on its own, so a provider
you leave out of `tv` is still used for `movie`. Here TVDB is tried first for a series and
last for a film.
You do not need to give both keys, or a full list in each. A kind you leave out uses the
default order:
```yaml
metadata_providers:
tv: [tvdb, tmdb] # movies keep the default order
```
`tv` covers every episode and `movie` covers every film. unshackle only looks up
metadata for those two kinds, so any other key, such as `anime` or `music`, is ignored.
!!! note "`ipinfo_api_key` never touches your service sessions"
The token is only ever sent to `api.ipinfo.io` as a per-request `Authorization` header; it
@@ -73,6 +141,14 @@ calls.
---
## Appearance { #appearance }
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `theme` | str | `catppuccin-mocha` | Named colour theme for all CLI output: banner, logs, progress bars, and help screens. One of `catppuccin-mocha` (aliases `default`, `catppuccin`, `mocha`), `dracula`, `nord`, `gruvbox`, `one-dark` (alias `onedark`), `tokyo-night` (alias `tokyonight`), `monokai`, `flexoki`, `solarized-dark` (alias `solarized`). Case-insensitive; underscores work in place of hyphens. Unknown names fall back to the default with a warning. Preview them all with `unshackle env theme`. |
---
## Logging, privacy & debug { #logging-privacy-debug }
| Key | Type | Default | Description |
@@ -107,3 +183,4 @@ calls.
| `curl_impersonate` | **Deprecated** → use [`network`](network.md) | Emits a `DeprecationWarning`; still honoured only if `network` is absent. |
| `downloader` | **Deprecated** | Any value other than `"requests"` emits a `DeprecationWarning`; the value is otherwise ignored (the unified requests downloader is always used). |
| `scene_naming` | **Removed** | If set to any non-null value, unshackle exits with an error directing you to configure [`output_template`](output.md) instead. |
| `imdb_api_enabled` | **Removed** | Ignored. It gated the old `imdbapi` provider, which the keyless [`imdb`](#external-api-keys) provider replaces. Safe to delete from your config. |
+9 -1
View File
@@ -35,7 +35,7 @@ folder-kind keys.
??? example "All valid template variables"
`title`, `year`, `season`, `episode`, `season_episode`, `episode_name`, `part`,
`date`, `quality`, `resolution`, `source`, `tag`, `track_number`, `artist`,
`absolute`, `date`, `quality`, `resolution`, `source`, `tag`, `track_number`, `artist`,
`album_artist`, `album`, `disc`, `track_total`, `disc_total`, `release_type`, `genre`,
`explicit`, `isrc`, `upc`, `label`, `audio`, `audio_channels`, `audio_full`, `atmos`,
`dual`, `multi`, `dubbed`, `video`, `hdr`, `hfr`, `edition`, `repack`, `lang_tag`,
@@ -51,6 +51,14 @@ folder-kind keys.
`{season_episode}`, and note that it is always empty in a folder template, since
folders do not carry the part.
!!! tip "`{absolute}` counts episodes across seasons"
`absolute` holds the absolute episode number, zero-padded to 3 digits (`007`), and is
empty on titles that have none. It is added to the name and never replaces `{season}`
or `{episode}`. Any series with a TVDB absolute order can use it, anime most often.
Services can supply it, and
[`--enrich`](../../guide/downloading.md#metadata-and-tagging) fills it in from TVDB.
Use `{absolute?}` so the name stays clean when it is unknown.
## Tagging & naming keys
| Key | Type | Default | Description |
+24 -4
View File
@@ -82,11 +82,22 @@ keep auth tokens in localStorage rather than in HTTP cookies need. Extraction is
Definitions of remote unshackle service servers, used by the `--remote` mode. Each entry is
named by you (pick it with `--server`, or omit that flag when only one is configured) and gives
the server's `url` (required), an optional `api_key` sent as `X-Secret-Key`, an optional
`server_cdm` boolean, and an optional `services` sub-dict of per-service local overrides such
as `title_map`. In `--remote` mode unshackle turns the server's service list into synthetic CLI
the server's `url` (required), an optional `api_key`, an optional `auth_headers` list, an
optional `server_cdm` boolean, and an optional `services` sub-dict of per-service local
overrides such as `title_map`.
`auth_headers` lists extra header names to send the API key in, tried before the defaults
`X-Secret-Key` and `X-Api-Key`, which are always appended as fallbacks. unshackle sends the
first name; if the server answers `401`, it retries the same request with the next name, and
keeps the one that works for the rest of the session. Names you list keep your spelling and are
not repeated in the fallbacks, so `auth_headers: ["Authorization", "x-secret-key"]` is tried as
`Authorization`, `x-secret-key`, `X-Api-Key`.
In `--remote` mode unshackle turns the server's service list into synthetic CLI
commands that run against it, falling back to the tags in that `services` sub-dict when the
list cannot be fetched. See [remote sessions](../../dev/rest-api/remote-sessions.md) for the
list cannot be fetched. Each synthetic command carries the server-side service's options and
documentation, so `unshackle dl --remote <TAG> -h` shows the same help text as it does on the
server. See [remote sessions](../../dev/rest-api/remote-sessions.md) for the
full setup.
## `serve`
@@ -117,6 +128,13 @@ Each entry under `users` is keyed by that user's API key and may set its own `se
`username` used as the log label for that key (defaults to a truncated form of the key). A
user with no `playready_devices` key gets no PlayReady access at all, not the global list.
`server_cdm` decides whether the server runs the CDM licensing for that key. It is `false`
unless the entry sets it, so a remote client configured with `server_cdm: true` is told to
license with its own local CDM instead, and a client that asks anyway gets a `FORBIDDEN`
error. Because a download job always licenses with the server's CDM, a key without
`server_cdm` also cannot submit or retry `/api/download` jobs. Keys that have no `users`
entry, such as `api_secret`, keep server CDM access.
```yaml
serve:
api_secret: change-me
@@ -128,6 +146,8 @@ serve:
users:
a1b2c3d4: # this user's API key
services: [EXAMPLE1] # may only use EXAMPLE1
e5f6a7b8:
server_cdm: true # this key may have the server do the licensing
```
!!! note "`dl` keys inside `serve`"