diff --git a/docs/ADVANCED_CONFIG.md b/docs/ADVANCED_CONFIG.md index bd2a388a..701b77b7 100644 --- a/docs/ADVANCED_CONFIG.md +++ b/docs/ADVANCED_CONFIG.md @@ -159,11 +159,25 @@ debug_keys: true --- +## debug_requests (bool) + +Controls whether HTTP-library logs are shown during `--debug` sessions. Default: `false` + +When `--debug` is active and this is `false`, unshackle silences the noisy per-request logs emitted by `urllib3`, `requests`, `rnet`, `httpx`, and related libraries (e.g. `GET ...seg-001.m4s 200`) so that unshackle's own structured DEBUG output stays readable. Set to `true` to see every individual HTTP request, including per-segment downloads. + +Has no effect when `--debug` is not active. + +```yaml +debug_requests: true +``` + +--- + ## set_terminal_bg (bool) -Controls whether unshackle should set the terminal background color. Default: `false` +Controls whether unshackle applies its theme background color to the terminal. Default: `false` -For example, +When enabled, the Rich console theme's background color is appended to the foreground styles for both the general color scheme and the ASCII art banner, so the full color palette renders correctly on terminals whose default background differs from the theme. Set to `true` if the banner or colored output looks off on your terminal. ```yaml set_terminal_bg: true diff --git a/docs/API.md b/docs/API.md index e5413651..1500f6f1 100644 --- a/docs/API.md +++ b/docs/API.md @@ -93,6 +93,31 @@ serve: Layering order: built-in defaults < `serve.*` overrides < service-specific click defaults < request body. +### Per-request override gates + +Two security gates under `serve:` guard capabilities that `/api/download` clients can request. Both default OFF; an unset or `false` value causes the server to reject the request with `403 FORBIDDEN`. + +**`cdm_overrides`** — a `/api/download` job body may include a `cdm` field to select a specific server-side device. When this gate is off, every such request is rejected. Set to `true` to allow any device the server has, or to a list of device names to restrict to an explicit subset: + +```yaml +serve: + # Allow any server-side device: + cdm_overrides: true + + # Or restrict to specific devices: + cdm_overrides: + - generic_nexus_4464_l3 +``` + +**`allow_job_credentials`** — a `/api/download` job body may include a `credential` (single) or `credentials` (map) field to authenticate the job with client-supplied secrets instead of the server's configured credentials. When this gate is off, any job containing either field is rejected. Each distinct credential gets its own isolated token cache on the server. Set to `true` to allow: + +```yaml +serve: + allow_job_credentials: true +``` + +Both gates are independent and can be combined. A typical locked-down deployment leaves both unset; a trusted single-client deployment might enable both. + --- ## Endpoint Map @@ -779,3 +804,70 @@ downloading -> cancelled Jobs are retained for 24 hours after completion (override via top-level `download_job_retention_hours` in `unshackle.yaml`). The server runs up to 2 concurrent download jobs by default; override via top-level `max_concurrent_downloads`. This is independent of `serve.downloads`, which controls parallel tracks **within** a single job. Remote sessions are managed by `SessionStore` (`unshackle/core/api/session_store.py`); idle sessions and their `InputBridge` instances are cleaned up by a background loop started/stopped with the app lifecycle. + +--- + +## Client Configuration: `remote_services` + +`remote_services` is a top-level block in `unshackle.yaml` on the **client** machine. It tells `dl` how to reach a remote `unshackle serve` instance instead of running service code locally. The client handles track selection, download, decrypt, and mux; the server handles auth, manifest parsing, and (optionally) DRM licensing. + +### CLI flags + +| Flag | Description | +| --- | --- | +| `--remote` | Use a remote server instead of local service code. Reads `remote_services` from config. | +| `--server ` | Select a named server when multiple are configured. If only one entry exists it is picked automatically. | + +```bash +unshackle dl --remote EXAMPLE1 "https://example.com/show/abc123" +unshackle dl --remote --server us-server EXAMPLE1 "https://example.com/show/abc123" +``` + +### Config structure + +```yaml +remote_services: + my-server: + url: "http://192.168.1.100:8786" # base URL of the remote serve instance + api_key: "your-secret-key-here" # sent as X-Secret-Key; omit or set "" for --no-key servers + + # server_cdm: the server runs its own CDM and returns KID:KEY pairs (mode: server_cdm). + # false (default): client runs its own CDM; license challenges are proxied through the server. + server_cdm: false + + # Per-service overrides applied on the client side after the remote session is set up. + # Supported keys: decryption, downloader, cdm (and any free-form service config keys). + services: + EXAMPLE1: + decryption: mp4decrypt # override decryption tool for this service (shaka or mp4decrypt) +``` + +**`server_cdm`** — when `false` (default) the client builds CDM challenges locally and calls `POST /api/session/{id}/license` with `mode: "proxy"`; the server forwards the challenge to the service's license endpoint and returns raw license bytes. When `true` the client skips local CDM setup and calls with `mode: "server_cdm"`; the server performs the full CDM flow and returns `{ "keys": { "": "" } }`. The server-side user key must have a matching device configured (`devices` / `playready_devices`) for `server_cdm` to work. + +### Multiple servers + +When more than one server is configured, `--server ` is required: + +```yaml +remote_services: + us-server: + url: "https://us.example.com:8786" + api_key: "us-key" + server_cdm: true + services: + EXAMPLE1: + decryption: mp4decrypt + + eu-server: + url: "https://eu.example.com:8786" + api_key: "eu-key" + server_cdm: false +``` + +```bash +unshackle dl --remote --server us-server EXAMPLE1 "https://example.com/show/abc123" +``` + +### Cache forwarding + +On session open the client sends any local `*.json` cache files for the service (tokens, etc.) to the server so it can skip interactive re-auth. On session close the server returns updated cache files; the client writes them back locally so the next remote session can forward them again. diff --git a/docs/DRM_CONFIG.md b/docs/DRM_CONFIG.md index d8866cda..0ab8972e 100644 --- a/docs/DRM_CONFIG.md +++ b/docs/DRM_CONFIG.md @@ -178,78 +178,141 @@ remote_cdm: use_vaults: true # Integrate with vault system ``` -**Advanced Example with Field Mapping:** +**Full field reference:** ```yaml remote_cdm: - name: advanced_custom_api type: custom_api host: https://api.example.com - device: - name: L1 - type: ANDROID - security_level: 1 + timeout: 30 # Default request timeout (seconds); overridden per-endpoint - # Authentication configuration + device: + name: L1 # Passed as 'scheme' in base request params + type: ANDROID # CHROME, ANDROID, or PLAYREADY + system_id: 26830 # Widevine system ID (informational; not sent) + security_level: 1 # 1–3 for Widevine; 2000/3000 for PlayReady + + # Authentication — applied to every request via session headers or Basic auth auth: - type: header - header_name: X-API-Key - key: YOUR_SECRET_KEY - custom_headers: + type: header # bearer | header | basic | body | query + header_name: X-API-Key # used when type=header (default: Authorization) + key: YOUR_SECRET_KEY # API key / token / password + # bearer_token: TOKEN # explicit token for type=bearer (falls back to 'key') + # username: user # required for type=basic + # password: pass # required for type=basic + custom_headers: # merged into every request User-Agent: Unshackle/3.1.0 X-Client-Version: "1.0" - # Endpoint configuration + # Endpoints — both keys are required endpoints: - get_request: + get_request: # first call: obtain challenge or cached keys path: /v2/challenge method: POST - timeout: 30 - decrypt_response: + timeout: 30 # overrides top-level timeout for this endpoint + decrypt_response: # second call: exchange license response for keys path: /v2/decrypt method: POST timeout: 30 - # Request parameter mapping + # request_mapping — transform outgoing parameters before each request + # Base params sent to get_request: scheme, init_data, [service], [service_certificate] + # Base params sent to decrypt_response: scheme, session_id, init_data, license_request, license_response request_mapping: get_request: - param_names: - init_data: pssh # Rename 'init_data' to 'pssh' - scheme: device_type # Rename 'scheme' to 'device_type' - static_params: - api_version: "2.0" # Add static parameter + param_names: # rename base param keys + init_data: pssh + scheme: device_type + static_params: # always-added fixed values + api_version: "2.0" + conditional_params: # added only when condition is true + - condition: "is_playready == True" + params: + drm_type: playready + transforms: # applied after renaming; type values listed below + - param: pssh + type: base64_encode + nested_params: # group named params under a parent key + payload: + - pssh + - device_type + exclude_params: # remove unwanted params before sending + - service_certificate decrypt_response: param_names: license_request: challenge license_response: license - # Response field mapping + # response_mapping — extract and validate fields from API responses response_mapping: get_request: - fields: - challenge: data.challenge # Deep field access + fields: # map standard names to dot-path locations in response JSON + challenge: data.challenge session_id: session.id - success_conditions: - - status == 'ok' # Validate response + message: status.message + response_types: # classify response so CDM can choose the right path + - condition: "message_type == 'license-request'" + type: license_request # CDM expects challenge + session_id + - condition: "message_type == 'cached-keys'" + type: cached_keys # CDM skips license round-trip + success_conditions: # all must be true; failure raises RequestException + - "message == 'success'" + error_fields: # inspected when success_conditions fail (default: error, message, details) + - error + - details + transforms: # applied to extracted fields + - field: challenge + type: base64_decode decrypt_response: fields: keys: data.keys - key_fields: - kid: key_id # Map 'kid' field - key: content_key # Map 'key' field + message: status.message + key_fields: # map key-object property names to standard names + kid: key_id # default: kid + key: content_key # default: key + type: key_type # default: type (value used as-is; CONTENT treated as content key) + success_conditions: + - "message == 'success'" + # caching — vault and API-level key caching caching: - enabled: true - use_vaults: true - check_cached_first: true # Check cache before API calls + enabled: true # master switch (default: true) + use_vaults: true # check/write unshackle key vaults (default: true) + check_cached_first: true # check vaults before making any API call (default: true) ``` -**Supported Authentication Types:** -- `bearer` - Bearer token authentication -- `header` - Custom header authentication -- `basic` - HTTP Basic authentication -- `body` - Credentials in request body -- `query` - Authentication added to query string parameters +**Supported `transforms` types** (for both `request_mapping` and `response_mapping`): +`base64_encode`, `base64_decode`, `hex_encode`, `hex_decode`, `json_stringify`, `json_parse`, `parse_key_string` + +**Supported authentication types:** +- `bearer` — `Authorization: Bearer ` (or `bearer_token` field) +- `header` — arbitrary header; set `header_name` (default `Authorization`) +- `basic` — HTTP Basic auth via `username` / `password` +- `body` — credentials injected into request body (handled by request_mapping) +- `query` — credentials appended to query string (handled by request_mapping) + +### PyPlayReady Remote CDM + +Connects to a running `unshackle serve` instance for PlayReady licensing. Uses +`pyplayready.remote.remotecdm.RemoteCdm` under the hood. No `type` field is needed — +the entry is routed to PlayReady when `device_type: PLAYREADY` is set (same branching +logic as the legacy Widevine path). + +```yaml +remote_cdm: + - name: playready_remote + device_name: my_prd_device # Device name registered on the serve instance + device_type: PLAYREADY # Required to select the PlayReady code path + system_id: 0 # Not used by pyplayready RemoteCdm; set to 0 + security_level: 3000 # 2000 = SL2000, 3000 = SL3000 + host: http://127.0.0.1:8786/playready # Must include the /playready path suffix + secret: your-api-secret-key # X-Secret-Key sent to the serve instance +``` + +**Note:** The `/playready` path suffix in `host` is required — `pyplayready.RemoteCdm` +appends its own endpoint paths on top of this base, so omitting the suffix will result +in 404 errors. ### Legacy PyWidevine Serve Format diff --git a/docs/GLUETUN.md b/docs/GLUETUN.md index 2b06b9d1..2ee1f89b 100644 --- a/docs/GLUETUN.md +++ b/docs/GLUETUN.md @@ -241,3 +241,81 @@ Common issues: - [Gluetun Wiki](https://github.com/qdm12/gluetun-wiki) - Official provider documentation - [Gluetun GitHub](https://github.com/qdm12/gluetun) + +--- + +# ExpressVPN HTTPS Proxy + +ExpressVPN is a standalone HTTPS proxy provider — it does **not** use Docker or the gluetun sub-provider system. It authenticates via a browser-extension OAuth flow (Keycloak/PKCE) and returns an authenticated `https://cat:@:443` proxy URL. + +## Prerequisites + +An active ExpressVPN subscription is required. The provider resolves proxy-capable locations through ExpressVPN's own API, so no local VPN client or Docker container is needed. + +## Authentication + +The provider uses a token cascade (tried in order): + +1. **Cached tokens** — `cache/global/expressvpn_tokens.json` (auto-written after first auth; access and refresh tokens are refreshed automatically when they expire). +2. **Exported browser cookies** — `cookies/vpn/expressvpn.txt` (also accepted at `cookies/vpns/expressvpn.txt`). Must contain the `KEYCLOAK_IDENTITY` and `KEYCLOAK_SESSION` cookies from `auth.expressvpn.com`. Used for a one-time PKCE bootstrap that populates the token cache. +3. **Desktop `account.json`** — configured via the `account_json` key (see below). + +Export cookies from a browser that is logged in to `auth.expressvpn.com`. Any Netscape-format, JSON array, or `name=value` file is accepted. + +## Configuration + +Add to `~/.config/unshackle/unshackle.yaml` under `proxy_providers`: + +```yaml +proxy_providers: + expressvpn: + region_map: + us: "ny" # --proxy expressvpn:us → New York (default city) + gb: "london" # --proxy expressvpn:gb → London + de: "frankfurt" # --proxy expressvpn:de → Frankfurt + es: "madrid" # --proxy expressvpn:es → Madrid + mx: # --proxy expressvpn:mx → smart/random MX location +``` + +`region_map` maps a country code to a default city preset. An empty (null) value enables smart connection (a random location in that country). The preset is only applied when the CLI query contains no city; an explicit city in the query always takes precedence. + +### Optional keys + +| Key | Default | Description | +|-----|---------|-------------| +| `region_map` | `{}` | Country → default city presets (see above) | +| `server_map` | `{}` | Alias → ExpressVPN location slug or direct hostname | +| `cookie_path` | `cookies/vpn/expressvpn.txt` | Path to exported browser cookies | +| `cache_path` | `cache/global/expressvpn_tokens.json` | Token cache location | +| `account_json` | *(none)* | Path to ExpressVPN desktop `account.json` | +| `refresh_token` | *(none)* | OAuth refresh token (manual override) | +| `timeout` | `10.0` | HTTP request timeout in seconds | + +## Usage + +```bash +unshackle dl SERVICE CONTENT --proxy expressvpn:us # random/smart US location +unshackle dl SERVICE CONTENT --proxy expressvpn:us-ny # specific city (New York) +unshackle dl SERVICE CONTENT --proxy expressvpn:us-ny-2 # pinned server #2 in New York +unshackle dl SERVICE CONTENT --proxy expressvpn:mx # random/smart Mexico location +unshackle dl SERVICE CONTENT --proxy expressvpn:es-madrid # specific city (Madrid) +``` + +Query format: `expressvpn:[-[-]]` + +- `country` — 2-letter country code (e.g. `us`, `gb`, `de`). +- `city` — optional city abbreviation or slug matched against location names using first-letter abbreviation (`ny` → "New York"), exact slug, prefix, then substring strategies. +- `N` — optional 1-based server index (e.g. `-2` selects the second server in the matched city). If `N` exceeds the available server count, a random server is selected. + +## City matching + +The city part of the query is matched against ExpressVPN location names using these strategies (in priority order): + +1. First-letter abbreviation — `ny` matches "New York" +2. Exact slug — `miami` matches "Miami" +3. Prefix match — `mia` matches "Miami" +4. Substring match — `york` matches "New York" + +## Token cache + +After the first successful authentication the token cache is written to `cache/global/expressvpn_tokens.json` (mode 0600). Subsequent runs refresh expired access/SRT/connection tokens automatically without re-reading cookies. diff --git a/docs/OUTPUT_CONFIG.md b/docs/OUTPUT_CONFIG.md index 74856185..5ac343ff 100644 --- a/docs/OUTPUT_CONFIG.md +++ b/docs/OUTPUT_CONFIG.md @@ -32,16 +32,66 @@ filenames: ## output_template (dict) -Configure custom output filename templates for movies, series, and songs. +Configure custom output filename templates for movies, series, songs, and albums. This is **required** in your `unshackle.yaml` — a warning is shown if not configured. -Available variables: `{title}`, `{year}`, `{season}`, `{episode}`, `{season_episode}`, `{episode_name}`, -`{quality}`, `{resolution}`, `{source}`, `{audio}`, `{audio_channels}`, `{audio_full}`, -`{video}`, `{hdr}`, `{hfr}`, `{atmos}`, `{dual}`, `{multi}`, `{tag}`, `{edition}`, `{repack}`, -`{lang_tag}`, `{track_number}`, `{artist}`, `{album}`, `{disc}` - Add `?` suffix to make a variable conditional (omitted when empty): `{year?}`, `{hdr?}`, `{repack?}` +### General variables + +| Variable | Meaning | +|---|---| +| `title` | Title of the movie, episode, or track | +| `year` | Release year | +| `source` | Service source tag (e.g. `AMZN`, `NF`) | +| `tag` | Group/release tag configured via `tag:` | +| `edition` | Edition label (e.g. `Extended`, `Theatrical`) | +| `repack` | `REPACK` when the release is a repack, otherwise empty | +| `lang_tag` | Language tag produced by `language_tags` rules (e.g. `NORDiC`) | + +### Series / episode variables + +| Variable | Meaning | +|---|---| +| `season` | Season number (e.g. `1`) | +| `episode` | Episode number (e.g. `1`) | +| `season_episode` | Combined season/episode (e.g. `S01E01`) | +| `episode_name` | Episode title | + +### Video / audio variables + +| Variable | Meaning | +|---|---| +| `quality` | Resolution label (e.g. `1080p`, `2160p`) | +| `resolution` | Raw resolution string | +| `video` | Video codec label (e.g. `H.264`, `H.265`) | +| `hdr` | HDR format label (e.g. `HDR`, `DV`) | +| `hfr` | HFR label when frame rate exceeds normal threshold | +| `audio` | Audio codec label (e.g. `AAC`, `EAC3`) | +| `audio_channels` | Channel count (e.g. `2.0`, `5.1`) | +| `audio_full` | Combined codec + channels (e.g. `DDP5.1`) | +| `atmos` | `Atmos` when Dolby Atmos is present, otherwise empty | +| `dual` | `DUAL` when two audio languages are present, otherwise empty | +| `multi` | `MULTi` when three or more audio languages are present, otherwise empty | + +### Music variables + +| Variable | Meaning | +|---|---| +| `track_number` | Track number within album | +| `disc` | Disc number | +| `track_total` | Total tracks on disc | +| `disc_total` | Total discs in release | +| `artist` | Track artist | +| `album_artist` | Album-level artist (may differ from track artist) | +| `album` | Album title | +| `release_type` | Release type (e.g. `Album`, `Single`, `EP`) | +| `genre` | Genre string | +| `explicit` | `Explicit` when track has explicit content flag, otherwise empty | +| `isrc` | International Standard Recording Code | +| `upc` | Universal Product Code (album barcode) | +| `label` | Record label name | + ```yaml output_template: # Scene-style (dot-separated) @@ -71,7 +121,9 @@ If not configured, the default folder naming is used: - Songs: `Artist - Album (Year)` `folder` accepts either a single string (applies to all title kinds) or a mapping with per-kind -templates keyed by `movies`, `series`, and/or `songs`. Unknown keys are warned about and ignored. +templates keyed by `movies`, `series`, `songs`, and/or `albums`. Unknown keys are warned about and ignored. + +Useful music variables for album folders: `album_artist`, `album`, `artist`, `year`, `genre`, `label`, `release_type`, `track_total`, `disc_total`. ```yaml output_template: @@ -90,6 +142,7 @@ output_template: # movies: '{title} ({year})' # series: '{title} ({year?})' # songs: '{artist} - {album} ({year?})' + # albums: '{album_artist} - {album} ({year?})' ``` Example outputs: diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..889ce59b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,18 @@ +# docs/ + +This folder is the **canonical configuration reference** for unshackle. `unshackle/unshackle-example.yaml` is a minimal starter config only — it intentionally omits most options. Come here for the full picture. + +## Contents + +| Document | Description | +|---|---| +| [Advanced & System Configuration](ADVANCED_CONFIG.md) | `serve`, debugging, and system-level options not covered elsewhere | +| [REST API Documentation](API.md) | HTTP API exposed by `unshackle serve`: endpoints, auth, remote downloads, CDM hosting | +| [Structured Debug Logging](DEBUG_LOGGING.md) | `-d`/`--debug` JSONL output: schema, redaction rules, `log_event`/`timed_operation` conventions | +| [Download & Processing Configuration](DOWNLOAD_CONFIG.md) | Downloader settings, concurrency, retries, and post-processing behaviour | +| [DRM & CDM Configuration](DRM_CONFIG.md) | Widevine, PlayReady, and ClearKey CDM setup; local and remote CDMs; key vaults | +| [Gluetun VPN Proxy](GLUETUN.md) | Docker-managed VPN proxies via Gluetun; setup, WireGuard ready-detection | +| [Network & Proxy Configuration](NETWORK_CONFIG.md) | Proxy providers, geofence bypass, per-service proxy overrides | +| [Output & Naming Configuration](OUTPUT_CONFIG.md) | Output directory layout, filename templates, folder naming | +| [Service Integration & Authentication](SERVICE_CONFIG.md) | Per-service config blocks, cookies, credentials, and metadata sources | +| [Subtitle Processing Configuration](SUBTITLE_CONFIG.md) | `subtitle:` key options: format conversion, styling, forced-sub rules | diff --git a/docs/SERVICE_CONFIG.md b/docs/SERVICE_CONFIG.md index 7ec54396..0e469ca6 100644 --- a/docs/SERVICE_CONFIG.md +++ b/docs/SERVICE_CONFIG.md @@ -150,6 +150,47 @@ Please be aware that this information is sensitive and to keep it safe. Do not s --- +## firefox_cookies (dict) + +Automatically extract cookies from your running Firefox installation instead of maintaining a +`cookies/.txt` file. Configured as a top-level dict keyed by service tag; each value is +a settings dict for that service. + +```yaml +firefox_cookies: + EXAMPLE: + hosts: + - example.com + - api.example.com + local_storage: false # optional, default false +``` + +**Fields:** + +- `hosts` — list of host patterns to match against Firefox's cookie store. Each pattern must be at + least 3 characters. A pattern matches the exact host **and** any dot-suffix subdomain + (e.g. `example.com` also matches `sub.example.com` and `.example.com`). +- `local_storage` — when `true`, also harvest `localStorage` entries for the listed hosts from + Firefox's `webappsstore.sqlite`. Default: `false`. + +At download time unshackle picks the **most recently modified** valid Firefox profile (the one +whose `cookies.sqlite` has the latest mtime), copies the database to a restricted temporary +directory (`0700`), and reads cookies from it without touching the live profile. If extraction +returns no cookies or fails for any reason, it falls back silently to the normal file-based cookie +(`cookies/.txt` or `cookies//.txt`). + +**Notes:** + +- Firefox must not have an exclusive write lock on the database at the moment of extraction (i.e. + Firefox may be open — the extractor copies both `cookies.sqlite` and its WAL file to capture any + unflushed writes). +- `local_storage: true` is only needed for services that store auth tokens in the browser's + localStorage rather than in HTTP cookies. +- The feature is implemented in `core/utils/firefox_cookie_extractor.py`; the fallback logic lives + in `dl.get_cookie_jar` (`commands/dl.py`). + +--- + ## tmdb_api_key (str) API key for The Movie Database (TMDB). This is used for tagging downloaded files with TMDB, diff --git a/docs/SUBTITLE_CONFIG.md b/docs/SUBTITLE_CONFIG.md index 58d55ef8..3fdfa292 100644 --- a/docs/SUBTITLE_CONFIG.md +++ b/docs/SUBTITLE_CONFIG.md @@ -2,7 +2,7 @@ This document covers subtitle processing and formatting options under the top-level `subtitle:` key in `unshackle.yaml`. -For the canonical example, see `unshackle/unshackle-example.yaml`. +This document is the canonical reference for subtitle configuration. `unshackle/unshackle-example.yaml` is a minimal starter only. ## subtitle (dict) diff --git a/unshackle/unshackle-example.yaml b/unshackle/unshackle-example.yaml index c1ccf367..4db3e1a9 100644 --- a/unshackle/unshackle-example.yaml +++ b/unshackle/unshackle-example.yaml @@ -1,851 +1,57 @@ -# API key for The Movie Database (TMDB) -tmdb_api_key: "" - -# Client ID for SIMKL API (optional, improves metadata matching) -# Get your free client ID at: https://simkl.com/settings/developer/ -simkl_client_id: "" - -# Optional ipinfo.io API token. When set, unshackle uses the free Lite endpoint -# which has higher rate limits and richer IP info (ASN, org, continent). -# Get a free token at: https://ipinfo.io/signup -ipinfo_api_key: "" - -# Group or Username to postfix to the end of all download filenames following a dash -tag: user_tag - -# Enable/disable tagging with group name (default: true) -tag_group_name: true - -# Enable/disable tagging with IMDB/TMDB/TVDB details (default: true) -tag_imdb_tmdb: true - -# Set terminal background color (custom option not in CONFIG.md) -set_terminal_bg: false - -# Custom output templates for filenames -# Configure output_template in your unshackle.yaml to control filename format. -# If not configured, default scene-style templates are used and a warning is shown. -# Available variables: {title}, {year}, {season}, {episode}, {season_episode}, {episode_name}, -# {quality}, {resolution}, {source}, {audio}, {audio_channels}, {audio_full}, -# {video}, {hdr}, {hfr}, {atmos}, {dual}, {multi}, {tag}, {edition}, {repack}, -# {lang_tag} -# Conditional variables (included only if present): Add ? suffix like {year?}, {episode_name?}, {hdr?} -# Customize the templates below: +# unshackle configuration (copy to unshackle.yaml) # -# Example outputs: -# Scene movies: 'The.Matrix.1999.1080p.SERVICE.WEB-DL.DDP5.1.H.264-EXAMPLE' -# Scene movies (HDR): 'Dune.2021.2160p.SERVICE.WEB-DL.DDP5.1.HDR10.H.265-EXAMPLE' -# Scene movies (REPACK): 'Dune.2021.REPACK.2160p.SERVICE.WEB-DL.DDP5.1.H.265-EXAMPLE' -# Scene series: 'Breaking.Bad.2008.S01E01.Pilot.1080p.SERVICE.WEB-DL.DDP5.1.H.264-EXAMPLE' -# Plex movies: 'The Matrix (1999) 1080p' -# Plex series: 'Breaking Bad S01E01 Pilot' +# This is a MINIMAL starter. Every option has a sane default, so unshackle runs +# even with most of this file empty or removed. +# +# The full, canonical configuration reference lives in docs/ — start at +# docs/README.md. Each section below points to the doc that documents it. + +# Group or username appended to download filenames after a dash. +tag: "NOGRP" + +# --- Output filename & folder templates --- docs/OUTPUT_CONFIG.md +# Full variable list (35 vars incl. music) and all naming options are +# documented in docs/OUTPUT_CONFIG.md. If omitted, scene-style defaults are +# used (with a warning). Conditional variables take a ? suffix, e.g. {year?}. output_template: # Scene-style naming (dot-separated) - movies: '{title}.{year}.{repack?}.{edition?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}' - series: '{title}.{year?}.{season_episode}.{episode_name?}.{repack?}.{edition?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}' - songs: '{track_number}.{title}.{repack?}.{edition?}.{source?}.WEB-DL.{audio_full}.{atmos?}-{tag}' + movies: "{title}.{year?}.{repack?}.{edition?}.{lang_tag?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}" + series: "{title}.{year?}.{season_episode}.{episode_name?}.{repack?}.{edition?}.{lang_tag?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}" + songs: "{track_number}.{title}.{repack?}.{edition?}.{lang_tag?}.{source?}.WEB-DL.{audio_full}.{atmos?}-{tag}" + folder: + movies: "{title}.{year?}.{repack?}.{edition?}.{lang_tag?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}" + series: "{title}.{year?}.{repack?}.{edition?}.{lang_tag?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}" + albums: "{album_artist} - {album} ({year?})" + # - # Plex-friendly naming (space-separated, clean format) + # Plex-friendly alternatives: # movies: '{title} ({year}) {quality}' # series: '{title} {season_episode} {episode_name?}' # songs: '{track_number}. {title}' # - # Minimal naming (basic info only) - # movies: '{title}.{year}.{quality}' - # series: '{title}.{season_episode}.{episode_name?}' - # - # Custom scene-style with specific elements - # movies: '{title}.{year}.{quality}.{hdr?}.{source}.WEB-DL.{audio_full}.{video}-{tag}' - # series: '{title}.{year?}.{season_episode}.{episode_name?}.{quality}.{hdr?}.{source}.WEB-DL.{audio_full}.{atmos?}.{video}-{tag}' - # - # Folder naming (optional). Controls the folder name for downloaded content. - # If not configured, series folders are derived from the series template (minus episode info), - # movie folders use "{title} ({year})", and song folders use "{artist} - {album} ({year})". - # Uses the same template variables as the file templates above. - # - # Scene-style folder: - # folder: '{title}.{year?}.{repack?}.{edition?}.{lang_tag?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}' - # - # Plex-friendly folder: + # Folder naming (optional). One template for all, or a per-kind map. + # Valid kinds: movies, series, songs, albums (album vars: see docs). # folder: '{title} ({year?})' - # - # Per-title-type folder templates (optional). Override folder naming separately for - # movies, series, and albums. Useful when music libraries need artist/album-style folders - # while movies/series follow a different scheme. Any kind omitted falls back to the - # default for that title type. - # - # For music: "songs" (above) names each track file, while "albums" (below) names the - # album folder the tracks are saved into. Album folder vars: {album_artist}, {album}, - # {artist}, {year}, {genre}, {label}, {release_type}, {track_total}, {disc_total}. - # # folder: # movies: '{title} ({year})' # series: '{title} ({year?})' # albums: '{album_artist} - {album} ({year?})' -# Language-based tagging for output filenames -# Automatically adds language identifiers (e.g., DANiSH, NORDiC, DKsubs) based on -# audio and subtitle track languages. Rules are evaluated in order; first match wins. -# Use {lang_tag?} in your output_template to place the tag in the filename. -# -# Conditions (all conditions in a rule must match): -# audio: - any audio track matches this language -# subs_contain: - any subtitle matches this language -# subs_contain_all: [lang, ...] - subtitles include ALL listed languages -# -# language_tags: -# rules: -# - audio: da -# tag: DANiSH -# - audio: sv -# tag: SWEDiSH -# - audio: nb -# tag: NORWEGiAN -# - audio: en -# subs_contain_all: [da, sv, nb] -# tag: NORDiC -# - audio: en -# subs_contain: da -# tag: DKsubs - -# Check for updates from GitHub repository on startup (default: true) -update_checks: true - -# How often to check for updates, in hours (default: 24) -update_check_interval: 24 - -# Title caching configuration -# Cache title metadata to reduce redundant API calls -title_cache_enabled: true # Enable/disable title caching globally (default: true) -title_cache_time: 1800 # Cache duration in seconds (default: 1800 = 30 minutes) -title_cache_max_retention: 86400 # Maximum cache retention for fallback when API fails (default: 86400 = 24 hours) - -# Filename Configuration -unicode_filenames: false # optionally replace non-ASCII characters with ASCII equivalents - -# Debug logging configuration -# Comprehensive JSON-based debug logging for troubleshooting and service development -debug: - false # Enable structured JSON debug logging (default: false) - # When enabled with --debug flag or set to true: - # - Creates JSON Lines (.jsonl) log files with complete debugging context - # - Logs: session info, CLI params, service config, CDM details, authentication, - # titles, tracks metadata, DRM operations, vault queries, errors with stack traces - # - File location: logs/unshackle_debug_{service}_{timestamp}.jsonl - # - Also creates text log: logs/unshackle_root_{timestamp}.log - -debug_keys: - false # Log decryption keys in debug logs (default: false) - # Set to true to include actual decryption keys in logs - # Useful for debugging key retrieval and decryption issues - # SECURITY NOTE: Passwords, tokens, cookies, and session tokens - # are ALWAYS redacted regardless of this setting - -debug_requests: - false # Show per-request HTTP library logs under --debug (default: false) - # When false, --debug mutes the noisy per-segment connection logs from the - # HTTP libraries (urllib3/requests/rnet, e.g. 'GET ...m4s 200'), keeping - # unshackle's own DEBUG output readable. - # Set to true to see every individual request/segment download. - # Only affects: content_key, key fields (the actual CEKs) - # Never affects: kid, keys_count, key_id (metadata is always logged) - -# Muxing configuration -muxing: - set_title: false - # merge_audio: Merge all audio tracks into each output file - # true (default): All selected audio in one MKV per quality - # false: Separate MKV per (quality, audio_codec) combination - # Example: Title.1080p.AAC.mkv, Title.1080p.EC3.mkv - merge_audio: true - # merge_video: Merge video language variants into one file - # false (default): One file per video track - # true: Group videos by (resolution, range, codec) and merge each group into one - # MKV - so only language varies within a file. Different resolutions, ranges - # (SDR/HDR10/DV/...) and codecs (H264/H265) still produce separate files. - # Example: -r HYBRID,DV,HDR10,SDR yields one file per range, not one mega-file. - # The --merge-video CLI flag overrides this. - merge_video: false - # default_language: Override which track is flagged as the default in the muxed MKV. - # audio: BCP-47 tag of the preferred default audio track (e.g. pl, en, pt-BR). - # Wins over the title's original_language. Falls back to is_original_lang - # if no matching track is present. - # video: BCP-47 tag of the preferred default video track. Falls back to the - # original-language / first-track rule if no match is found. - # subtitle: BCP-47 tag of the preferred default subtitle track. Falls back to - # the existing rule (forced sub matching the audio language) if no - # matching subtitle is present. - # default_language: - # audio: pl - # video: pl - # subtitle: pl - -# Login credentials for each Service -credentials: - # Direct credentials (no profile support) - EXAMPLE: email@example.com:password - - # Per-profile credentials with default fallback - SERVICE_NAME: - default: default@email.com:password # Used when no -p/--profile is specified - profile1: user1@email.com:password1 - profile2: user2@email.com:password2 - - # Per-profile credentials without default (requires -p/--profile) - SERVICE_NAME2: - john: john@example.com:johnspassword - jane: jane@example.com:janespassword - - # You can also use list format for passwords with special characters - SERVICE_NAME3: - default: ["user@email.com", ":PasswordWith:Colons"] - -# Override default directories used across unshackle -directories: - cache: Cache - cookies: Cookies - dcsl: DCSL # Device Certificate Status List - downloads: Downloads - logs: Logs - temp: Temp - wvds: WVDs - prds: PRDs - exports: Exports # JSON export output from --export flag - # Additional directories that can be configured: - # commands: Commands - services: - - /path/to/services - - /other/path/to/services - # vaults: Vaults - # fonts: Fonts - -# Pre-define which Widevine or PlayReady device to use for each Service +# --- DRM / CDM --- docs/DRM_CONFIG.md +# Which Widevine (.wvd) or PlayReady (.prd) device to use. 'default' is the +# global fallback. Per-service, per-profile, quality-based selection and remote +# CDMs are all documented in docs/DRM_CONFIG.md. cdm: - # Global default CDM device (fallback for all services/profiles) default: WVD_1 - # Direct service-specific CDM - DIFFERENT_EXAMPLE: PRD_1 - - # Per-profile CDM configuration - EXAMPLE: - john_sd: chromecdm_903_l3 # Profile 'john_sd' uses Chrome CDM L3 - jane_uhd: nexus_5_l1 # Profile 'jane_uhd' uses Nexus 5 L1 - default: generic_android_l3 # Default CDM for this service - - # NEW: Quality-based CDM selection - # Use different CDMs based on video resolution - # Supports operators: >=, >, <=, <, or exact match - EXAMPLE_QUALITY: - "<=1080": generic_android_l3 # Use L3 for 1080p and below - ">1080": nexus_5_l1 # Use L1 for above 1080p (1440p, 2160p) - default: generic_android_l3 # Optional: fallback if no quality match - - # You can mix profiles and quality thresholds in the same service - NETFLIX: - # Profile-based selection (existing functionality) - john: netflix_l3_profile - jane: netflix_l1_profile - # Quality-based selection (new functionality) - "<=720": netflix_mobile_l3 - "1080": netflix_standard_l3 - ">=1440": netflix_premium_l1 - # Fallback - default: netflix_standard_l3 - -# Use pywidevine Serve-compliant Remote CDMs - -# Example: Custom CDM API Configuration -# This demonstrates the highly configurable custom_api type that can adapt to any CDM API format -# - name: "chrome" -# type: "custom_api" -# host: "http://remotecdm.test/" -# timeout: 30 -# device: -# name: "ChromeCDM" -# type: "CHROME" -# system_id: 34312 -# security_level: 3 -# auth: -# type: "header" -# header_name: "x-api-key" -# key: "YOUR_API_KEY_HERE" -# custom_headers: -# User-Agent: "Unshackle/2.0.0" -# endpoints: -# get_request: -# path: "/get-challenge" -# method: "POST" -# timeout: 30 -# decrypt_response: -# path: "/get-keys" -# method: "POST" -# timeout: 30 -# request_mapping: -# get_request: -# param_names: -# scheme: "device" -# init_data: "init_data" -# static_params: -# scheme: "Widevine" -# decrypt_response: -# param_names: -# scheme: "device" -# license_request: "license_request" -# license_response: "license_response" -# static_params: -# scheme: "Widevine" -# response_mapping: -# get_request: -# fields: -# challenge: "challenge" -# session_id: "session_id" -# message: "message" -# message_type: "message_type" -# response_types: -# - condition: "message_type == 'license-request'" -# type: "license_request" -# success_conditions: -# - "message == 'success'" -# decrypt_response: -# fields: -# keys: "keys" -# message: "message" -# key_fields: -# kid: "kid" -# key: "key" -# type: "type" -# success_conditions: -# - "message == 'success'" -# caching: -# enabled: true -# use_vaults: true -# check_cached_first: true - -remote_cdm: - - name: "chrome" - device_name: chrome - device_type: CHROME - system_id: 27175 - security_level: 3 - host: https://domain.com/api - secret: secret_key - - name: "chrome-2" - device_name: chrome - device_type: CHROME - system_id: 26830 - security_level: 3 - host: https://domain-2.com/api - secret: secret_key - - - name: "decrypt_labs_chrome" - type: "decrypt_labs" # Required to identify as DecryptLabs CDM - device_name: "ChromeCDM" # Scheme identifier - must match exactly - device_type: CHROME - system_id: 4464 # Doesn't matter - security_level: 3 - host: "https://keyxtractor.decryptlabs.com" - secret: "your_decrypt_labs_api_key_here" # Replace with your API key - - name: "decrypt_labs_l1" - type: "decrypt_labs" - device_name: "L1" # Scheme identifier - must match exactly - device_type: ANDROID - system_id: 4464 - security_level: 1 - host: "https://keyxtractor.decryptlabs.com" - secret: "your_decrypt_labs_api_key_here" - - - name: "decrypt_labs_l2" - type: "decrypt_labs" - device_name: "L2" # Scheme identifier - must match exactly - device_type: ANDROID - system_id: 4464 - security_level: 2 - host: "https://keyxtractor.decryptlabs.com" - secret: "your_decrypt_labs_api_key_here" - - - name: "decrypt_labs_playready_sl2" - type: "decrypt_labs" - device_name: "SL2" # Scheme identifier - must match exactly - device_type: PLAYREADY - system_id: 0 - security_level: 2000 - host: "https://keyxtractor.decryptlabs.com" - secret: "your_decrypt_labs_api_key_here" - - - name: "decrypt_labs_playready_sl3" - type: "decrypt_labs" - device_name: "SL3" # Scheme identifier - must match exactly - device_type: PLAYREADY - system_id: 0 - security_level: 3000 - host: "https://keyxtractor.decryptlabs.com" - secret: "your_decrypt_labs_api_key_here" - - # PyPlayReady RemoteCdm - connects to an unshackle serve instance - - name: "playready_remote" - device_name: "my_prd_device" # Device name on the serve instance - device_type: PLAYREADY - system_id: 0 - security_level: 3000 # 2000 for SL2000, 3000 for SL3000 - host: "http://127.0.0.1:8786/playready" # Include /playready path - secret: "your-api-secret-key" - -# Key Vaults store your obtained Content Encryption Keys (CEKs) -# Use 'no_push: true' to prevent a vault from receiving pushed keys -# while still allowing it to provide keys when requested - -# Global network timeout (seconds) for remote vaults (API, HTTP). Defaults to 10. -# Override per-vault with a 'timeout:' key on that vault entry. -# vault_timeout: 10.0 - +# --- Key vaults --- docs/DRM_CONFIG.md +# Where obtained Content Encryption Keys (CEKs) are stored and retrieved. key_vaults: - type: SQLite name: Local path: key_store.db - # Additional vault types: - # - type: API - # name: "Remote Vault" - # uri: "https://key-vault.example.com" - # token: "secret_token" - # timeout: 5.0 # overrides the global vault_timeout for this vault - # no_push: true # This vault will only provide keys, not receive them - # - type: MySQL - # name: "MySQL Vault" - # host: "127.0.0.1" - # port: 3306 - # database: vault - # username: user - # password: pass - # no_push: false # Default behavior - vault both provides and receives keys -# Choose what software to use to download data -downloader: requests -# Options: requests -# Downloading now uses the unified in-process requests/rnet downloader; the legacy -# aria2c, curl_impersonate, and n_m3u8dl_re backends have been removed. - -# rnet TLS impersonation preset (not a downloader). Selects the browser -# fingerprint the HTTP session impersonates. -curl_impersonate: - browser: chrome120 - -# Pre-define default options and switches of the dl command -# Audio track selection preferences -audio: - # Codec priority order used as a tiebreaker when multiple audio tracks share the same - # bitrate and language. Listed codecs are ranked in the order given; codecs not in the - # list keep their bitrate-based ordering and are placed after all listed codecs. - # Atmos still trumps codec priority. Valid names: AAC, AC3, EC3, AC4, OPUS, OGG, DTS, ALAC, FLAC. - # codec_priority: [FLAC, ALAC, AC4, EC3, DTS, AC3, OPUS, AAC, OGG] - -# Global defaults for the dl command. Any dl option can be set here. -# Use the option name with dashes as underscores (--v-lang -> v_lang). -# range and list work as-is (range_/list_ also accepted). -# -# Any of these can also be overridden per service under services..dl -# (see the services section below). Precedence, highest first: -# CLI arguments > services..dl > this dl section > built-in defaults -dl: - sub_format: srt - downloads: 4 - workers: 16 - lang: - - en - - fr - v_lang: [en] # --v-lang: video language(s), if different from audio - s_lang: [en, de] # --s-lang: subtitle language(s) - range: SDR # --range: SDR, HDR10, HDR10+, DV - quality: [1080] # --quality: resolution(s), int or list - slow: false # --slow: true = random 60-120s delay between titles, or "MIN-MAX" string - # Nesting a SERVICE TAG here provides defaults for that service's OWN command - # options (e.g. a service-defined --bitrate flag) -- NOT for the dl options - # above. To override dl options per service, use services..dl instead. - EXAMPLE: - bitrate: CBR - -# Chapter Name to use when exporting a Chapter without a Name -chapter_fallback_name: "Chapter {j:02}" - -# Case-Insensitive dictionary of headers for all Services -headers: - Accept-Language: "en-US,en;q=0.8" - User-Agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36" - -# Override default filenames used across unshackle -filenames: - debug_log: "unshackle_debug_{service}_{time}.jsonl" # JSON Lines debug log file - config: "config.yaml" - root_config: "unshackle.yaml" - chapters: "Chapters_{title}_{random}.txt" - subtitle: "Subtitle_{id}_{language}.srt" - -# conversion_method: -# - auto (default): Smart routing - subby for WebVTT/SAMI, pycaption for others -# - subby: Always use subby with advanced processing -# - pycaption: Use only pycaption library (no SubtitleEdit, no subby) -# - subtitleedit: Prefer SubtitleEdit when available, fall back to pycaption -# - pysubs2: Use pysubs2 library (supports SRT/SSA/ASS/WebVTT/TTML/SAMI/MicroDVD/MPL2/TMP) -subtitle: - conversion_method: auto - # Which backend converts subtitles (data-driven registry, pin-then-fallback) - # - auto (default): best available by rank (SubtitleEdit > subby/pysubs2 > pycaption) - # - subby | pysubs2 | subtitleedit | pycaption: pin that backend first, still falls back - # Styled ASS/SSA are never auto-downconverted to SRT (kept as-is); --sub-format srt overrides. - # SubtitleEdit on Linux/macOS = install the SE5 "SeConv" (seconv) CLI on PATH or unshackle/binaries/. - - sdh_method: auto - # Method to use for SDH (hearing impaired) stripping - # - auto (default): Try subby (SRT only), then SubtitleEdit (if available), then subtitle-filter - # - subby: Use subby library (SRT only) - # - subtitleedit: Use SubtitleEdit / seconv (SE5 CLI, cross-platform), falls back to subtitle-filter - # - filter-subs: Use subtitle-filter library directly - - strip_sdh: true - # Automatically create stripped (non-SDH) versions of SDH subtitles - # Set to false to disable automatic SDH stripping entirely (default: true) - - convert_before_strip: true - # Auto-convert VTT/other formats to SRT before using subtitle-filter - # This ensures compatibility when subtitle-filter is used as fallback (default: true) - - preserve_formatting: true - # Preserve original subtitle formatting (tags, positioning, styling) - # When true, skips pycaption processing for WebVTT files to keep tags like , , positioning intact - # Combined with no sub_format setting, ensures subtitles remain in their original format (default: true) - - output_mode: mux - # Output mode for subtitles - # - mux: Embed subtitles in MKV container only (default) - # - sidecar: Save subtitles as separate files only - # - both: Embed in MKV AND save as sidecar files - - sidecar_format: srt - # Format for sidecar subtitle files - # Options: srt, vtt, ass, original (keep current format) - -# Configuration for pywidevine and pyplayready's serve functionality -# Also used for remote services (unshackle serve) -serve: - api_secret: "your-secret-key-here" - - # Compression level for API payloads (manifests, cache, cookies) - # 0=off, 1=fast, 6=balanced, 9=max compression (default: 1) - compression_level: 1 - - # Session inactivity timeout in seconds (default: 300 = 5 minutes) - # Sessions are automatically deleted after this many seconds of inactivity - # Each API request resets the timer - session_ttl: 300 - - # Maximum concurrent sessions before oldest is evicted (default: 100) - max_sessions: 100 - - # Global service allowlist (optional) - # Only these services will be exposed on the API. If omitted, all services are available. - # services: - # - SERVICE_TAG_1 - # - SERVICE_TAG_2 - - users: - secret_key_for_user: - devices: # Widevine devices (WVDs) this user can access - - generic_nexus_4464_l3 - playready_devices: # PlayReady devices (PRDs) this user can access - - playready_device_sl3000 - username: user - # Per-user service allowlist (optional) - # Restricts this user to only the listed services. If omitted, user can access - # all globally-allowed services. Effective access is the intersection of global - # and per-user allowlists. - # services: - # - SERVICE_TAG_1 - # devices: # Widevine device paths (auto-populated from directories.wvds) - # - '/path/to/device.wvd' - # playready_devices: # PlayReady device paths (auto-populated from directories.prds) - # - '/path/to/device.prd' - - # Per-request override gates (both default OFF = locked to server-side config). - # A /api/download job may ask to use a specific server-side CDM device or to - # authenticate with client-supplied credentials; both are rejected with 403 unless - # explicitly opted in here, so a default deployment can't be driven to use arbitrary - # devices or inject credentials. - # cdm_overrides: true # true = allow any device; or a list of permitted device names: - # cdm_overrides: - # - generic_nexus_4464_l3 - # allow_job_credentials: true # allow a per-request `credential` / `credentials` in a job - # # (each distinct credential gets an isolated token cache) - - # Optional: any /api/download flag can be set here as a server-side default. - # Per-request body values still win. Useful for raising concurrency without - # changing every client call. Full list of accepted keys: see docs/API.md. - # downloads: 4 # parallel tracks per download job - # workers: 16 # threads per track segment fetch - # best_available: true - # no_proxy_download: false - -# Remote Services Configuration -# Connect to a remote unshackle server (unshackle serve) to use its services -# without needing the service code locally. Use with: unshackle dl --remote -# If multiple servers are configured, specify which with: --server -remote_services: - # Server name (used with --server flag if multiple configured) - my-server: - url: "http://192.168.1.100:8786" - api_key: "your-secret-key-here" - - # Server-CDM mode: server handles all DRM licensing using its own CDM devices - # When false (default), client uses its own CDM and proxies license requests through the server - server_cdm: false - - # Per-service overrides for remote services - # Override downloader, decryption tool, or CDM settings per service on the client - services: - # Example: Override the decryption tool for specific services - # EXAMPLE_SERVICE: - # decryption: mp4decrypt # Override decryption tool (shaka, mp4decrypt) - - # Example: Multiple servers - # us-server: - # url: "https://us.example.com:8786" - # api_key: "us-api-key" - # server_cdm: true - # services: - # EXAMPLE: - # decryption: mp4decrypt - # eu-server: - # url: "https://eu.example.com:8786" - # api_key: "eu-api-key" - # server_cdm: false - -# Configuration data for each Service -services: - # Service-specific configuration goes here - # Profile-specific configurations can be nested under service names - - # You can override ANY global configuration option on a per-service basis - # This allows fine-tuned control for services with special requirements - # Supported overrides: dl, curl_impersonate, subtitle, muxing, headers, etc. - - # Example: Comprehensive service configuration showing all features - EXAMPLE: - # Standard service config - api_key: "service_api_key" - - # Service certificate for Widevine L1/L2 (base64 encoded) - # This certificate is automatically used when L1/L2 schemes are selected - # Services obtain this from their DRM provider or license server - certificate: | - CAUSwwUKvQIIAxIQ5US6QAvBDzfTtjb4tU/7QxiH8c+TBSKOAjCCAQoCggEBAObzvlu2hZRsapAPx4Aa4GUZj4/GjxgXUtBH4THSkM40x63wQeyVxlEEo - # ... (full base64 certificate here) - - # Profile-specific device configurations - profiles: - john_sd: - device: - app_name: "AIV" - device_model: "SHIELD Android TV" - jane_uhd: - device: - app_name: "AIV" - device_model: "Fire TV Stick 4K" - - # Service-specific proxy mappings - # Override global proxy selection with specific servers for this service - # When --proxy matches a key in proxy_map, the mapped server will be used - # instead of the default/random server selection - proxy_map: - nordvpn:ca: ca1577 # Use ca1577 when --proxy nordvpn:ca is specified - nordvpn:us: us9842 # Use us9842 when --proxy nordvpn:us is specified - us: 123 # Use server 123 (from any provider) when --proxy us is specified - gb: 456 # Use server 456 (from any provider) when --proxy gb is specified - # Without this service, --proxy nordvpn:ca picks a random CA server - # With this config, --proxy nordvpn:ca EXAMPLE uses ca1577 specifically - # Other services or no service specified will still use random selection - - # NEW: Configuration overrides (can be combined with profiles and certificates) - # Override dl command defaults for this service. ANY dl option works here, - # same key naming as the global dl section (v_lang, range_, list_, ...). - # These beat the global dl section; explicit CLI arguments beat both. - dl: - downloads: 4 # Limit concurrent track downloads (global default: 6) - workers: 8 # Reduce workers per track (global default: 16) - lang: ["en", "es-419"] # Different language priority for this service - v_lang: [en] # Video language differs from audio on this service - s_lang: [all] # Grab every subtitle language here - range: HDR10 # Prefer HDR10 on this service (CLI -r still wins) - quality: 720 # This service rate-limits 1080p; int or list both work - sub_format: srt # Force SRT subtitle format - slow: true # Pace title downloads (60-120s); or "20-40" for a custom range - repack: false # Even if the global dl section sets repack: true - - # Override subtitle processing for this service - subtitle: - conversion_method: pycaption # Use specific subtitle converter - sdh_method: auto - - # Service-specific headers - headers: - User-Agent: "Service-specific user agent string" - Accept-Language: "en-US,en;q=0.9" - - # Override muxing options (always merge this service's video tracks into one file) - muxing: - set_title: true - merge_video: true - - # Remap service-provided titles before naming/output - # Keyed by the exact title the service returns -> desired output title. - title_map: - Service Title: Desired Title - - # Example: Service with different regions per profile - SERVICE_NAME: - profiles: - us_account: - region: "US" - api_endpoint: "https://api.us.service.com" - uk_account: - region: "GB" - api_endpoint: "https://api.uk.service.com" - - # Example: Rate-limited service - RATE_LIMITED_SERVICE: - dl: - downloads: 2 # Limit concurrent downloads - workers: 4 # Reduce workers to avoid rate limits - - # Notes on service-specific overrides: - # - Overrides are merged with global config, not replaced - # - Only specified keys are overridden, others use global defaults - # - Reserved keys (profiles, api_key, certificate, etc.) are NOT treated as overrides - # - Any dict-type config option can be overridden (dl, subtitle, muxing, headers, etc.) - # - Any dl option can be overridden; dashes become underscores (--v-lang -> v_lang) - # - Precedence: CLI arguments > service dl override > global dl section > built-in defaults - -# External proxy provider services -proxy_providers: - nordvpn: - username: username_from_service_credentials - password: password_from_service_credentials - # server_map: global mapping that applies to ALL services - # Difference from service-specific proxy_map: - # - server_map: applies to ALL services when --proxy nordvpn:us is used - # - proxy_map: only applies to the specific service configured (see services: EXAMPLE: proxy_map above) - # - proxy_map takes precedence over server_map for that service - server_map: - us: 12 # force US server #12 for US proxies - ca:calgary: 2534 # force CA server #2534 for Calgary proxies - us:seattle: 7890 # force US server #7890 for Seattle proxies - surfsharkvpn: - username: your_surfshark_service_username # Service credentials from https://my.surfshark.com/vpn/manual-setup/main/openvpn - password: your_surfshark_service_password # Service credentials (not your login password) - server_map: - us: 3844 # force US server #3844 for US proxies - gb: 2697 # force GB server #2697 for GB proxies - au: 4621 # force AU server #4621 for AU proxies - us:seattle: 5678 # force US server #5678 for Seattle proxies - ca:toronto: 1234 # force CA server #1234 for Toronto proxies - windscribevpn: - username: your_windscribe_username # Service credentials from https://windscribe.com/getconfig/openvpn - password: your_windscribe_password # Service credentials (not your login password) - server_map: - us: "us-central-096.totallyacdn.com" # force US server - gb: "uk-london-055.totallyacdn.com" # force GB server - us:seattle: "us-west-011.totallyacdn.com" # force US Seattle server - ca:toronto: "ca-toronto-012.totallyacdn.com" # force CA Toronto server - - # Gluetun: Dynamic Docker-based VPN proxy (supports 50+ VPN providers) - # Creates Docker containers running Gluetun to bridge VPN connections to HTTP proxies - # Requires Docker to be installed and running - # Usage: --proxy gluetun:windscribe:us or --proxy gluetun:nordvpn:de - gluetun: - # Global settings - base_port: 8888 # Starting port for HTTP proxies (increments for each container) - auto_cleanup: true # Automatically remove containers when done - container_prefix: "unshackle-gluetun" # Docker container name prefix - verify_ip: true # Verify VPN IP matches expected region - # Optional HTTP proxy authentication (for the proxy itself, not VPN) - # auth_user: proxy_user - # auth_password: proxy_password - - # VPN provider configurations - providers: - # Windscribe (WireGuard) - Get credentials from https://windscribe.com/getconfig/wireguard - windscribe: - vpn_type: wireguard - credentials: - private_key: "YOUR_WIREGUARD_PRIVATE_KEY" - addresses: "YOUR_WIREGUARD_ADDRESS" # e.g., "10.x.x.x/32" - # Map friendly names to country codes - server_countries: - us: US - uk: GB - ca: CA - de: DE - - # NordVPN (OpenVPN) - Get service credentials from https://my.nordaccount.com/dashboard/nordvpn/manual-configuration/ - # Note: Service credentials are NOT your email+password - generate them from the link above - # nordvpn: - # vpn_type: openvpn - # credentials: - # username: "YOUR_NORDVPN_SERVICE_USERNAME" - # password: "YOUR_NORDVPN_SERVICE_PASSWORD" - # server_countries: - # us: US - # uk: GB - - # ExpressVPN (OpenVPN) - Get credentials from ExpressVPN setup page - # expressvpn: - # vpn_type: openvpn - # credentials: - # username: "YOUR_EXPRESSVPN_USERNAME" - # password: "YOUR_EXPRESSVPN_PASSWORD" - # server_countries: - # us: US - # uk: GB - - # Surfshark (WireGuard) - Get credentials from https://my.surfshark.com/vpn/manual-setup/main/wireguard - # surfshark: - # vpn_type: wireguard - # credentials: - # private_key: "YOUR_SURFSHARK_PRIVATE_KEY" - # addresses: "YOUR_SURFSHARK_ADDRESS" - # server_countries: - # us: US - # uk: GB - - # Specific server selection: Use format like "us1239" to select specific servers - # Example: --proxy gluetun:nordvpn:us1239 connects to us1239.nordvpn.com - # Supported providers: nordvpn, surfshark, expressvpn, cyberghost - - # ExpressVPN HTTPS Proxy Provider - # Uses browser extension proxy flow with OAuth token authentication. - # Requires exported browser cookies from auth.expressvpn.com (Keycloak session). - # Cookie file: cookies/vpn/expressvpn.txt (or cookies/vpns/expressvpn.txt) - # Token cache: cache/global/expressvpn_tokens.json (auto-managed) - # - # Query formats: - # --proxy expressvpn:us → random US location (smart connection) - # --proxy expressvpn:us-ny → specific city (New York) - # --proxy expressvpn:us-ny-2 → pinned server #2 in New York - # --proxy expressvpn:mx → random Mexico location - # --proxy expressvpn:es-madrid → specific city (Madrid) - # - # expressvpn: - # region_map: - # us: "ny" # Default to New York when --proxy expressvpn:us - # gb: "london" # Default to London when --proxy expressvpn:gb - # es: "madrid" # Default to Madrid when --proxy expressvpn:es - # mx: # Smart connection (random MX location) when --proxy expressvpn:mx - # de: "frankfurt" # Default to Frankfurt when --proxy expressvpn:de - - basic: - GB: - - "socks5://username:password@bhx.socks.ipvanish.com:1080" # 1 (Birmingham) - - "socks5://username:password@gla.socks.ipvanish.com:1080" # 2 (Glasgow) - AU: - - "socks5://username:password@syd.socks.ipvanish.com:1080" # 1 (Sydney) - - "https://username:password@au-syd.prod.surfshark.com" # 2 (Sydney) - - "https://username:password@au-bne.prod.surfshark.com" # 3 (Brisbane) - BG: "https://username:password@bg-sof.prod.surfshark.com" +# --- Everything else lives in docs/ --- +# credentials, directories, proxy providers, subtitles, muxing, serve/REST API, +# remote services, per-service overrides, debug logging, Firefox cookie import, +# and more. See docs/README.md for the full index.