diff --git a/README.md b/README.md index b043aa9a..952d3a78 100644 --- a/README.md +++ b/README.md @@ -36,18 +36,18 @@ unshackle --help ### Requirements -External tools on your `PATH` (recommended versions): +External tools. (recommended versions): - [Python](https://www.python.org/) - 3.11 - 3.14 - [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 Optional: +- [Bento4](https://github.com/axiomatic-systems/Bento4) - ≥ 1.6.0-639 +- [dovi_tool](https://github.com/quietvoid/dovi_tool) - ≥ 2.1 - [SubtitleEdit](https://github.com/SubtitleEdit/subtitleedit/releases) - ≥ 5.0 (`SeConv` CLI) ## License diff --git a/docs/getting-started/configuration-file.md b/docs/getting-started/configuration-file.md index 32d99792..4111bb8e 100644 --- a/docs/getting-started/configuration-file.md +++ b/docs/getting-started/configuration-file.md @@ -105,7 +105,7 @@ turned into underscores (`--best-available` → `best_available`). dl: lang: [en] # -l en quality: [1080] # -q 1080 - vcodec: [H265] # -v H265 + vcodec: [H.265] # -v H.265 sub_format: srt # convert subtitles to SRT downloads: 2 # two tracks at once ``` @@ -159,7 +159,7 @@ The directories unshackle uses: | `fonts` | Bundled fonts | Yes | !!! note "Some directories cannot be moved" - A handful of internal locations are protected: the package root, the core directory, the user-config directory, and the data base. If you list them under `directories`, unshackle silently ignores the override. This is intentional; those paths are tied to where the package is installed. + unshackle protects five internal entries: `app_dirs`, `core_dir`, `namespace_dir`, `user_configs`, and `data`. If you list any of them under `directories`, unshackle ignores the override. This is intentional; those paths are tied to where the package is installed. ### The `services` directory is special @@ -168,12 +168,12 @@ Unlike the other entries, `services` is a **list**, and each entry can be either ```yaml title="unshackle.yaml" directories: services: - - unshackle-dl/services # a GitHub owner/repo shorthand - - https://github.com/me/my-services.git + - you/your-services # a GitHub owner/repo shorthand + - https://example.com/private-services.git - ~/code/local-services # a local folder ``` -Entries are searched in the order listed, and **the first source to define a given service tag wins**, so put local folders last if you want them to act as fallbacks rather than overrides. Remote repositories are cloned and periodically updated for you. See [Services](../dev/creating-a-service.md) for the full details on how service discovery and repositories work. +unshackle searches entries in the order listed, and **the first source to define a given service tag wins**, so put local folders last if you want them to act as fallbacks rather than overrides. unshackle clones remote repositories on first use and refreshes them at most once a day. See [Creating a Service](../dev/creating-a-service.md) for how service discovery and repositories work. ## The `filenames` key @@ -197,10 +197,10 @@ $ unshackle cfg tag MYGRP $ unshackle cfg cdm.default my_device_l3 ``` -Values are parsed as Python literals where possible, so numbers, booleans, and lists work naturally, while bare text is treated as a string: +unshackle parses the value as a Python literal, so write booleans as `True`/`False` and quote the strings inside a list (`"['en']"`). Anything that is not valid Python literal syntax is stored as a plain string: ```console -$ unshackle cfg update_checks false +$ unshackle cfg update_checks False $ unshackle cfg vault_timeout 30 ``` @@ -216,7 +216,7 @@ $ unshackle cfg cdm.default --unset $ unshackle cfg --list ``` -When it writes, `unshackle cfg` targets the config file that was loaded. If none exists yet, it creates `unshackle.yaml` in your OS user-config directory. +When it writes, `unshackle cfg` targets the config file that was loaded. If none exists yet, it creates `unshackle.yaml` inside the `unshackle` package folder (search location 1), not your OS user-config directory. To keep the config outside the package, create the file at the user-config path yourself first, then `unshackle cfg` writes to it. !!! warning "Editing with `cfg` strips comments" Because `unshackle cfg` rewrites the whole file when it saves, any comments in `unshackle.yaml` are removed by a write. If you keep important notes as comments, edit the file by hand instead, or keep those notes elsewhere. @@ -225,4 +225,4 @@ When it writes, `unshackle cfg` targets the config file that was loaded. If none - Browse the [Configuration Reference](../reference/configuration.md) for every key and default. - Set up your first download in [Downloading](../guide/downloading.md). -- Learn how services are discovered and updated in [Services](../dev/creating-a-service.md). +- Learn how services are discovered and updated in [Creating a Service](../dev/creating-a-service.md). diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 1ddeaf5a..af9d8d52 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -88,8 +88,8 @@ uv run unshackle env check ## External tools on your PATH unshackle shells out to a number of external command-line programs for media -processing and DRM work. It looks for each one first in a `binaries/` folder -next to the installed package, and then falls back to your system `PATH`, so +processing and DRM work. It looks for each one first in the `binaries/` folder +inside the installed package, and then falls back to your system `PATH`, so installing these tools system-wide (or dropping them into that folder) both work. @@ -108,7 +108,7 @@ These must be present for core downloading, decryption, and muxing to work: !!! warning "shaka-packager is the default decryptor" unshackle decrypts with shaka-packager unless you explicitly configure a different decryptor. If it is missing, protected downloads will fail at the - decryption step. See the [configuration reference](configuration-file.md) for the + decryption step. See the [configuration reference](../reference/configuration.md#decryption) for the `decryption` key if you want to switch to `mp4decrypt`. ### Recommended and optional tools @@ -147,7 +147,7 @@ it depends on. unshackle --help ``` -This prints the banner and the list of available commands (`dl`, `env`, `kv`, +This prints the list of available commands (`dl`, `env`, `kv`, `serve`, `wvd`, `prd`, `search`, `util`, `cfg`, `import`). If your shell reports that `unshackle` is not found, the `uv tool` bin directory is probably not on your `PATH`. Run `uv tool update-shell` and open a new terminal. diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 43881be6..f4b75cf9 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -1,15 +1,21 @@ # Quick Start This page takes you from a fresh install to your first downloaded file. You will -create the one piece of configuration unshackle insists on, add a streaming -service and a CDM so it can actually fetch and decrypt media, run a basic -`unshackle dl` command, and learn where the finished file lands. +create the one piece of configuration unshackle insists on, supply the service +module and CDM it needs to fetch and decrypt media, run a basic `unshackle dl` +command, and learn where the finished file lands. !!! note "Before you start" This guide assumes unshackle is already installed and on your `PATH`. If `unshackle --help` does not print a help screen, work through [Installation](installation.md) first. +!!! warning "unshackle includes no services" + unshackle handles manifests, DRM, downloading, and muxing. It does not include a + module for any streaming platform, so you write that part yourself + ([step 3](#3-write-a-service)). `EXAMPLE` in this guide stands in for the tag you + give yours. + ## 1. Check your environment unshackle drives several external tools (FFmpeg, MKVToolNix, shaka-packager, and @@ -58,12 +64,12 @@ Everything else has a sensible default. A minimal, working config looks like thi ```yaml title="unshackle.yaml" output_template: movies: "{title} ({year}) {quality} {source}" - series: "{title} S{season}E{episode} {episode_name} {quality} {source}" + series: "{title} {season_episode} {episode_name?} {quality} {source}" ``` Each `{variable}` is filled in from the title and the tracks you downloaded. The full set of valid variables, including `resolution`, `video`, `audio`, `hdr`, -`edition`, `tag`, and more, is documented in [Configuration](configuration-file.md). +`edition`, `tag`, and more, is documented in [Output and Naming](../guide/output-and-naming.md). !!! note "Spaces or dots?" unshackle auto-detects your naming style from the template: if the separators @@ -77,51 +83,15 @@ full set of valid variables, including `resolution`, `video`, `audio`, `hdr`, rewrites the file and **removes any comments** it contained. If you keep notes in your config, edit the file by hand instead. -## 3. Add a service +## 3. Write a service -The service tag you pass to `dl` (like `EXAMPLE`) maps to a **service module**, a small -plugin that knows how to talk to one streaming platform. unshackle ships without -any bundled services, so you add the one(s) you want. +The service tag you pass to `dl` (like `EXAMPLE`) maps to a **service module**, the +plugin that talks to one streaming platform. unshackle includes none, so you write +the one you need. [Creating a Service](../dev/creating-a-service.md) covers how. -Services live under the `directories.services` path (run `unshackle env info` to -see where that is). unshackle discovers them by looking for `*/__init__.py`: the -folder name becomes the **service tag**, and the folder must contain a class with -that exact same name. - -=== "Local folder" - - Drop a service folder into your services directory. A service tagged `EXAMPLE` - lives at: - - ``` - /EXAMPLE/__init__.py # defines a class named EXAMPLE - ``` - - Once the folder is in place, the tag `EXAMPLE` is immediately available to `dl`, - `search`, and the other service-aware commands. - -=== "Git repository" - - `directories.services` may also list git repositories. unshackle clones them - on first use and keeps them up to date: - - ```yaml title="unshackle.yaml" - directories: - services: - - owner/unshackle-services # a GitHub owner/repo shorthand - - /path/to/local/services # local folders still work too - ``` - - Force a refresh of every configured service repo at any time with: - - ```shell - unshackle util refresh-services - ``` - -!!! info "Where to get services" - Service modules are kept separate from the core project. Ask in the community - [Discord](https://discord.gg/mHYyPaCbFK) for where to find the service you - need. The core repository deliberately does not host them. +Put the finished module in your `directories.services` folder (`unshackle env info` +shows the path). The tag then works with `dl`, `search`, and the other service +commands. ## 4. Add a CDM for DRM @@ -144,7 +114,7 @@ cdm: ``` PlayReady works the same way with `.prd` files created and managed by the -[`prd`](configuration-file.md) command. If a title is DRM-free, no CDM is needed. +[`prd`](../guide/cli-reference.md#prd) command. If a title is DRM-free, no CDM is needed. !!! tip Run `unshackle wvd parse my_device` to inspect a device's security level and @@ -199,7 +169,7 @@ unshackle dl - **`<SERVICE-TAG>`** picks which service to talk to (e.g. `EXAMPLE`). - **`<TITLE>`** is the URL, ID, or slug the service understands. -A good first command asks for 1080p with English audio and subtitles: +A good first command asks for 1080p with English audio: ```shell title="Your first download" unshackle dl -q 1080 -l en EXAMPLE 81234567 @@ -213,7 +183,7 @@ keys through your CDM (and any key vaults), then decrypt, mux, and tag the resul | Flag | Meaning | |---|---| | `-q`, `--quality` | Target resolution(s), e.g. `-q 1080` or `-q 1080,720`. Defaults to best available. | -| `-l`, `--lang` | Language(s) for video and audio, e.g. `-l en` or `-l orig,en`. `orig` = the title's original language. | +| `-l`, `--lang` | Language(s) for video and audio, e.g. `-l en` or `-l orig,en`. `orig` = the title's original language. Defaults to `orig`. | | `-sl`, `--s-lang` | Subtitle language(s); defaults to `all`. | | `-v`, `--vcodec` | Video codec, e.g. `-v H.265`. Defaults to any. | | `-r`, `--range` | Dynamic range, e.g. `-r HDR10` or `-r DV`. Defaults to `SDR`. | @@ -238,18 +208,18 @@ codec, bitrate, channel-layout, and track-type selection. ## 8. Where the output lands -By default, finished files are written to the `downloads` directory +By default, unshackle writes finished files to the `downloads` directory (`unshackle env info` shows its exact path; the built-in default is a `downloads` -folder alongside the project). Override it per run with `-o`: +folder one level above the installed `unshackle` package). Override it per run with `-o`: ```shell unshackle dl -q 1080 -o /mnt/media/incoming EXAMPLE 81234567 ``` -- **Movies** are written as a single `.mkv` file named from your `movies` template. +- unshackle writes **movies** as a single `.mkv` file named from your `movies` template. - **TV episodes** are grouped into a per-show / per-season folder (from your `series` template) unless you pass `--no-folder`. -- The filename is built from your `output_template`, and IMDb/TMDB IDs are added to +- unshackle builds the filename from your `output_template` and adds IMDb/TMDB IDs to the file's metadata tags when available. To change the default output location permanently, set it in your config: @@ -270,5 +240,5 @@ directories: !!! tip "You are never far from help" `unshackle --help`, `unshackle dl --help`, and `unshackle <command> --help` - list every option with its default. When in doubt, add `--list` to a `dl` + list every option. When in doubt, add `--list` to a `dl` command to preview what it would do before it downloads anything. diff --git a/docs/guide/downloading.md b/docs/guide/downloading.md index 07b84768..98cc3f95 100644 --- a/docs/guide/downloading.md +++ b/docs/guide/downloading.md @@ -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 @@ -347,7 +351,7 @@ with `-` to exclude it. | --- | --- | | `--select-titles` | Interactively pick episodes from a list (Series only). **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 +402,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 +432,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 diff --git a/docs/index.md b/docs/index.md index b71f6f46..5eb5fcb3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,18 +5,20 @@ media from streaming manifests, handles the DRM, and muxes everything into clean well-tagged files, all driven from a single command-line tool, or served over a REST API for automation. -It is a fork of [Devine](https://github.com/devine-dl/devine) with first-class -DASH, HLS, and Smooth Streaming (ISM) parsing, both **Widevine** and **PlayReady** -DRM support, and a built-in HTTP server. +It is a fork of [Devine](https://github.com/devine-dl/devine) with DASH, HLS, and +Smooth Streaming (ISM) parsing, both **Widevine** and **PlayReady** DRM support, +and a built-in HTTP server. !!! warning "Use responsibly" - unshackle is licensed under [GPL-3.0](https://github.com/unshackle-dl/unshackle/blob/master/LICENSE). + unshackle is licensed under [GPL-3.0](https://github.com/unshackle-dl/unshackle/blob/main/LICENSE). Do not use it for content you do not have the rights to. Keep the core free and open, keep service code private, and be kind. ## What it does -Point unshackle at a title on a streaming service and it will: +unshackle does everything except talk to the streaming platform. You write that part +as a service module ([Creating a Service](dev/creating-a-service.md)). Point unshackle +at a title and it will: - Fetch the title metadata (movies, full series, seasons, episodes, albums, tracks). - Parse the streaming manifest: **DASH**, **HLS**, or **Smooth Streaming (ISM)**. @@ -40,7 +42,7 @@ Point unshackle at a title on a streaming service and it will: | Subtitles | SRT, WebVTT, ASS/SSA, TTML and more, with optional SDH stripping and format conversion | | Key vaults | SQLite, MySQL, and HTTP/API vaults to store and share content keys | | Proxies | Basic proxies plus NordVPN, ProtonVPN, Surfshark, Windscribe, ExpressVPN, Gluetun, and Hola providers | -| Services | Pluggable service modules, loadable from local folders or git repositories | +| Services | A plugin API for the service modules you write. unshackle includes none | | Automation | A REST API with a job queue, live progress, history, and remote-download sessions | ## Installation at a glance @@ -70,7 +72,7 @@ Point unshackle at a title on a streaming service and it will: Every download is three parts: the `dl` command, a **service tag**, and the service's title argument (a URL, ID, or slug). -```shell title="Download a 1080p title with English audio and subtitles" +```shell title="Download a 1080p title with English audio" unshackle dl -q 1080 -l en EXAMPLE 81234567 ``` @@ -84,7 +86,7 @@ install to your first file. - **[Installation](getting-started/installation.md)**. Install unshackle and the external tools it depends on. - **[Quickstart](getting-started/quickstart.md)**. Configure `unshackle.yaml` and run your first download. - **[Downloading](guide/downloading.md)**. The full `dl` command guide: quality, codecs, languages, track selection, and output. -- **[Configuration](getting-started/configuration-file.md)**. Every `unshackle.yaml` key, directories, vaults, proxies, and naming templates. +- **[Configuration](getting-started/configuration-file.md)**. Where `unshackle.yaml` lives, the directory layout, and how to edit values. Full key list in the [Configuration Reference](reference/configuration.md). - **[REST API](dev/rest-api/index.md)**. Run the `serve` HTTP server and drive downloads programmatically. !!! tip "Getting help" diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 681df844..22ab66da 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -1,6 +1,6 @@ # Configuration Reference -This page is the exhaustive, authoritative reference for **every** key you can set in +This page documents every key you can set in `unshackle.yaml`. For each key you will find its name, expected type, default value, and what it does. Keys are grouped by area so you can jump straight to the part of unshackle you are configuring. @@ -10,10 +10,12 @@ edit it), start with [The Configuration File](../getting-started/configuration-f This page assumes you already know the basics and want precise details. !!! info "How defaults work" - Every key is optional. If you omit a key, unshackle uses the built-in default listed - here. There is **no schema validation**: unknown keys are silently ignored (they never - become settings), and the file is parsed with `yaml.safe_load`. An empty or missing - file means *all* defaults apply. + Every key is optional except [`output_template`](#output-naming), which `dl` requires. + Without it, `dl` aborts with an error. If you omit any other key, unshackle uses + the built-in default listed here. There is **no schema validation**: unknown keys are + silently ignored (they never become settings), and the file is parsed with + `yaml.safe_load`. An empty or missing file means every other key falls back to its + default. !!! warning "Two keys behave specially on load" - `curl_impersonate` is a **deprecated** alias of [`network`](#network-proxy) and emits a @@ -32,7 +34,7 @@ This page assumes you already know the basics and want precise details. |------|------| | [Directories](#directories) | `directories`, `filenames` | | [Services & authentication](#services-auth) | `services`, `credentials`, `firefox_cookies`, `remote_services`, `serve` | -| [Download & processing](#download-processing) | `dl`, `subtitle`, `audio`, `muxing`, `language_tags` | +| [Download & processing](#download-processing) | `dl`, `subtitle`, `audio`, `muxing`, `language_tags`, `dual_multi_mode` | | [Output & naming](#output-naming) | `output_template`, `tag`, `tag_group_name`, `tag_imdb_tmdb`, `chapter_fallback_name`, `unicode_filenames` | | [DRM & CDM](#drm-cdm) | `cdm`, `remote_cdm`, `decryption` | | [Network & proxy](#network-proxy) | `network`, `headers`, `proxy_providers` | @@ -91,7 +93,7 @@ directories: ```yaml directories: services: - - unshackle-dl/community-services # cloned from GitHub + - you/your-services # cloned from GitHub - https://example.com/private.git@stable - ~/my-services # local, lowest priority ``` @@ -238,7 +240,7 @@ is the [REST API](../dev/rest-api/index.md) section; these are the config keys. | `api_secret` | str | *(unset)* | Master secret accepted in the `X-Secret-Key` header. | | `users` | dict | `{}` | Per-user API keys and their allowlists (see below). | | `services` | list | *(unset)* | Global service allowlist. Omit to allow all. | -| `remote_only` | bool | `false` | Run without a local `output_template` (CORS/Cloudflare-friendly). | +| `remote_only` | bool | `false` | Expose only the remote service session endpoints (health, services, search, session) and disable the rest of the REST API. | | `session_ttl` | int (s) | `300` | Lifetime of an interactive auth session. | | `max_sessions` | int | `100` | Maximum concurrent sessions. | | `history_limit` | int | `100` | How many finished jobs to retain in history. | @@ -247,7 +249,6 @@ is the [REST API](../dev/rest-api/index.md) section; these are the config keys. | `allow_job_credentials` | bool | `false` | Permit clients to supply credentials per job. | | `devices` | list | *(auto)* | Widevine devices offered; auto-filled from `directories.wvds`. | | `playready_devices` | list | *(auto)* | PlayReady devices; auto-filled from `directories.prds`. | -| `country` / `locations` | str / dict | *(unset)* | Advertised region metadata. | Each entry under `users` is keyed by that user's API key and may set its own `services`, `devices`, and `playready_devices` allowlists, narrowing the global ones. @@ -266,9 +267,10 @@ serve: ``` !!! tip "Declaring `dl` defaults under `serve`" - You can set any [`dl`](#dl) flag key (`downloads`, `workers`, `best_available`, and so on) - directly inside `serve`. The point is to raise concurrency and behaviour defaults - **server-wide** (every request the server handles picks them up) without having to change + You can set most [`dl`](#dl) flag keys (`downloads`, `workers`, `best_available`, and so on) + directly inside `serve`; the server recognises a fixed subset of download parameters, so a + few CLI-only flags are ignored here. This raises concurrency and behaviour defaults + **server-wide** (every request the server handles picks them up) without changing each client call individually. --- @@ -299,7 +301,7 @@ after the flag's internal destination rather than its visible name; set these ex | `-o` / `--output` | `output_dir` | | `--cdm-only` / `--vaults-only` | `cdm_only` | -**Precedence** (highest first): an explicit CLI flag or environment variable → a per-service +**Precedence** (highest first): an explicit CLI flag → a per-service [`services.<TAG>.dl`](#services-auth) override → this global `dl:` block → the built-in default. In other words, config only fills in options you did not set on the command line. @@ -369,7 +371,7 @@ written as sidecar files. See [Subtitles](../guide/subtitles.md) for the full gu | `convert_before_strip` | bool | `true` | Convert to the working format before stripping SDH. | | `conversion_method` | str | `"auto"` | Subtitle conversion backend. | | `preferred_conversion_method` | str | *(unset)* | Preferred converter when several are available. | -| `output_mode` | str | `"mux"` | `mux` embeds subtitles in the MKV; `sidecar` writes separate files. | +| `output_mode` | str | `"mux"` | `mux` embeds subtitles in the MKV; `sidecar` writes separate files; `both` writes sidecars and still muxes. | | `sidecar_format` | str | `"srt"` | Format for sidecar files when `output_mode: sidecar`. | | `type_priority` | list | *(unset)* | Ordered ranking of subtitle types (`forced`, `normal`, `sdh`; CC counts as SDH) that sorts the variants within each language. Types you leave out fall to the end. | @@ -406,9 +408,9 @@ audio: ``` !!! note "`codec_priority` is a soft priority: nothing is dropped" - `codec_priority` only breaks ties **among tracks that share the same bitrate and language**. - Codecs not in the list keep their normal bitrate-based ordering and are simply placed after - all listed codecs, and none are ever removed. Two higher-level rules still override it: **Atmos + `codec_priority` outranks bitrate: any listed codec sorts above every unlisted one, and among + listed codecs the list order decides. Bitrate order survives only within a single codec rank. + Nothing is ever removed. Two higher-level rules still override it: **Atmos tracks always take precedence over `codec_priority`**, and **audio-description tracks are always forced to the end** regardless of it. When `codec_priority` is unset, tracks sort by bitrate alone, with those same Atmos/descriptive rules still applied. @@ -424,7 +426,7 @@ Matroska (MKV) muxing options. | `set_title` | bool | `true` | Write a human-readable title into the MKV container. | | `default_language` | dict | `{}` | Force which language is flagged *default* per track type, e.g. `{audio: en, subtitle: en}`. | | `merge_audio` | bool | `true` | Merge all audio into one file. `--split-audio` on the CLI flips this off. | -| `merge_video` | bool | `false` | Merge all video tracks into one file. `--merge-video` on the CLI flips this on. | +| `merge_video` | bool | `false` | Merge video tracks that share height, range, and codec into one file, so only language varies inside a file. `--merge-video` on the CLI flips this on. | ```yaml muxing: @@ -451,9 +453,6 @@ A rule may test: ```yaml language_tags: rules: - - audio: [en] - subs_contain_all: [en] - tag: "" # native English release, no tag - audio: [ja] subs_contain: [en] tag: "SUBBED" @@ -498,7 +497,7 @@ For the full picture of how templates render, see ### `output_template` -- **Type:** `dict`  ·  **Default:** `{}` +- **Type:** `dict`  ·  **Required** (no usable default; `dl` exits with an error if this is missing) Filename templates keyed by media kind, using `{variable}` placeholders. Recognised kinds are `movies`, `series`, and `songs` (and `albums` for folder templates). A trailing `?` on a @@ -627,7 +626,7 @@ case-insensitively; `"mp4decrypt"` selects Bento4's mp4decrypt, and **anything e ```yaml decryption: default: shaka - SOMESERVICE: mp4decrypt + EXAMPLE1: mp4decrypt ``` --- @@ -758,8 +757,9 @@ key_vaults: !!! tip "Common per-entry options" - `no_push: true` makes a vault read-only (keys are fetched but never written to it). - - A vault of `type: api` whose `name` contains `decrypt_labs` auto-fills its `token` from - [`decrypt_labs_api_key`](#external-api-keys) when not set inline. + - A vault of `type: API` whose `name` contains `decrypt_labs` auto-fills its `token` from + [`decrypt_labs_api_key`](#external-api-keys) when not set inline. Vault `type` values are + case-sensitive module names. - An all-zero content key (32 zeros) is treated as "no key" everywhere and is never stored. @@ -786,9 +786,9 @@ All default to an empty string and enable optional metadata/geolocation features | `ipinfo_api_key` | str | `""` | ipinfo.io API key for IP/region lookups. | !!! tip "SIMKL is a fallback for TMDB" - SIMKL is positioned as an alternative metadata source, and it is most valuable precisely - **when no `tmdb_api_key` is configured**. It improves title-matching and tagging - reliability in that case rather than replacing TMDB where TMDB is available. + SIMKL is an alternative metadata source. Use it when you have no `tmdb_api_key` + configured; it improves title matching and tagging in that case rather than replacing + TMDB. !!! 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