mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-11-14 13:05:09 +00:00
docs(config): make docs/ canonical, slim unshackle-example.yaml to a starter
This commit is contained in:
+16
-2
@@ -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
|
||||
|
||||
+92
@@ -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 <name>` | 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": { "<KID>": "<KEY>" } }`. 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 <name>` 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.
|
||||
|
||||
+100
-37
@@ -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 <key>` (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
|
||||
|
||||
|
||||
@@ -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:<token>@<host>: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>[-<city>[-<N>]]`
|
||||
|
||||
- `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.
|
||||
|
||||
+60
-7
@@ -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:
|
||||
|
||||
@@ -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 |
|
||||
@@ -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/<SERVICE>.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/<SERVICE>.txt` or `cookies/<SERVICE>/<profile>.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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user