mirror of
https://github.com/vinefeeder/TwinVine.git
synced 2026-07-15 18:10:04 +02:00
updates 5.1.0
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
# Advanced & System Configuration
|
||||
|
||||
This document covers advanced features, debugging, and system-level configuration options.
|
||||
|
||||
## serve (dict)
|
||||
|
||||
Configuration for the integrated server that provides CDM endpoints (Widevine/PlayReady) and a REST API for remote downloading.
|
||||
|
||||
Start the server with:
|
||||
|
||||
```bash
|
||||
envied serve # Default: localhost:8786
|
||||
envied serve -h 0.0.0.0 -p 8888 # Listen on all interfaces
|
||||
envied serve --no-key # Disable authentication
|
||||
envied serve --api-only # REST API only, no CDM endpoints
|
||||
envied serve --remote-only # Only expose remote service session endpoints
|
||||
```
|
||||
|
||||
### CLI Options
|
||||
|
||||
| Option | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `-h, --host` | `127.0.0.1` | Host to serve from |
|
||||
| `-p, --port` | `8786` | Port to serve from |
|
||||
| `--caddy` | `false` | Also serve with Caddy reverse-proxy for HTTPS |
|
||||
| `--api-only` | `false` | Serve only the REST API, disable CDM endpoints |
|
||||
| `--no-widevine` | `false` | Disable Widevine CDM endpoints |
|
||||
| `--no-playready` | `false` | Disable PlayReady CDM endpoints |
|
||||
| `--no-key` | `false` | Disable API key authentication (allows all requests) |
|
||||
| `--debug-api` | `false` | Include tracebacks and stderr in API error responses |
|
||||
| `--debug` | `false` | Enable debug logging for API operations |
|
||||
| `--remote-only` | `false` | Only expose remote service session endpoints (health, services, search, session) |
|
||||
|
||||
### Configuration
|
||||
|
||||
- `api_secret` - Secret key for REST API authentication. Required unless `--no-key` is used. All API requests must include this key via the `X-Secret-Key` header.
|
||||
- `compression_level` - Compression level for API payloads (manifests, cache, cookies). `0`=off, `1`=fast, `6`=balanced, `9`=max. Default: `1`.
|
||||
- `session_ttl` - Session inactivity timeout in seconds. Each request resets the timer. Default: `300`.
|
||||
- `max_sessions` - Maximum concurrent sessions before the oldest is evicted. Default: `100`.
|
||||
- `services` - Optional global service allowlist. Only these service tags are exposed. If omitted, all services are available.
|
||||
- `devices` - List of Widevine device files (.wvd). If not specified, auto-populated from the WVDs directory.
|
||||
- `playready_devices` - List of PlayReady device files (.prd). If not specified, auto-populated from the PRDs directory.
|
||||
- `users` - Dictionary mapping user secret keys to their access configuration:
|
||||
- `devices` - List of Widevine devices this user can access
|
||||
- `playready_devices` - List of PlayReady devices this user can access
|
||||
- `username` - Internal logging name for the user (not visible to users)
|
||||
- `services` - Optional per-user service allowlist. Effective access is the intersection of global and per-user allowlists.
|
||||
|
||||
#### Server-side `dl` defaults
|
||||
|
||||
Any key accepted by `/api/download` (see `docs/API.md`) can also be declared directly under `serve:` and the REST API will treat it as a default. Per-request bodies still win. Use this to raise concurrency, force `best_available`, etc. without each client repeating the values:
|
||||
|
||||
```yaml
|
||||
serve:
|
||||
api_secret: "..."
|
||||
users: { ... }
|
||||
downloads: 4 # parallel tracks per job
|
||||
workers: 16 # threads per track
|
||||
best_available: true
|
||||
no_proxy_download: false
|
||||
```
|
||||
|
||||
Layering: built-in defaults < `serve.*` overrides < service-specific defaults < request body.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
serve:
|
||||
api_secret: "your-secret-key-here"
|
||||
compression_level: 1
|
||||
session_ttl: 300
|
||||
max_sessions: 100
|
||||
# services: # global allowlist (optional)
|
||||
# - EXAMPLE1
|
||||
# - EXAMPLE2
|
||||
users:
|
||||
secret_key_for_jane: # 32bit hex recommended, case-sensitive
|
||||
devices: # list of allowed Widevine devices for this user
|
||||
- generic_nexus_4464_l3
|
||||
playready_devices: # list of allowed PlayReady devices for this user
|
||||
- my_playready_device
|
||||
username: jane # only for internal logging, users will not see this name
|
||||
# services: # per-user allowlist (optional)
|
||||
# - EXAMPLE1
|
||||
secret_key_for_james:
|
||||
devices:
|
||||
- generic_nexus_4464_l3
|
||||
username: james
|
||||
# devices can be manually specified by path if you don't want to add it to
|
||||
# envied's WVDs directory for whatever reason
|
||||
# devices:
|
||||
# - 'C:\Users\john\Devices\test_devices_001.wvd'
|
||||
# playready_devices:
|
||||
# - '/path/to/device.prd'
|
||||
```
|
||||
|
||||
### REST API
|
||||
|
||||
When the server is running, interactive API documentation is available at:
|
||||
|
||||
- **Swagger UI**: `http://localhost:8786/api/docs/`
|
||||
|
||||
See [API.md](API.md) for full REST API documentation with endpoints, parameters, and examples.
|
||||
|
||||
---
|
||||
|
||||
## max_concurrent_downloads (int)
|
||||
|
||||
Maximum number of `/api/download` jobs the serve queue manager will execute in parallel. Each job runs the full `dl` pipeline (authenticate, fetch tracks, decrypt, mux) in its own worker subprocess. This is independent of `serve.downloads`, which controls parallel tracks **inside** a single job. Default: `2`.
|
||||
|
||||
```yaml
|
||||
max_concurrent_downloads: 4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## download_job_retention_hours (int)
|
||||
|
||||
How long completed, failed, or cancelled download jobs remain queryable via `/api/download/jobs/{job_id}` before the periodic cleanup loop drops them. Default: `24`.
|
||||
|
||||
```yaml
|
||||
download_job_retention_hours: 48
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## debug (bool)
|
||||
|
||||
Enables comprehensive debug logging. Default: `false`
|
||||
|
||||
When enabled (either via config or the `-d`/`--debug` CLI flag):
|
||||
- Sets console log level to DEBUG for verbose output
|
||||
- Creates JSON Lines (`.jsonl`) debug log files with structured logging
|
||||
- Logs detailed information about sessions, service configuration, DRM operations, and errors with full stack traces
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
debug: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## debug_keys (bool)
|
||||
|
||||
Controls whether actual decryption keys (CEKs) are included in debug logs. Default: `false`
|
||||
|
||||
When enabled:
|
||||
- Content encryption keys are logged in debug output
|
||||
- Only affects `content_key` and `key` fields (the actual CEKs)
|
||||
- Key metadata (`kid`, `keys_count`, `key_id`) is always logged regardless of this setting
|
||||
- Passwords, tokens, cookies, and session tokens remain redacted even when enabled
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
debug_keys: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## set_terminal_bg (bool)
|
||||
|
||||
Controls whether envied should set the terminal background color. Default: `false`
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
set_terminal_bg: true
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## update_checks (bool)
|
||||
|
||||
Check for updates from the GitHub repository on startup. Default: `true`.
|
||||
|
||||
---
|
||||
|
||||
## update_check_interval (int)
|
||||
|
||||
How often to check for updates, in hours. Default: `24`.
|
||||
|
||||
---
|
||||
|
||||
## title_cache_enabled (bool)
|
||||
|
||||
Enable or disable title metadata caching globally. Default: `true`.
|
||||
|
||||
---
|
||||
|
||||
## title_cache_time (int)
|
||||
|
||||
Title cache duration in seconds. Default: `1800` (30 minutes).
|
||||
|
||||
---
|
||||
|
||||
## title_cache_max_retention (int)
|
||||
|
||||
Maximum cache retention in seconds, used as fallback when the upstream API fails. Default: `86400` (24 hours).
|
||||
|
||||
---
|
||||
|
||||
## unicode_filenames (bool)
|
||||
|
||||
When `false`, replaces non-ASCII characters in output filenames with ASCII equivalents. Default: `false`.
|
||||
|
||||
---
|
||||
|
||||
## ipinfo_api_key (str)
|
||||
|
||||
Optional ipinfo.io token. When set, envied uses the ipinfo.io Lite endpoint for IP/geolocation lookups instead of the unauthenticated fallback.
|
||||
|
||||
---
|
||||
|
||||
## tmdb_api_key (str)
|
||||
|
||||
Optional TMDB API key, used for metadata enrichment and IMDb/TMDb tagging.
|
||||
|
||||
---
|
||||
|
||||
## simkl_client_id (str)
|
||||
|
||||
Optional Simkl client ID for metadata lookups.
|
||||
|
||||
---
|
||||
|
||||
## decrypt_labs_api_key (str)
|
||||
|
||||
Optional Decrypt Labs API key, used by services that integrate with the service.
|
||||
|
||||
---
|
||||
+781
@@ -0,0 +1,781 @@
|
||||
# REST API Documentation
|
||||
|
||||
The envied REST API allows you to control downloads, search services, drive remote downloads from a thin client, and (optionally) co-host the pywidevine/pyplayready CDM. Start the server with `envied serve` and access the interactive Swagger UI at `http://localhost:8786/api/docs/`.
|
||||
|
||||
The server is built on **aiohttp** (not FastAPI). Implementation lives in `envied/commands/serve.py` and `envied/core/api/` (`routes.py`, `handlers.py`, `session_store.py`, `input_bridge.py`, `download_manager.py`, `download_worker.py`).
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Start the server (no authentication)
|
||||
envied serve --no-key
|
||||
|
||||
# Start with authentication (api_secret in envied.yaml)
|
||||
envied serve
|
||||
|
||||
# Serve only the REST API (no pywidevine/pyplayready CDM)
|
||||
envied serve --api-only
|
||||
|
||||
# Serve only the remote-dl session endpoints (CORS/Cloudflare friendly)
|
||||
envied serve --remote-only
|
||||
|
||||
# Disable just one CDM
|
||||
envied serve --no-widevine
|
||||
envied serve --no-playready
|
||||
|
||||
# Verbose error responses (tracebacks/stderr in JSON)
|
||||
envied serve --debug-api
|
||||
```
|
||||
|
||||
`serve` flags:
|
||||
|
||||
| Flag | Description |
|
||||
| --- | --- |
|
||||
| `-h, --host` | Bind host (default `127.0.0.1`) |
|
||||
| `-p, --port` | Bind port (default `8786`) |
|
||||
| `--caddy` | Also launch Caddy using `Caddyfile` next to the envied config |
|
||||
| `--api-only` | REST API only; skip the bundled pywidevine/pyplayready CDM endpoints |
|
||||
| `--no-widevine` | Disable Widevine CDM endpoints |
|
||||
| `--no-playready` | Disable PlayReady CDM endpoints |
|
||||
| `--no-key` | Disable API key authentication entirely |
|
||||
| `--debug-api` | Include tracebacks/stderr in error responses |
|
||||
| `--debug` | Enable DEBUG-level logging for API operations |
|
||||
| `--remote-only` | Expose only `/api/health`, `/api/services`, `/api/search`, and `/api/session/*` (implies `--api-only`) |
|
||||
|
||||
## Authentication
|
||||
|
||||
When `api_secret` is set in `envied.yaml`, all API requests require the **`X-Secret-Key`** header. There is no query-parameter fallback. `/api/health` is always reachable without authentication. `--no-key` disables auth entirely (not recommended for public-facing servers).
|
||||
|
||||
```yaml
|
||||
# envied.yaml
|
||||
serve:
|
||||
api_secret: "your-master-secret" # falls back to global users map below
|
||||
remote_only: false # also toggleable via --remote-only
|
||||
services: ["EXAMPLE1", "EXAMPLE2"] # optional global service allowlist
|
||||
users:
|
||||
user-secret-1:
|
||||
username: alice
|
||||
devices: ["my_widevine_l3"] # Widevine WVD names this user may use
|
||||
playready_devices: ["my_pr_sl2000"] # PlayReady PRD names; defaults to [] (no access)
|
||||
services: ["EXAMPLE1"] # optional per-user allowlist (intersected with global)
|
||||
user-secret-2:
|
||||
username: bob
|
||||
devices: []
|
||||
playready_devices: []
|
||||
```
|
||||
|
||||
### Service allowlists
|
||||
|
||||
`config.serve.services` is the global allowlist; `users.<key>.services` further narrows it per key. The effective set is the intersection. Endpoints affected: `/api/services`, `/api/search`, `/api/list-titles`, `/api/list-tracks`, `/api/download`, and all `/api/session/*` routes.
|
||||
|
||||
### CDM access (server-side decryption)
|
||||
|
||||
There is no separate "tier" flag. Whether the server can return KID:KEY for a session-mode download depends solely on the device lists configured for the calling user key:
|
||||
|
||||
- Empty `devices` and `playready_devices` -> server can only proxy CDM challenges; the client must run its own CDM and parse the license.
|
||||
- Populated lists -> the client may set `mode: "server_cdm"` on `/api/session/{id}/license` and receive `{ "keys": { "<track_id>": { "<KID>": "<KEY>" } } }` instead of raw license bytes.
|
||||
|
||||
Per-service CDM type can be pinned via `config.cdm` (`widevine`/`playready`) or per-service `cdm_type`; otherwise the server picks the type the user has devices for.
|
||||
|
||||
### Server-side `dl` defaults
|
||||
|
||||
Any flag accepted by `/api/download` (see the table below) can be declared under `serve:` in `envied.yaml` and the API will apply it as a default. Request-body values still win. Useful for raising concurrency without changing every client call:
|
||||
|
||||
```yaml
|
||||
serve:
|
||||
api_secret: "..."
|
||||
users: { ... }
|
||||
downloads: 4 # parallel tracks per download job
|
||||
workers: 16 # threads per track segment fetch
|
||||
best_available: true
|
||||
no_proxy_download: false
|
||||
```
|
||||
|
||||
Layering order: built-in defaults < `serve.*` overrides < service-specific click defaults < request body.
|
||||
|
||||
---
|
||||
|
||||
## Endpoint Map
|
||||
|
||||
Standard endpoints (suppressed in `--remote-only` mode are marked R):
|
||||
|
||||
| Method | Path | R |
|
||||
| --- | --- | :-: |
|
||||
| GET | `/api/health` | ok |
|
||||
| GET | `/api/services` | ok |
|
||||
| POST | `/api/search` | ok |
|
||||
| POST | `/api/list-titles` | hidden |
|
||||
| POST | `/api/list-tracks` | hidden |
|
||||
| POST | `/api/download` | hidden |
|
||||
| GET | `/api/download/jobs` | hidden |
|
||||
| GET | `/api/download/jobs/{job_id}` | hidden |
|
||||
| DELETE | `/api/download/jobs/{job_id}` | hidden |
|
||||
| POST | `/api/session/create` | ok |
|
||||
| GET | `/api/session/{session_id}` | ok |
|
||||
| DELETE | `/api/session/{session_id}` | ok |
|
||||
| GET | `/api/session/{session_id}/titles` | ok |
|
||||
| POST | `/api/session/{session_id}/tracks` | ok |
|
||||
| POST | `/api/session/{session_id}/segments` | ok |
|
||||
| POST | `/api/session/{session_id}/license` | ok |
|
||||
| GET | `/api/session/{session_id}/prompt` | ok |
|
||||
| POST | `/api/session/{session_id}/prompt` | ok |
|
||||
|
||||
CDM endpoints (`/{wvd}/...`, `/playready/{prd}/...`) are exposed unless `--api-only` / `--remote-only` / `--no-widevine` / `--no-playready` is set, and use pywidevine / pyplayready's own auth scheme.
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
### GET /api/health
|
||||
|
||||
Health check with version and update information. Always reachable without auth.
|
||||
|
||||
```bash
|
||||
curl http://localhost:8786/api/health
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"version": "4.0.0",
|
||||
"update_check": {
|
||||
"update_available": false,
|
||||
"current_version": "4.0.0",
|
||||
"latest_version": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /api/services
|
||||
|
||||
List all available streaming services (filtered by the effective allowlist for the caller).
|
||||
|
||||
```bash
|
||||
curl -H "X-Secret-Key: $KEY" http://localhost:8786/api/services
|
||||
```
|
||||
|
||||
Returns `{"services": [...]}`. Each entry has `tag`, `aliases`, `geofence`, `title_regex`, `url` (from `cli.short_help`), `help` (full docstring), and `cli_params` describing the service-level Click parameters.
|
||||
|
||||
---
|
||||
|
||||
### POST /api/search
|
||||
|
||||
Search for titles from a streaming service.
|
||||
|
||||
**Required parameters:**
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `service` | string | Service tag |
|
||||
| `query` | string | Search query |
|
||||
|
||||
**Optional parameters:**
|
||||
| Parameter | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `profile` | string | `null` | Profile for credentials/cookies |
|
||||
| `proxy` | string | `null` | Proxy URI or country code |
|
||||
| `no_proxy` | boolean | `false` | Disable all proxy use |
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8786/api/search \
|
||||
-H "X-Secret-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"service": "EXAMPLE1", "query": "example show"}'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"results": [
|
||||
{
|
||||
"id": "abc123def456",
|
||||
"title": "Example Show",
|
||||
"description": null,
|
||||
"label": "TV Show",
|
||||
"url": "https://example.com/show/abc123def456"
|
||||
}
|
||||
],
|
||||
"count": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST /api/list-titles
|
||||
|
||||
Get available titles (seasons/episodes/movies) for a service and title ID. Disabled in `--remote-only` mode.
|
||||
|
||||
**Required parameters:**
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `service` | string | Service tag |
|
||||
| `title_id` | string | Title ID or URL |
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8786/api/list-titles \
|
||||
-H "X-Secret-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"service": "EXAMPLE1", "title_id": "abc123def456"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### POST /api/list-tracks
|
||||
|
||||
Get video, audio, and subtitle tracks for a title. Disabled in `--remote-only` mode.
|
||||
|
||||
**Required parameters:**
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `service` | string | Service tag |
|
||||
| `title_id` | string | Title ID or URL |
|
||||
|
||||
**Optional parameters:**
|
||||
| Parameter | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `wanted` | array | all | Episode filter (e.g., `["S01E01"]`) |
|
||||
| `profile` | string | `null` | Profile for credentials/cookies |
|
||||
| `proxy` | string | `null` | Proxy URI or country code |
|
||||
| `no_proxy` | boolean | `false` | Disable all proxy use |
|
||||
|
||||
Returns video, audio, and subtitle tracks with codec, bitrate, resolution, language, and DRM information.
|
||||
|
||||
---
|
||||
|
||||
### POST /api/download
|
||||
|
||||
Start a download job. Returns immediately with a job ID (HTTP 202). Disabled in `--remote-only` mode.
|
||||
|
||||
**Required parameters:**
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `service` | string | Service tag |
|
||||
| `title_id` | string | Title ID or URL |
|
||||
|
||||
**Quality and codec parameters:**
|
||||
| Parameter | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `quality` | array[int] | best | Resolution(s) (e.g., `[1080, 2160]`) |
|
||||
| `vcodec` | string or array | any | Video codec(s): `H264`, `H265`/`HEVC`, `VP9`, `AV1`, `VC1`, `VP8` |
|
||||
| `acodec` | string or array | any | Audio codec(s): `AAC`, `AC3`, `EC3`, `AC4`, `OPUS`, `FLAC`, `ALAC`, `DTS`, `OGG` |
|
||||
| `vbitrate` | int | highest | Video bitrate in kbps |
|
||||
| `abitrate` | int | highest | Audio bitrate in kbps |
|
||||
| `range` | array[string] | `["SDR"]` | Color range(s): `SDR`, `HDR10`, `HDR10+`, `HLG`, `DV`, `HYBRID` |
|
||||
| `channels` | float | any | Audio channels (e.g., `5.1`, `7.1`) |
|
||||
| `no_atmos` | boolean | `false` | Exclude Dolby Atmos tracks |
|
||||
| `split_audio` | boolean | `null` | Create separate output per audio codec |
|
||||
| `sub_format` | string | `null` | Output subtitle format: `SRT`, `VTT`, `ASS`, `SSA`, `TTML` |
|
||||
|
||||
**Episode selection:**
|
||||
| Parameter | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `wanted` | array[string] | all | Episodes (e.g., `["S01E01", "S01E02-S01E05"]`) |
|
||||
| `latest_episode` | boolean | `false` | Download only the most recent episode |
|
||||
|
||||
**Language parameters:**
|
||||
| Parameter | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `lang` | array[string] | `["orig"]` | Language for video and audio (`orig` = original) |
|
||||
| `v_lang` | array[string] | `[]` | Language override for video tracks only |
|
||||
| `a_lang` | array[string] | `[]` | Language override for audio tracks only |
|
||||
| `s_lang` | array[string] | `["all"]` | Language for subtitles |
|
||||
| `require_subs` | array[string] | `[]` | Required subtitle languages (skip if missing) |
|
||||
| `forced_subs` | boolean | `false` | Include forced subtitle tracks |
|
||||
| `exact_lang` | boolean | `false` | Exact language matching (no variants) |
|
||||
|
||||
**Track selection:**
|
||||
| Parameter | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `video_only` | boolean | `false` | Only download video tracks |
|
||||
| `audio_only` | boolean | `false` | Only download audio tracks |
|
||||
| `subs_only` | boolean | `false` | Only download subtitle tracks |
|
||||
| `chapters_only` | boolean | `false` | Only download chapters |
|
||||
| `no_video` | boolean | `false` | Skip video tracks |
|
||||
| `no_audio` | boolean | `false` | Skip audio tracks |
|
||||
| `no_subs` | boolean | `false` | Skip subtitle tracks |
|
||||
| `no_chapters` | boolean | `false` | Skip chapters |
|
||||
| `audio_description` | boolean | `false` | Include audio description tracks |
|
||||
|
||||
**Output and tagging:**
|
||||
| Parameter | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `tag` | string | `null` | Override group tag |
|
||||
| `repack` | boolean | `false` | Add REPACK tag to filename |
|
||||
| `tmdb_id` | int | `null` | Use specific TMDB ID for tagging |
|
||||
| `imdb_id` | string | `null` | Use specific IMDB ID (e.g., `tt1375666`) |
|
||||
| `animeapi_id` | string | `null` | Anime database ID via AnimeAPI (e.g., `mal:12345`) |
|
||||
| `enrich` | boolean | `false` | Override show title and year from external source |
|
||||
| `no_folder` | boolean | `false` | Disable folder creation for TV shows |
|
||||
| `no_source` | boolean | `false` | Remove source tag from filename |
|
||||
| `no_mux` | boolean | `false` | Do not mux tracks into container |
|
||||
| `output_dir` | string | `null` | Override output directory |
|
||||
|
||||
**Download behavior:**
|
||||
| Parameter | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `profile` | string | `null` | Profile for credentials/cookies |
|
||||
| `proxy` | string | `null` | Proxy URI or country code |
|
||||
| `no_proxy` | boolean | `false` | Disable all proxy use |
|
||||
| `no_proxy_download` | boolean | `false` | Bypass proxy for segment downloads only. Manifest, license, and auth still use proxy |
|
||||
| `workers` | int | `null` | Max threads per track download |
|
||||
| `downloads` | int | `1` | Concurrent track downloads |
|
||||
| `slow` | boolean or string | `null` | Add randomized delay between titles. `true` = 60-120s, or `"MIN-MAX"` string (e.g., `"20-40"`). Min must be >= 20 |
|
||||
| `best_available` | boolean | `false` | Continue if requested quality unavailable |
|
||||
| `worst` | boolean | `false` | Select the lowest bitrate track within the specified quality. Requires `quality` |
|
||||
| `skip_dl` | boolean | `false` | Skip download, only get decryption keys |
|
||||
| `export` | boolean | `false` | Export manifest, track URLs, keys, and subtitles to JSON in the exports directory |
|
||||
| `cdm_only` | boolean | `null` | Only use CDM (`true`) or only vaults (`false`) |
|
||||
| `no_cache` | boolean | `false` | Bypass title cache |
|
||||
| `reset_cache` | boolean | `false` | Clear title cache before fetching |
|
||||
|
||||
**Example:**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:8786/api/download \
|
||||
-H "X-Secret-Key: $KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"service": "EXAMPLE1",
|
||||
"title_id": "abc123def456",
|
||||
"wanted": ["S01E01"],
|
||||
"quality": [1080, 2160],
|
||||
"vcodec": ["H265"],
|
||||
"acodec": ["AAC", "EC3"],
|
||||
"range": ["HDR10", "SDR"],
|
||||
"split_audio": true,
|
||||
"lang": ["en"]
|
||||
}'
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"job_id": "504db959-80b0-446c-a764-7924b761d613",
|
||||
"status": "queued",
|
||||
"created_time": "2026-02-27T18:00:00.000000"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /api/download/jobs
|
||||
|
||||
List all download jobs with optional filtering and sorting. Disabled in `--remote-only` mode.
|
||||
|
||||
**Query parameters:**
|
||||
| Parameter | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `status` | string | all | Filter by status: `queued`, `downloading`, `completed`, `failed`, `cancelled` |
|
||||
| `service` | string | all | Filter by service tag |
|
||||
| `sort_by` | string | `created_time` | Sort field: `created_time`, `started_time`, `completed_time`, `progress`, `status`, `service` |
|
||||
| `sort_order` | string | `desc` | Sort order: `asc`, `desc` |
|
||||
|
||||
```bash
|
||||
curl -H "X-Secret-Key: $KEY" "http://localhost:8786/api/download/jobs?status=completed"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /api/download/jobs/{job_id}
|
||||
|
||||
Get detailed information about a specific download job including progress, parameters, and error details.
|
||||
|
||||
```json
|
||||
{
|
||||
"job_id": "504db959-80b0-446c-a764-7924b761d613",
|
||||
"status": "completed",
|
||||
"created_time": "2026-02-27T18:00:00.000000",
|
||||
"service": "EXAMPLE1",
|
||||
"title_id": "abc123def456",
|
||||
"progress": 100.0,
|
||||
"parameters": { },
|
||||
"started_time": "2026-02-27T18:00:01.000000",
|
||||
"completed_time": "2026-02-27T18:00:15.000000",
|
||||
"output_files": [],
|
||||
"error_message": null,
|
||||
"error_details": null
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### DELETE /api/download/jobs/{job_id}
|
||||
|
||||
Cancel a queued or running download job. Returns 400 if the job has already terminated.
|
||||
|
||||
---
|
||||
|
||||
## Remote Service Sessions
|
||||
|
||||
These endpoints back the `RemoteService` adapter in `envied/core/remote_service.py`. They let a thin `dl` client (or any consumer) authenticate against a service on the server, fetch titles/tracks/manifests, and either proxy CDM challenges or have the server resolve KID:KEY directly. The `dl` command's `RemoteService` adapter replaces the old `remote_dl` command. These endpoints are the only `/api/*` routes available in `--remote-only` mode (in addition to `health`, `services`, and `search`).
|
||||
|
||||
### POST /api/session/create
|
||||
|
||||
Authenticate against a service and open a session. Body fields:
|
||||
|
||||
| Field | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| `service` | string | Service tag (required) |
|
||||
| `title_id` | string | Title ID/URL (required) |
|
||||
| `credentials` | object | Auth credentials forwarded to `Service.authenticate` |
|
||||
| `cookies` | string | Cookie blob (Netscape or JSON) |
|
||||
| `proxy` | string | Proxy URI or country code |
|
||||
| `no_proxy` | bool | Force-disable proxies |
|
||||
| `profile` | string | Profile name |
|
||||
| `cache` | object | Optional pre-warmed title cache payload |
|
||||
|
||||
If the service requires interactive input during authentication, poll `GET /api/session/{id}/prompt` and submit responses via `POST /api/session/{id}/prompt` until status is `authenticated`.
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
{
|
||||
"service": "EXAMPLE1",
|
||||
"title_id": "abc123def456",
|
||||
"credentials": {"username": "alice", "password": "hunter2"},
|
||||
"cookies": "# Netscape HTTP Cookie File\n...",
|
||||
"proxy": "us",
|
||||
"no_proxy": false,
|
||||
"profile": "default",
|
||||
"cache": {}
|
||||
}
|
||||
```
|
||||
|
||||
**Response (202-style; auth runs asynchronously):**
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "f1c4a8b2-9c7e-4d2a-bf91-2d3e4f5a6b7c",
|
||||
"service": "EXAMPLE1",
|
||||
"status": "authenticating"
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/session/{session_id}
|
||||
|
||||
Returns session metadata. 404 if expired or unknown.
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "f1c4a8b2-9c7e-4d2a-bf91-2d3e4f5a6b7c",
|
||||
"service": "EXAMPLE1",
|
||||
"valid": true,
|
||||
"expires_in": 3600,
|
||||
"track_count": 0,
|
||||
"title_count": 0
|
||||
}
|
||||
```
|
||||
|
||||
### DELETE /api/session/{session_id}
|
||||
|
||||
Tears down the session, cancels any pending prompts, and returns any updated per-session cache files (base64-encoded, zlib-compressed) so the client can re-warm next time.
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"cache": {
|
||||
"tokens": "eJzLSM3JyVcozy/KSVGo5AIAGgQEvQ=="
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/session/{session_id}/titles
|
||||
|
||||
Returns the resolved titles list.
|
||||
|
||||
```json
|
||||
{
|
||||
"session_id": "f1c4a8b2-9c7e-4d2a-bf91-2d3e4f5a6b7c",
|
||||
"titles": [
|
||||
{
|
||||
"type": "episode",
|
||||
"name": "Pilot",
|
||||
"series_title": "Example Show",
|
||||
"season": 1,
|
||||
"number": 1,
|
||||
"year": 2024,
|
||||
"id": "ep-0001",
|
||||
"language": "en"
|
||||
},
|
||||
{
|
||||
"type": "movie",
|
||||
"name": "Example Movie",
|
||||
"year": 2024,
|
||||
"id": "mov-0001",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/session/{session_id}/tracks
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
{"title_id": "ep-0001"}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"title": {
|
||||
"type": "episode",
|
||||
"name": "Pilot",
|
||||
"series_title": "Example Show",
|
||||
"season": 1,
|
||||
"number": 1,
|
||||
"year": 2024,
|
||||
"id": "ep-0001",
|
||||
"language": "en"
|
||||
},
|
||||
"video": [
|
||||
{
|
||||
"id": "v-1080p-h264",
|
||||
"codec": "H264",
|
||||
"codec_display": "H.264",
|
||||
"bitrate": 6000,
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"resolution": "1920x1080",
|
||||
"fps": "23.976",
|
||||
"range": "SDR",
|
||||
"range_display": "SDR",
|
||||
"language": "en",
|
||||
"drm": [
|
||||
{
|
||||
"type": "widevine",
|
||||
"pssh": "AAAAW3Bzc2gAAAAA7e+...",
|
||||
"kids": ["abcdef0123456789abcdef0123456789"],
|
||||
"license_url": "https://license.example.com/widevine"
|
||||
}
|
||||
],
|
||||
"descriptor": "DASH",
|
||||
"url": "https://cdn.example.com/manifest.mpd"
|
||||
}
|
||||
],
|
||||
"audio": [
|
||||
{
|
||||
"id": "a-en-eac3",
|
||||
"codec": "EC3",
|
||||
"codec_display": "Dolby Digital Plus",
|
||||
"bitrate": 640,
|
||||
"channels": "5.1",
|
||||
"language": "en",
|
||||
"atmos": false,
|
||||
"descriptive": false,
|
||||
"drm": null,
|
||||
"descriptor": "DASH",
|
||||
"url": "https://cdn.example.com/manifest.mpd"
|
||||
}
|
||||
],
|
||||
"subtitles": [
|
||||
{
|
||||
"id": "s-en-vtt",
|
||||
"codec": "WebVTT",
|
||||
"language": "en",
|
||||
"forced": false,
|
||||
"sdh": false,
|
||||
"cc": false,
|
||||
"descriptor": "DASH",
|
||||
"url": "https://cdn.example.com/subs/en.vtt"
|
||||
}
|
||||
],
|
||||
"chapters": [
|
||||
{"timestamp": "00:00:00.000", "name": "Chapter 1"}
|
||||
],
|
||||
"attachments": [],
|
||||
"manifests": [
|
||||
{
|
||||
"type": "dash",
|
||||
"url": "https://cdn.example.com/manifest.mpd",
|
||||
"data": "eJzNVk1v2zAM/Ss..."
|
||||
}
|
||||
],
|
||||
"session_headers": {
|
||||
"User-Agent": "Mozilla/5.0 ..."
|
||||
},
|
||||
"session_cookies": {
|
||||
"session": "abc123"
|
||||
},
|
||||
"server_cdm_type": "widevine"
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/session/{session_id}/segments
|
||||
|
||||
**Request:**
|
||||
|
||||
```json
|
||||
{"track_ids": ["v-1080p-h264", "a-en-eac3"]}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"tracks": {
|
||||
"v-1080p-h264": {
|
||||
"descriptor": "DASH",
|
||||
"url": "https://cdn.example.com/manifest.mpd",
|
||||
"drm": [
|
||||
{
|
||||
"type": "widevine",
|
||||
"pssh": "AAAAW3Bzc2gAAAAA7e+...",
|
||||
"kids": ["abcdef0123456789abcdef0123456789"],
|
||||
"license_url": "https://license.example.com/widevine"
|
||||
}
|
||||
],
|
||||
"headers": {"User-Agent": "Mozilla/5.0 ..."},
|
||||
"cookies": {"session": "abc123"},
|
||||
"data": {}
|
||||
},
|
||||
"a-en-eac3": {
|
||||
"descriptor": "DASH",
|
||||
"url": "https://cdn.example.com/manifest.mpd",
|
||||
"drm": null,
|
||||
"headers": {"User-Agent": "Mozilla/5.0 ..."},
|
||||
"cookies": {"session": "abc123"},
|
||||
"data": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### POST /api/session/{session_id}/license
|
||||
|
||||
Two modes, selected by the `mode` field.
|
||||
|
||||
**`mode: "proxy"` (default)** -- forward a client-built CDM challenge to the service's license endpoint.
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{
|
||||
"mode": "proxy",
|
||||
"track_id": "v-1080p-h264",
|
||||
"challenge": "CAESxQEK...",
|
||||
"drm_type": "widevine",
|
||||
"pssh": "AAAAW3Bzc2gAAAAA7e+..."
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{"license": "CAIS3wIK..."}
|
||||
```
|
||||
|
||||
**`mode: "server_cdm"`** -- the server uses its own CDM to license the track and extract keys. Single-track form takes `track_id`; batch form takes `track_ids`. Requires the calling user key to have a matching device (`devices` for Widevine, `playready_devices` for PlayReady) in `envied.yaml`.
|
||||
|
||||
Request (batch):
|
||||
|
||||
```json
|
||||
{
|
||||
"mode": "server_cdm",
|
||||
"track_ids": ["v-1080p-h264", "a-en-eac3"],
|
||||
"drm_type": "widevine"
|
||||
}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"keys": {
|
||||
"v-1080p-h264": {
|
||||
"abcdef0123456789abcdef0123456789": "00112233445566778899aabbccddeeff"
|
||||
},
|
||||
"a-en-eac3": {
|
||||
"abcdef0123456789abcdef0123456789": "00112233445566778899aabbccddeeff"
|
||||
}
|
||||
},
|
||||
"drm_type": "widevine"
|
||||
}
|
||||
```
|
||||
|
||||
### GET /api/session/{session_id}/prompt
|
||||
|
||||
Polled by the client during interactive authentication (OTP, PIN, device codes). Backed by the `InputBridge` in `envied/core/api/input_bridge.py`; `Service.request_input()` blocks server-side until the client posts a response.
|
||||
|
||||
Pending input:
|
||||
|
||||
```json
|
||||
{"status": "pending_input", "prompt": "Enter OTP code: "}
|
||||
```
|
||||
|
||||
Other states:
|
||||
|
||||
```json
|
||||
{"status": "authenticating"}
|
||||
```
|
||||
|
||||
```json
|
||||
{"status": "authenticated"}
|
||||
```
|
||||
|
||||
```json
|
||||
{"status": "failed", "error": "Invalid credentials"}
|
||||
```
|
||||
|
||||
### POST /api/session/{session_id}/prompt
|
||||
|
||||
Unblocks the server-side `request_input()` call.
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
{"response": "123456"}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{"status": "accepted"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Responses
|
||||
|
||||
All endpoints return consistent error responses:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"error_code": "INVALID_PARAMETERS",
|
||||
"message": "Invalid vcodec: XYZ. Must be one of: H264, H265, VP9, AV1, VC1, VP8",
|
||||
"timestamp": "2026-02-27T18:00:00.000000+00:00",
|
||||
"details": { }
|
||||
}
|
||||
```
|
||||
|
||||
Common error codes:
|
||||
|
||||
- `INVALID_INPUT` -- malformed request body
|
||||
- `INVALID_PARAMETERS` -- invalid parameter values
|
||||
- `MISSING_SERVICE` -- service tag not provided
|
||||
- `INVALID_SERVICE` -- service not found or not in the caller's allowlist
|
||||
- `SERVICE_ERROR` -- service initialization or runtime error
|
||||
- `AUTH_FAILED` -- authentication failure
|
||||
- `NOT_FOUND` / `TRACK_NOT_FOUND` / session not found -- job/session/track/title missing
|
||||
- `INTERNAL_ERROR` -- unexpected server error
|
||||
|
||||
When `--debug-api` is enabled, error responses include additional `debug_info` with tracebacks and stderr output.
|
||||
|
||||
Authentication errors from the auth middleware are returned as `{"status": 401, "message": "..."}` (not the standard error envelope).
|
||||
|
||||
---
|
||||
|
||||
## Download Job Lifecycle
|
||||
|
||||
```
|
||||
queued -> downloading -> completed
|
||||
\-> failed
|
||||
queued -> cancelled
|
||||
downloading -> cancelled
|
||||
```
|
||||
|
||||
Jobs are retained for 24 hours after completion (override via top-level `download_job_retention_hours` in `envied.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` (`envied/core/api/session_store.py`); idle sessions and their `InputBridge` instances are cleaned up by a background loop started/stopped with the app lifecycle.
|
||||
@@ -0,0 +1,322 @@
|
||||
# Download & Processing Configuration
|
||||
|
||||
This document covers configuration options related to downloading and processing media content.
|
||||
|
||||
## downloader
|
||||
|
||||
envied ships a single unified downloader at `envied/core/downloaders/requests.py`. The legacy
|
||||
`aria2c`, `curl_impersonate`, and `n_m3u8dl_re` backends have been removed; their config blocks no
|
||||
longer have any effect.
|
||||
|
||||
The unified downloader:
|
||||
|
||||
- Works with both a standard `requests.Session` and `RnetSession` (rnet/BoringSSL TLS impersonation,
|
||||
which replaces the previous `curl_cffi` backend). When a service exposes its own session via
|
||||
`self.session`, TLS fingerprinting is preserved on every segment.
|
||||
- Uses adaptive chunk sizing between **512 KB and 4 MB**, picked from the response `Content-Length`.
|
||||
- Spawns **up to `min(16, cpu_count + 4)` worker threads** by default for segmented downloads
|
||||
(override via `--workers` / `dl.workers`).
|
||||
- Resumes interrupted downloads via HTTP `Range` requests (a sibling `<file>.!dev` control file
|
||||
marks an in-progress download).
|
||||
- Has a single-URL fast path: if the server supports byte ranges and the file is at least 64 MB,
|
||||
the file is split into 16 MB parts and downloaded in parallel into a pre-allocated file.
|
||||
- Is selected per-track via `track.downloader`, which defaults to this unified `requests` downloader.
|
||||
|
||||
There is no `downloader:` config key to set anymore. Setting one to a legacy value will emit a
|
||||
`DeprecationWarning` and otherwise be ignored.
|
||||
|
||||
---
|
||||
|
||||
## dl (dict)
|
||||
|
||||
Pre-define default options and switches of the `dl` command.
|
||||
The values will be ignored if explicitly set in the CLI call.
|
||||
|
||||
The Key must be the same value Python click would resolve it to as an argument.
|
||||
E.g., `@click.option("-r", "--range", "range_", type=...` actually resolves as `range_` variable.
|
||||
|
||||
For example to set the default primary language to download to German,
|
||||
|
||||
```yaml
|
||||
lang: de
|
||||
```
|
||||
|
||||
You can also set multiple preferred languages using a list, e.g.,
|
||||
|
||||
```yaml
|
||||
lang:
|
||||
- en
|
||||
- fr
|
||||
```
|
||||
|
||||
to set how many tracks to download concurrently to 4 and download threads to 16,
|
||||
|
||||
```yaml
|
||||
downloads: 4
|
||||
workers: 16
|
||||
```
|
||||
|
||||
to set `--bitrate=CVBR` for a specific service,
|
||||
|
||||
```yaml
|
||||
lang: de
|
||||
EXAMPLE:
|
||||
bitrate: CVBR
|
||||
```
|
||||
|
||||
or to change the output subtitle format from the default (original format) to WebVTT,
|
||||
|
||||
```yaml
|
||||
sub_format: vtt
|
||||
```
|
||||
|
||||
### All Available `dl` Keys
|
||||
|
||||
Below is a comprehensive list of keys that can be pre-defined in the `dl` section. Each corresponds
|
||||
to a CLI option on the `dl` command. CLI arguments always take priority over config values.
|
||||
|
||||
**Quality and codec:**
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `quality` | int or list | best | Resolution(s) to download (e.g., `1080`, `[1080, 2160]`) |
|
||||
| `vcodec` | str or list | any | Video codec(s): `H264`, `H265`, `VP9`, `AV1`, `VC1` |
|
||||
| `acodec` | str or list | any | Audio codec(s): `AAC`, `AC3`, `EC3`, `AC4`, `OPUS`, `FLAC`, `ALAC`, `DTS` |
|
||||
| `vbitrate` | int | highest | Video bitrate in kbps |
|
||||
| `abitrate` | int | highest | Audio bitrate in kbps |
|
||||
| `vbitrate_range` | str | none | Video bitrate window in kbps, format `MIN-MAX` (e.g., `6000-7000`) |
|
||||
| `abitrate_range` | str | none | Audio bitrate window in kbps, format `MIN-MAX` |
|
||||
| `real_video_bitrate` | bool | `false` | Probe actual media size to compute true video bitrates, overriding the manifest's declared value (`-rvb`). See [Real bitrate probing](#real-bitrate-probing) |
|
||||
| `real_audio_bitrate` | bool | `false` | Same as above for audio tracks (`-rab`). Slower than video (more renditions) |
|
||||
| `range_` | str or list | `SDR` | Color range(s): `SDR`, `HDR10`, `HDR10+`, `HLG`, `DV`, `HYBRID` |
|
||||
| `channels` | float | any | Audio channels (e.g., `5.1`, `7.1`) |
|
||||
| `worst` | bool | `false` | Select the lowest bitrate track within the specified quality. Requires `quality` |
|
||||
| `best_available` | bool | `false` | Continue if requested quality is unavailable |
|
||||
|
||||
**Language:**
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `lang` | str or list | `orig` | Language for video and audio (`orig` = original language) |
|
||||
| `v_lang` | list | `[]` | Language override for video tracks only |
|
||||
| `a_lang` | list | `[]` | Language override for audio tracks only |
|
||||
| `s_lang` | list | `["all"]` | Language for subtitles |
|
||||
| `require_subs` | list | `[]` | Required subtitle languages (skip title if missing) |
|
||||
| `forced_subs` | bool | `false` | Include forced subtitle tracks |
|
||||
| `exact_lang` | bool | `false` | Exact language matching (no regional variants) |
|
||||
| `latest_episode` | bool | `false` | Download only the single most recent episode of a series |
|
||||
|
||||
**Track selection:**
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `video_only` | bool | `false` | Only download video tracks |
|
||||
| `audio_only` | bool | `false` | Only download audio tracks |
|
||||
| `subs_only` | bool | `false` | Only download subtitle tracks |
|
||||
| `chapters_only` | bool | `false` | Only download chapters |
|
||||
| `no_video` | bool | `false` | Skip video tracks |
|
||||
| `no_audio` | bool | `false` | Skip audio tracks |
|
||||
| `no_subs` | bool | `false` | Skip subtitle tracks |
|
||||
| `no_chapters` | bool | `false` | Skip chapters |
|
||||
| `no_atmos` | bool | `false` | Exclude Dolby Atmos audio tracks |
|
||||
| `audio_description` | bool | `false` | Include audio description tracks |
|
||||
|
||||
**Output and tagging:**
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `tag` | str | config default | Override group tag |
|
||||
| `repack` | bool | `false` | Add REPACK tag to output filename |
|
||||
| `sub_format` | str | original | Output subtitle format: `srt`, `vtt`, `ass`, `ssa`, `ttml` |
|
||||
| `no_folder` | bool | `false` | Disable folder creation for TV shows |
|
||||
| `no_source` | bool | `false` | Remove source tag from filename |
|
||||
| `no_mux` | bool | `false` | Do not mux tracks into a container file |
|
||||
| `split_audio` | bool | `false` | Create separate output files per audio codec |
|
||||
| `export` | bool | `false` | Write a JSON sidecar with manifest URLs, subtitles, per-track KID:KEY, codec/track info |
|
||||
|
||||
**Metadata enrichment:**
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `tmdb_id` | int | `null` | Use specific TMDB ID for tagging |
|
||||
| `imdb_id` | str | `null` | Use specific IMDB ID (e.g., `tt1375666`) |
|
||||
| `animeapi_id` | str | `null` | Anime database ID via AnimeAPI (e.g., `mal:12345`, `anilist:98765`) |
|
||||
| `enrich` | bool | `false` | Override show title and year from external source. Requires `tmdb_id`, `imdb_id`, or `animeapi_id` |
|
||||
|
||||
**Download behavior:**
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| `downloads` | int | `1` | Concurrent track downloads |
|
||||
| `workers` | int | `min(16, cpu_count + 4)` | Max threads per track download (segments / ranged parts) |
|
||||
| `slow` | bool or `MIN-MAX` | `false` | Randomized delay between titles. `true` uses 60-120s; pass `MIN-MAX` (e.g., `20-40`) for a custom range |
|
||||
| `no_proxy_download` | bool | `false` | Bypass proxy for segment downloads only. Manifest, license, and auth still use proxy |
|
||||
| `skip_dl` | bool | `false` | Skip download, only get decryption keys |
|
||||
| `cdm_only` | bool | `null` | Only use CDM (`true`) or only vaults (`false`) |
|
||||
|
||||
### Real bitrate probing
|
||||
|
||||
Some services declare inaccurate `bandwidth`/`BANDWIDTH` in their manifests — often
|
||||
a peak or nominal figure that is far from the real average. Because `track.bitrate`
|
||||
drives the track listing, sorting, and `--vbitrate` / `--vbitrate-range` selection,
|
||||
a wrong value picks the wrong track.
|
||||
|
||||
`-rvb` / `--real-video-bitrate` (and `-rab` / `--real-audio-bitrate` for audio)
|
||||
probe the actual media size and overwrite `track.bitrate` with the measured value
|
||||
(`bytes * 8 / duration`) before listing and selection. So `-rvb --list` shows the
|
||||
true numbers, and `-rvb --vbitrate-range 6000-7000` selects against them. Without
|
||||
the flag, behaviour is unchanged (the manifest value is used).
|
||||
|
||||
How it works:
|
||||
|
||||
- **Single-file tracks** (one whole file per rendition — e.g. DASH `SegmentBase`
|
||||
or services that collapse to a `BaseURL`) are measured **exactly**: the whole
|
||||
file size over the track duration.
|
||||
- **Multi-segment tracks** (most HLS) are a **sampled estimate** — a spread of
|
||||
segments is probed and extrapolated, typically within a few percent. Segment
|
||||
bytes include container overhead, so MPEG-TS HLS reads a few percent above the
|
||||
demuxed stream (this is the real *delivered* size).
|
||||
- Only the top renditions per quality tier are probed (video grouped by
|
||||
codec + range, audio by codec + channels + language), in parallel, then extended
|
||||
downward only as far as needed to keep ranking correct. This keeps the pass fast
|
||||
even when a service exposes dozens of renditions.
|
||||
- Tracks whose duration cannot be determined fall back to `ffprobe`; probe failures
|
||||
are non-fatal and leave the manifest bitrate in place.
|
||||
|
||||
Per-track before→after values are logged at debug level (run with `-d`); the
|
||||
corrected values always appear in the Available Tracks panel.
|
||||
|
||||
You can also set per-service `dl` overrides (see [Service Integration & Authentication Configuration](SERVICE_CONFIG.md)):
|
||||
|
||||
```yaml
|
||||
dl:
|
||||
lang: en
|
||||
downloads: 4
|
||||
workers: 16
|
||||
EXAMPLE:
|
||||
bitrate: CVBR
|
||||
EXAMPLE2:
|
||||
worst: true
|
||||
quality: 1080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## audio (dict)
|
||||
|
||||
Configuration for audio track selection.
|
||||
|
||||
- `codec_priority`
|
||||
Optional list of audio codec names defining the preferred order when multiple audio
|
||||
tracks share the same bitrate and language. Listed codecs are ranked in the order given.
|
||||
Codecs not in the list retain their bitrate-based ordering and are placed after all
|
||||
listed codecs (i.e. soft priority — nothing is dropped).
|
||||
|
||||
Atmos tracks still take precedence over codec priority, and audio description tracks
|
||||
are still moved to the end.
|
||||
|
||||
Valid codec names: `AAC`, `AC3`, `EC3`, `AC4`, `OPUS`, `OGG`, `DTS`, `ALAC`, `FLAC`.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
audio:
|
||||
codec_priority: [FLAC, ALAC, AC4, EC3, DTS, AC3, OPUS, AAC, OGG]
|
||||
```
|
||||
|
||||
Or to only prefer a subset (e.g. surround codecs first, everything else falls back to
|
||||
bitrate order):
|
||||
|
||||
```yaml
|
||||
audio:
|
||||
codec_priority: [EC3, DTS, AC3, AAC]
|
||||
```
|
||||
|
||||
When unset, audio tracks are sorted by bitrate alone (with Atmos/descriptive rules still
|
||||
applied).
|
||||
|
||||
---
|
||||
|
||||
## subtitle (dict)
|
||||
|
||||
Configuration for subtitle processing and conversion.
|
||||
|
||||
- `conversion_method`
|
||||
Method to use for converting subtitles between formats. Default: `"auto"`
|
||||
- `"auto"` — Smart routing: uses 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).
|
||||
- `sdh_method`
|
||||
Method to use for SDH (hearing impaired) stripping. Default: `"auto"`
|
||||
- `"auto"` — Try subby (SRT only), then SubtitleEdit (if available), then subtitle-filter.
|
||||
- `"subby"` — Use subby library (SRT only).
|
||||
- `"subtitleedit"` — Use SubtitleEdit tool (Windows only, falls back to subtitle-filter).
|
||||
- `"filter-subs"` — Use subtitle-filter library directly.
|
||||
- `strip_sdh`
|
||||
Automatically create stripped (non-SDH) versions of SDH subtitles. Default: `true`
|
||||
- `convert_before_strip`
|
||||
Auto-convert VTT/other formats to SRT before using subtitle-filter for SDH stripping.
|
||||
Ensures compatibility when subtitle-filter is used as fallback. Default: `true`
|
||||
- `preserve_formatting`
|
||||
Preserve original subtitle formatting (tags, positioning, styling).
|
||||
When `true`, skips pycaption processing for WebVTT files to keep tags like `<i>`, `<b>`,
|
||||
positioning intact. Combined with no `sub_format` setting, ensures subtitles remain in
|
||||
their original format. Default: `true`
|
||||
- `output_mode`
|
||||
Output mode for subtitles. Default: `"mux"`
|
||||
- `"mux"` — Embed subtitles in MKV container only.
|
||||
- `"sidecar"` — Save subtitles as separate files only.
|
||||
- `"both"` — Embed in MKV and save as sidecar files.
|
||||
- `sidecar_format`
|
||||
Format for sidecar subtitle files when `output_mode` is `"sidecar"` or `"both"`. Default: `"srt"`
|
||||
Options: `srt`, `vtt`, `ass`, `original` (keep current format).
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
subtitle:
|
||||
conversion_method: auto
|
||||
sdh_method: auto
|
||||
strip_sdh: true
|
||||
convert_before_strip: true
|
||||
preserve_formatting: true
|
||||
output_mode: mux
|
||||
sidecar_format: srt
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## decryption (str | dict)
|
||||
|
||||
Choose what software to use to decrypt DRM-protected content throughout envied where needed.
|
||||
You may provide a single decryption method globally or a mapping of service tags to
|
||||
decryption methods.
|
||||
|
||||
Options:
|
||||
|
||||
- `shaka` (default) - Shaka Packager - <https://github.com/shaka-project/shaka-packager>
|
||||
- `mp4decrypt` - mp4decrypt from Bento4 - <https://github.com/axiomatic-systems/Bento4>
|
||||
|
||||
Note that Shaka Packager is the traditional method and works with most services. mp4decrypt
|
||||
is an alternative that may work better with certain services that have specific encryption formats.
|
||||
|
||||
Example mapping:
|
||||
|
||||
```yaml
|
||||
decryption:
|
||||
EXAMPLE: mp4decrypt
|
||||
EXAMPLE2: shaka
|
||||
default: shaka
|
||||
```
|
||||
|
||||
The `default` entry is optional. If omitted, `shaka` will be used for services not listed.
|
||||
|
||||
Simple configuration (single method for all services):
|
||||
|
||||
```yaml
|
||||
decryption: mp4decrypt
|
||||
```
|
||||
|
||||
---
|
||||
@@ -0,0 +1,481 @@
|
||||
# DRM & CDM Configuration
|
||||
|
||||
This document covers Digital Rights Management (DRM) and Content Decryption Module (CDM) configuration options.
|
||||
|
||||
## cdm (dict)
|
||||
|
||||
Pre-define which Widevine or PlayReady device to use for each Service by Service Tag as Key (case-sensitive).
|
||||
The value should be a WVD or PRD filename without the file extension. When
|
||||
loading the device, envied will look in both the `WVDs` and `PRDs` directories
|
||||
for a matching file.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
EXAMPLE: chromecdm_903_l3
|
||||
EXAMPLE2: nexus_6_l1
|
||||
```
|
||||
|
||||
You may also specify this device based on the profile used.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
EXAMPLE: chromecdm_903_l3
|
||||
EXAMPLE2: nexus_6_l1
|
||||
EXAMPLE3:
|
||||
john_sd: chromecdm_903_l3
|
||||
jane_uhd: nexus_5_l1
|
||||
```
|
||||
|
||||
You can also specify a fallback value to predefine if a match was not made.
|
||||
This can be done using `default` key. This can help reduce redundancy in your specifications.
|
||||
|
||||
For example, the following has the same result as the previous example, as well as all other
|
||||
services and profiles being pre-defined to use `chromecdm_903_l3`.
|
||||
|
||||
```yaml
|
||||
EXAMPLE2: nexus_6_l1
|
||||
EXAMPLE3:
|
||||
jane_uhd: nexus_5_l1
|
||||
default: chromecdm_903_l3
|
||||
```
|
||||
|
||||
You can also select CDMs based on video resolution using comparison operators (`>=`, `>`, `<=`, `<`)
|
||||
or exact match on the resolution height.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
EXAMPLE:
|
||||
"<=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 # Fallback if no quality match
|
||||
```
|
||||
|
||||
You can mix profiles and quality thresholds in the same service:
|
||||
|
||||
```yaml
|
||||
EXAMPLE:
|
||||
john: example_l3_profile # Profile-based selection
|
||||
"<=720": example_mobile_l3 # Quality-based selection
|
||||
"1080": example_standard_l3 # Exact match for 1080p
|
||||
">=1440": example_premium_l1 # Quality-based selection
|
||||
default: example_standard_l3 # Fallback
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## remote_cdm (list\[dict])
|
||||
|
||||
Configure remote CDM (Content Decryption Module) APIs to use for decrypting DRM-protected content.
|
||||
Remote CDMs allow you to use high-security CDMs (L1/L2 for Widevine, SL2000/SL3000 for PlayReady) without
|
||||
having the physical device files locally.
|
||||
|
||||
envied supports multiple types of remote CDM providers:
|
||||
|
||||
1. **DecryptLabs CDM** - Official DecryptLabs KeyXtractor API with intelligent caching
|
||||
2. **Custom API CDM** - Highly configurable adapter for any third-party CDM API
|
||||
3. **Legacy PyWidevine Serve** - Standard pywidevine serve-compliant APIs
|
||||
|
||||
The name of each defined remote CDM can be referenced in the `cdm` configuration as if it was a local device file.
|
||||
|
||||
### DecryptLabs Remote CDM
|
||||
|
||||
DecryptLabs provides a professional CDM API service with support for multiple device types and intelligent key caching.
|
||||
|
||||
**Supported Devices:**
|
||||
- **Widevine**: `ChromeCDM` (L3), `L1` (Security Level 1), `L2` (Security Level 2)
|
||||
- **PlayReady**: `SL2` (SL2000), `SL3` (SL3000)
|
||||
|
||||
**Configuration:**
|
||||
|
||||
```yaml
|
||||
remote_cdm:
|
||||
# Widevine L1 Device
|
||||
- name: decrypt_labs_l1
|
||||
type: decrypt_labs # Required: identifies as DecryptLabs CDM
|
||||
device_name: L1 # Required: must match exactly (L1, L2, ChromeCDM, SL2, SL3)
|
||||
host: https://keyxtractor.decryptlabs.com
|
||||
secret: YOUR_API_KEY # Your DecryptLabs API key
|
||||
|
||||
# Widevine L2 Device
|
||||
- name: decrypt_labs_l2
|
||||
type: decrypt_labs
|
||||
device_name: L2
|
||||
host: https://keyxtractor.decryptlabs.com
|
||||
secret: YOUR_API_KEY
|
||||
|
||||
# Chrome CDM (L3)
|
||||
- name: decrypt_labs_chrome
|
||||
type: decrypt_labs
|
||||
device_name: ChromeCDM
|
||||
host: https://keyxtractor.decryptlabs.com
|
||||
secret: YOUR_API_KEY
|
||||
|
||||
# PlayReady SL2000
|
||||
- name: decrypt_labs_playready_sl2
|
||||
type: decrypt_labs
|
||||
device_name: SL2
|
||||
device_type: PLAYREADY # Required for PlayReady
|
||||
host: https://keyxtractor.decryptlabs.com
|
||||
secret: YOUR_API_KEY
|
||||
|
||||
# PlayReady SL3000
|
||||
- name: decrypt_labs_playready_sl3
|
||||
type: decrypt_labs
|
||||
device_name: SL3
|
||||
device_type: PLAYREADY
|
||||
host: https://keyxtractor.decryptlabs.com
|
||||
secret: YOUR_API_KEY
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Intelligent key caching system (reduces API calls)
|
||||
- Automatic integration with envied's vault system
|
||||
- Support for both Widevine and PlayReady
|
||||
- Multiple security levels (L1, L2, L3, SL2000, SL3000)
|
||||
|
||||
**Note:** The `device_type` field determines whether the CDM operates in PlayReady or Widevine mode.
|
||||
Setting `device_type: PLAYREADY` (or using `device_name: SL2` / `SL3`) activates PlayReady mode.
|
||||
The `security_level` field is auto-computed from `device_name` when not specified (e.g., SL2 defaults
|
||||
to 2000, SL3 to 3000, and Widevine devices default to 3). You can override these if needed.
|
||||
|
||||
### Custom API Remote CDM
|
||||
|
||||
A highly configurable CDM adapter that can work with virtually any third-party CDM API through YAML configuration.
|
||||
This allows you to integrate custom CDM services without writing code.
|
||||
|
||||
**Basic Example:**
|
||||
|
||||
```yaml
|
||||
remote_cdm:
|
||||
- name: custom_chrome_cdm
|
||||
type: custom_api # Required: identifies as Custom API CDM
|
||||
host: https://your-cdm-api.com
|
||||
timeout: 30 # Optional: request timeout in seconds
|
||||
|
||||
device:
|
||||
name: ChromeCDM
|
||||
type: CHROME # CHROME, ANDROID, PLAYREADY
|
||||
system_id: 27175
|
||||
security_level: 3
|
||||
|
||||
auth:
|
||||
type: bearer # bearer, header, basic, body
|
||||
key: YOUR_API_TOKEN
|
||||
|
||||
endpoints:
|
||||
get_request:
|
||||
path: /get-challenge
|
||||
method: POST
|
||||
decrypt_response:
|
||||
path: /get-keys
|
||||
method: POST
|
||||
|
||||
caching:
|
||||
enabled: true # Enable key caching
|
||||
use_vaults: true # Integrate with vault system
|
||||
```
|
||||
|
||||
**Advanced Example with Field Mapping:**
|
||||
|
||||
```yaml
|
||||
remote_cdm:
|
||||
- name: advanced_custom_api
|
||||
type: custom_api
|
||||
host: https://api.example.com
|
||||
device:
|
||||
name: L1
|
||||
type: ANDROID
|
||||
security_level: 1
|
||||
|
||||
# Authentication configuration
|
||||
auth:
|
||||
type: header
|
||||
header_name: X-API-Key
|
||||
key: YOUR_SECRET_KEY
|
||||
custom_headers:
|
||||
User-Agent: envied/3.1.0
|
||||
X-Client-Version: "1.0"
|
||||
|
||||
# Endpoint configuration
|
||||
endpoints:
|
||||
get_request:
|
||||
path: /v2/challenge
|
||||
method: POST
|
||||
timeout: 30
|
||||
decrypt_response:
|
||||
path: /v2/decrypt
|
||||
method: POST
|
||||
timeout: 30
|
||||
|
||||
# Request parameter mapping
|
||||
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
|
||||
decrypt_response:
|
||||
param_names:
|
||||
license_request: challenge
|
||||
license_response: license
|
||||
|
||||
# Response field mapping
|
||||
response_mapping:
|
||||
get_request:
|
||||
fields:
|
||||
challenge: data.challenge # Deep field access
|
||||
session_id: session.id
|
||||
success_conditions:
|
||||
- status == 'ok' # Validate response
|
||||
decrypt_response:
|
||||
fields:
|
||||
keys: data.keys
|
||||
key_fields:
|
||||
kid: key_id # Map 'kid' field
|
||||
key: content_key # Map 'key' field
|
||||
|
||||
caching:
|
||||
enabled: true
|
||||
use_vaults: true
|
||||
check_cached_first: true # Check cache before API calls
|
||||
```
|
||||
|
||||
**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
|
||||
|
||||
### Legacy PyWidevine Serve Format
|
||||
|
||||
Standard [pywidevine] serve-compliant remote CDM configuration (backwards compatibility).
|
||||
|
||||
```yaml
|
||||
remote_cdm:
|
||||
- name: legacy_chrome_cdm
|
||||
device_name: chrome
|
||||
device_type: CHROME
|
||||
system_id: 27175
|
||||
security_level: 3
|
||||
host: https://domain.com/api
|
||||
secret: secret_key
|
||||
```
|
||||
|
||||
**Note:** If the `type` field is not specified, the entry is treated as a legacy pywidevine serve CDM.
|
||||
|
||||
[pywidevine]: https://github.com/rlaphoenix/pywidevine
|
||||
|
||||
---
|
||||
|
||||
## decrypt_labs_api_key (str)
|
||||
|
||||
API key for DecryptLabs CDM service integration.
|
||||
|
||||
When set, enables the use of DecryptLabs remote CDM services in your `remote_cdm` configuration.
|
||||
This is used specifically for `type: "decrypt_labs"` entries in the remote CDM list.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
decrypt_labs_api_key: "your_api_key_here"
|
||||
```
|
||||
|
||||
**Note**: This is different from the per-CDM `secret` field in `remote_cdm` entries. This provides a global
|
||||
API key that can be referenced across multiple DecryptLabs CDM configurations. If a `remote_cdm` entry with
|
||||
`type: "decrypt_labs"` does not have a `secret` field specified, the global `decrypt_labs_api_key` will be
|
||||
used as a fallback.
|
||||
|
||||
---
|
||||
|
||||
## decryption (str|dict)
|
||||
|
||||
Configure which decryption tool to use for DRM-protected content. Default: `shaka`.
|
||||
|
||||
Supported values:
|
||||
- `shaka` - Shaka Packager (default)
|
||||
- `mp4decrypt` - Bento4 mp4decrypt
|
||||
|
||||
You can specify a single decrypter for all services:
|
||||
|
||||
```yaml
|
||||
decryption: shaka
|
||||
```
|
||||
|
||||
Or configure per-service with a `DEFAULT` fallback:
|
||||
|
||||
```yaml
|
||||
decryption:
|
||||
DEFAULT: shaka
|
||||
EXAMPLE: mp4decrypt
|
||||
EXAMPLE2: shaka
|
||||
```
|
||||
|
||||
Service keys are case-insensitive (normalized to uppercase internally).
|
||||
|
||||
---
|
||||
|
||||
## MonaLisa DRM
|
||||
|
||||
MonaLisa is a WASM-based DRM system that uses local key extraction and two-stage segment decryption.
|
||||
Unlike Widevine and PlayReady, MonaLisa does not use a challenge/response flow with a license server.
|
||||
Instead, the PSSH value (ticket) is provided directly by the service API, and keys are extracted
|
||||
locally via a WASM module.
|
||||
|
||||
### Requirements
|
||||
|
||||
- **ML-Worker binary**: Must be available on your system `PATH` (discovered via `binaries.ML_Worker`).
|
||||
This is the binary that performs stage-1 decryption.
|
||||
|
||||
### Decryption stages
|
||||
|
||||
1. **ML-Worker binary**: Removes MonaLisa encryption layer (bbts -> ents). The key is passed via command-line argument.
|
||||
2. **AES-ECB decryption**: Final decryption with service-provided key.
|
||||
|
||||
MonaLisa uses per-segment decryption during download (not post-download like Widevine/PlayReady),
|
||||
so segments are decrypted as they are downloaded.
|
||||
|
||||
**Note:** MonaLisa is configured per-service rather than through global config options. Services
|
||||
that use MonaLisa handle ticket/key retrieval and CDM initialization internally.
|
||||
|
||||
---
|
||||
|
||||
## key_vaults (list\[dict])
|
||||
|
||||
Key Vaults store your obtained Content Encryption Keys (CEKs) and Key IDs per-service.
|
||||
|
||||
This can help reduce unnecessary License calls even during the first download. This is because a Service may
|
||||
provide the same Key ID and CEK for both Video and Audio, as well as for multiple resolutions or bitrates.
|
||||
|
||||
You can have as many Key Vaults as you would like. It's nice to share Key Vaults or use a unified Vault on
|
||||
Teams as sharing CEKs immediately can help reduce License calls drastically.
|
||||
|
||||
Four types of Vaults are in the Core codebase: API, SQLite, MySQL, and HTTP. API and HTTP make HTTP requests to a RESTful API,
|
||||
whereas SQLite and MySQL directly connect to an SQLite or MySQL Database.
|
||||
|
||||
Note: SQLite and MySQL vaults have to connect directly to the Host/IP. It cannot be in front of a PHP API or such.
|
||||
Beware that some Hosting Providers do not let you access the MySQL server outside their intranet and may not be
|
||||
accessible outside their hosting platform.
|
||||
|
||||
Additional behavior:
|
||||
|
||||
- `no_push` (bool): Optional per-vault flag. When `true`, the vault will not receive pushed keys (writes) but
|
||||
will still be queried and can provide keys for lookups. Useful for read-only/backup vaults.
|
||||
|
||||
### Using an API Vault
|
||||
|
||||
API vaults use a specific HTTP request format, therefore API or HTTP Key Vault APIs from other projects or services may
|
||||
not work in envied. The API format can be seen in the [API Vault Code](envied/vaults/API.py).
|
||||
|
||||
```yaml
|
||||
- type: API
|
||||
name: "John#0001's Vault" # arbitrary vault name
|
||||
uri: "https://key-vault.example.com" # api base uri (can also be an IP or IP:Port)
|
||||
# uri: "127.0.0.1:80/key-vault"
|
||||
# uri: "https://api.example.com/key-vault"
|
||||
token: "random secret key" # authorization token
|
||||
# no_push: true # optional; make this API vault read-only (lookups only)
|
||||
```
|
||||
|
||||
### Using a MySQL Vault
|
||||
|
||||
MySQL vaults can be either MySQL or MariaDB servers. I recommend MariaDB.
|
||||
A MySQL Vault can be on a local or remote network, but I recommend SQLite for local Vaults.
|
||||
|
||||
```yaml
|
||||
- type: MySQL
|
||||
name: "John#0001's Vault" # arbitrary vault name
|
||||
host: "127.0.0.1" # host/ip
|
||||
# port: 3306 # port (defaults to 3306)
|
||||
database: vault # database used for envied
|
||||
username: jane11
|
||||
password: Doe123
|
||||
# no_push: false # optional; defaults to false
|
||||
```
|
||||
|
||||
I recommend giving only a trustable user (or yourself) CREATE permission and then use envied to cache at least one CEK
|
||||
per Service to have it create the tables. If you don't give any user permissions to create tables, you will need to
|
||||
make tables yourself.
|
||||
|
||||
- Use a password on all user accounts.
|
||||
- Never use the root account with envied (even if it's you).
|
||||
- Do not give multiple users the same username and/or password.
|
||||
- Only give users access to the database used for envied.
|
||||
- You may give trusted users CREATE permission so envied can create tables if needed.
|
||||
- Other uses should only be given SELECT and INSERT permissions.
|
||||
|
||||
### Using an SQLite Vault
|
||||
|
||||
SQLite Vaults are usually only used for locally stored vaults. This vault may be stored on a mounted Cloud storage
|
||||
drive, but I recommend using SQLite exclusively as an offline-only vault. Effectively this is your backup vault in
|
||||
case something happens to your MySQL Vault.
|
||||
|
||||
```yaml
|
||||
- type: SQLite
|
||||
name: "My Local Vault" # arbitrary vault name
|
||||
path: "C:/Users/Jane11/Documents/envied/data/key_vault.db"
|
||||
# no_push: true # optional; commonly true for local backup vaults
|
||||
```
|
||||
|
||||
**Note**: You do not need to create the file at the specified path.
|
||||
SQLite will create a new SQLite database at that path if one does not exist.
|
||||
Try not to accidentally move the `db` file once created without reflecting the change in the config, or you will end
|
||||
up with multiple databases.
|
||||
|
||||
If you work on a Team I recommend every team member having their own SQLite Vault even if you all use a MySQL vault
|
||||
together.
|
||||
|
||||
### Using an HTTP Vault
|
||||
|
||||
HTTP Vaults provide flexible HTTP-based key storage with support for multiple API modes. This vault type
|
||||
is useful for integrating with various third-party key vault APIs.
|
||||
|
||||
```yaml
|
||||
- type: HTTP
|
||||
name: "My HTTP Vault"
|
||||
host: "https://vault-api.example.com"
|
||||
api_key: "your_api_key" # or use 'password' field
|
||||
api_mode: "json" # query, json, or decrypt_labs
|
||||
# username: "user" # required for query mode only
|
||||
# no_push: false # optional; defaults to false
|
||||
```
|
||||
|
||||
**Supported API Modes:**
|
||||
|
||||
- `query` - Uses GET requests with query parameters. Requires `username` field.
|
||||
- `json` - Uses POST requests with JSON payloads. Token-based authentication.
|
||||
- `decrypt_labs` - DecryptLabs API format. Read-only mode (`no_push` is forced to `true`).
|
||||
|
||||
**Example configurations:**
|
||||
|
||||
```yaml
|
||||
# Query mode (requires username)
|
||||
- type: HTTP
|
||||
name: "Query Vault"
|
||||
host: "https://api.example.com/keys"
|
||||
username: "myuser"
|
||||
password: "mypassword"
|
||||
api_mode: "query"
|
||||
|
||||
# JSON mode
|
||||
- type: HTTP
|
||||
name: "JSON Vault"
|
||||
host: "https://api.example.com/vault"
|
||||
api_key: "secret_token"
|
||||
api_mode: "json"
|
||||
|
||||
# DecryptLabs mode (read-only)
|
||||
- type: HTTP
|
||||
name: "DecryptLabs Cache"
|
||||
host: "https://keyxtractor.decryptlabs.com/cache"
|
||||
api_key: "your_decrypt_labs_api_key"
|
||||
api_mode: "decrypt_labs"
|
||||
```
|
||||
|
||||
**Note**: The `decrypt_labs` mode is always read-only and cannot receive pushed keys.
|
||||
|
||||
---
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
# Gluetun VPN Proxy
|
||||
|
||||
Gluetun provides Docker-managed VPN proxies supporting 50+ VPN providers.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
**Docker must be installed and running.**
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
sudo usermod -aG docker $USER # Then log out/in
|
||||
|
||||
# Windows/Mac
|
||||
# Install Docker Desktop: https://www.docker.com/products/docker-desktop/
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Configuration
|
||||
|
||||
Add to `~/.config/envied/envied.yaml`:
|
||||
|
||||
```yaml
|
||||
proxy_providers:
|
||||
gluetun:
|
||||
providers:
|
||||
windscribe:
|
||||
vpn_type: openvpn
|
||||
credentials:
|
||||
username: "YOUR_OPENVPN_USERNAME"
|
||||
password: "YOUR_OPENVPN_PASSWORD"
|
||||
```
|
||||
|
||||
### 2. Usage
|
||||
|
||||
Use 2-letter country codes directly:
|
||||
|
||||
```bash
|
||||
envied dl SERVICE CONTENT --proxy gluetun:windscribe:us
|
||||
envied dl SERVICE CONTENT --proxy gluetun:windscribe:uk
|
||||
```
|
||||
|
||||
Format: `gluetun:provider:region`
|
||||
|
||||
## Provider Credential Requirements
|
||||
|
||||
**OpenVPN (Recommended)**: Most providers support OpenVPN with just `username` and `password` - the simplest setup.
|
||||
|
||||
**WireGuard**: Requires private keys and varies by provider. See the [Gluetun Wiki](https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers) for provider-specific requirements. Note that `vpn_type` defaults to `wireguard` if not specified.
|
||||
|
||||
## Getting Your Credentials
|
||||
|
||||
### Windscribe (OpenVPN)
|
||||
|
||||
1. Go to [windscribe.com/getconfig/openvpn](https://windscribe.com/getconfig/openvpn)
|
||||
2. Log in with your Windscribe account
|
||||
3. Select any location and click "Get Config"
|
||||
4. Copy the username and password shown
|
||||
|
||||
### NordVPN (OpenVPN)
|
||||
|
||||
1. Go to [NordVPN Service Credentials](https://my.nordaccount.com/dashboard/nordvpn/manual-configuration/service-credentials/)
|
||||
2. Log in with your NordVPN account
|
||||
3. Generate or view your service credentials
|
||||
4. Copy the username and password
|
||||
|
||||
> **Note**: Use service credentials, NOT your account email/password.
|
||||
|
||||
### WireGuard Credentials (Advanced)
|
||||
|
||||
WireGuard requires private keys instead of username/password. See the [Gluetun Wiki](https://github.com/qdm12/gluetun-wiki/tree/main/setup/providers) for provider-specific WireGuard setup.
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
**OpenVPN (Recommended)**
|
||||
|
||||
Most providers support OpenVPN with just username and password:
|
||||
|
||||
```yaml
|
||||
providers:
|
||||
windscribe:
|
||||
vpn_type: openvpn
|
||||
credentials:
|
||||
username: YOUR_OPENVPN_USERNAME
|
||||
password: YOUR_OPENVPN_PASSWORD
|
||||
|
||||
nordvpn:
|
||||
vpn_type: openvpn
|
||||
credentials:
|
||||
username: YOUR_SERVICE_USERNAME
|
||||
password: YOUR_SERVICE_PASSWORD
|
||||
```
|
||||
|
||||
**WireGuard (Advanced)**
|
||||
|
||||
WireGuard can be faster but requires more complex credential setup:
|
||||
|
||||
```yaml
|
||||
# NordVPN/ProtonVPN (only private_key needed)
|
||||
providers:
|
||||
nordvpn:
|
||||
vpn_type: wireguard
|
||||
credentials:
|
||||
private_key: YOUR_PRIVATE_KEY
|
||||
|
||||
# Surfshark/Mullvad/IVPN (private_key AND addresses required)
|
||||
surfshark:
|
||||
vpn_type: wireguard
|
||||
credentials:
|
||||
private_key: YOUR_PRIVATE_KEY
|
||||
addresses: 10.x.x.x/32
|
||||
|
||||
# Windscribe (all three credentials required)
|
||||
windscribe:
|
||||
vpn_type: wireguard
|
||||
credentials:
|
||||
private_key: YOUR_PRIVATE_KEY
|
||||
addresses: 10.x.x.x/32
|
||||
preshared_key: YOUR_PRESHARED_KEY
|
||||
```
|
||||
|
||||
## Server Selection
|
||||
|
||||
Most providers use `SERVER_COUNTRIES`, but some use `SERVER_REGIONS`:
|
||||
|
||||
| Variable | Providers |
|
||||
|----------|-----------|
|
||||
| `SERVER_COUNTRIES` | NordVPN, ProtonVPN, Surfshark, Mullvad, ExpressVPN, and most others |
|
||||
| `SERVER_REGIONS` | Windscribe, VyprVPN, VPN Secure |
|
||||
|
||||
envied handles this automatically - just use 2-letter country codes.
|
||||
|
||||
### Per-Provider Server Mapping
|
||||
|
||||
You can explicitly map region codes to country names, cities, or hostnames per provider:
|
||||
|
||||
```yaml
|
||||
providers:
|
||||
nordvpn:
|
||||
vpn_type: openvpn
|
||||
credentials:
|
||||
username: YOUR_USERNAME
|
||||
password: YOUR_PASSWORD
|
||||
server_countries:
|
||||
us: "United States"
|
||||
uk: "United Kingdom"
|
||||
server_cities:
|
||||
us: "New York"
|
||||
server_hostnames:
|
||||
us: "us1239.nordvpn.com"
|
||||
```
|
||||
|
||||
### Specific Server Selection
|
||||
|
||||
Use a `<country><number>` region (e.g. `us1239`) to target a specific server. envied builds the
|
||||
hostname automatically per provider:
|
||||
|
||||
| Provider | Hostname format |
|
||||
|----------|-----------------|
|
||||
| NordVPN | `us1239.nordvpn.com` |
|
||||
| Surfshark | `us-1239.prod.surfshark.com` |
|
||||
| ExpressVPN | `us-1239.expressvpn.com` |
|
||||
| CyberGhost | `us-s1239.cg-dialup.net` |
|
||||
| Other | `us1239` (passed as-is to `SERVER_HOSTNAMES`) |
|
||||
|
||||
### Extra Environment Variables
|
||||
|
||||
You can pass additional Gluetun environment variables per provider using `extra_env`:
|
||||
|
||||
```yaml
|
||||
providers:
|
||||
nordvpn:
|
||||
vpn_type: openvpn
|
||||
credentials:
|
||||
username: YOUR_USERNAME
|
||||
password: YOUR_PASSWORD
|
||||
extra_env:
|
||||
LOG_LEVEL: debug
|
||||
```
|
||||
|
||||
## Global Settings
|
||||
|
||||
```yaml
|
||||
proxy_providers:
|
||||
gluetun:
|
||||
providers: {...}
|
||||
base_port: 8888 # Starting port (default: 8888)
|
||||
auto_cleanup: true # Remove containers on exit (default: true)
|
||||
verify_ip: true # Verify IP matches region (default: true)
|
||||
container_prefix: "envied-gluetun" # Docker container name prefix (default: "envied-gluetun")
|
||||
auth_user: username # Proxy auth (optional)
|
||||
auth_password: password # Proxy auth (optional)
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Container Reuse**: First request takes 10-30s; subsequent requests are instant. Containers created by other envied processes are auto-detected via `docker inspect` and reused.
|
||||
- **Ready Detection**: Waits up to 60s for both the HTTP proxy to listen (`[http proxy] listening`) and the VPN tunnel to come up (`initialization sequence completed` or `public ip address is`) before returning the proxy URI. Bails early on `fatal` or `invalid credentials` log lines.
|
||||
- **IP Verification**: When `verify_ip: true` (default), looks up the exit IP via `ipinfo.io` through the proxy and compares country code to the requested region. Retries 3 times with exponential backoff (1s, 2s, 4s).
|
||||
- **Concurrent Sessions**: Multiple downloads share the same container; ports are allocated thread-safely starting at `base_port`.
|
||||
- **Specific Servers**: Use `--proxy gluetun:nordvpn:us1239` for specific server selection (see table above).
|
||||
- **Automatic Image Pull**: The Gluetun Docker image (`qmcgaw/gluetun:latest`) is pulled automatically on first use (5 min timeout).
|
||||
- **Secure Credentials**: Credentials are passed via temporary env files (mode 0600), then zero-overwritten and unlinked after `docker run`. They never appear in process listings.
|
||||
- **Auto Cleanup**: Containers are removed via `atexit` (Ctrl+C still works normally). Disable with `auto_cleanup: false` to leave them stopped instead.
|
||||
|
||||
## Container Management
|
||||
|
||||
```bash
|
||||
# View containers
|
||||
docker ps | grep envied-gluetun
|
||||
|
||||
# Check logs
|
||||
docker logs envied-gluetun-nordvpn-us
|
||||
|
||||
# Remove all containers
|
||||
docker ps -a | grep envied-gluetun | awk '{print $1}' | xargs docker rm -f
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Docker Permission Denied (Linux)
|
||||
```bash
|
||||
sudo usermod -aG docker $USER
|
||||
# Then log out and log back in
|
||||
```
|
||||
|
||||
### VPN Connection Failed
|
||||
Check container logs for specific errors:
|
||||
```bash
|
||||
docker logs envied-gluetun-nordvpn-us
|
||||
```
|
||||
|
||||
Common issues:
|
||||
- Invalid/missing credentials
|
||||
- Windscribe WireGuard requires `preshared_key` (can be empty string, but must be set in credentials)
|
||||
- VPN provider server issues
|
||||
- Container startup timeout (default 60 seconds)
|
||||
|
||||
## Resources
|
||||
|
||||
- [Gluetun Wiki](https://github.com/qdm12/gluetun-wiki) - Official provider documentation
|
||||
- [Gluetun GitHub](https://github.com/qdm12/gluetun)
|
||||
@@ -0,0 +1,178 @@
|
||||
# Network & Proxy Configuration
|
||||
|
||||
This document covers network and proxy configuration options for bypassing geofencing and managing connections.
|
||||
|
||||
## proxy_providers (dict)
|
||||
|
||||
Enable external proxy provider services. These proxies will be used automatically where needed as defined by the
|
||||
Service's GEOFENCE class property, but can also be explicitly used with `--proxy`. You can specify which provider
|
||||
to use by prefixing it with the provider key name, e.g., `--proxy basic:de` or `--proxy nordvpn:de`. Some providers
|
||||
support specific query formats for selecting a country/server.
|
||||
|
||||
### basic (dict[str, str|list])
|
||||
|
||||
Define a mapping of country to proxy to use where required.
|
||||
The keys are region Alpha 2 Country Codes. Alpha 2 Country Codes are `[a-z]{2}` codes, e.g., `us`, `gb`, and `jp`.
|
||||
Don't get this mixed up with language codes like `en` vs. `gb`, or `ja` vs. `jp`.
|
||||
|
||||
Do note that each key's value can be a list of strings, or a string. For example,
|
||||
|
||||
```yaml
|
||||
us:
|
||||
- "http://john%40email.tld:password123@proxy-us.domain.tld:8080"
|
||||
- "http://jane%40email.tld:password456@proxy-us.domain2.tld:8080"
|
||||
de: "https://127.0.0.1:8080"
|
||||
```
|
||||
|
||||
Note that if multiple proxies are defined for a region, then by default one will be randomly chosen.
|
||||
You can choose a specific one by specifying it's number, e.g., `--proxy basic:us2` will choose the
|
||||
second proxy of the US list.
|
||||
|
||||
### nordvpn (dict)
|
||||
|
||||
Set your NordVPN Service credentials with `username` and `password` keys to automate the use of NordVPN as a Proxy
|
||||
system where required.
|
||||
|
||||
You can also specify specific servers to use per-region with the `server_map` key.
|
||||
Sometimes a specific server works best for a service than others, so hard-coding one for a day or two helps.
|
||||
|
||||
You can also select servers by city using the format `--proxy nordvpn:us:seattle` or `--proxy nordvpn:ca:calgary`.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
username: zxqsR7C5CyGwmGb6KSvk8qsZ # example of the login format
|
||||
password: wXVHmht22hhRKUEQ32PQVjCZ
|
||||
server_map:
|
||||
us: 12 # force US server #12 for US proxies
|
||||
```
|
||||
|
||||
The username and password should NOT be your normal NordVPN Account Credentials.
|
||||
They should be the `Service credentials` which can be found on your Nord Account Dashboard.
|
||||
|
||||
Once set, you can also specifically opt in to use a NordVPN proxy by specifying `--proxy nordvpn:gb` or such.
|
||||
You can even set a specific server number this way, e.g., `--proxy nordvpn:gb2366`.
|
||||
|
||||
Note that `gb` is used instead of `uk` to be more consistent across regional systems.
|
||||
|
||||
### surfsharkvpn (dict)
|
||||
|
||||
Enable Surfshark VPN proxy service using Surfshark Service credentials (not your login password).
|
||||
You may pin specific server IDs per region using `server_map`.
|
||||
|
||||
You can also select servers by city using the format `--proxy surfsharkvpn:us:seattle`.
|
||||
|
||||
```yaml
|
||||
username: your_surfshark_service_username # https://my.surfshark.com/vpn/manual-setup/main/openvpn
|
||||
password: your_surfshark_service_password # service credentials, not account password
|
||||
server_map:
|
||||
us: 3844 # force US server #3844
|
||||
gb: 2697 # force GB server #2697
|
||||
au: 4621 # force AU server #4621
|
||||
```
|
||||
|
||||
### hola
|
||||
|
||||
Enable Hola VPN proxy service. Requires the `hola-proxy` binary to be installed and available in your PATH.
|
||||
No configuration is needed under `proxy_providers`. Hola is loaded automatically when the `hola-proxy` binary
|
||||
is detected.
|
||||
|
||||
Once available, use `--proxy hola:us` or similar to connect through Hola.
|
||||
|
||||
### windscribevpn (dict)
|
||||
|
||||
Enable Windscribe VPN proxy service using static OpenVPN service credentials.
|
||||
|
||||
Use the service credentials from https://windscribe.com/getconfig/openvpn (not your account login credentials).
|
||||
|
||||
```yaml
|
||||
proxy_providers:
|
||||
windscribevpn:
|
||||
username: openvpn_username # From https://windscribe.com/getconfig/openvpn
|
||||
password: openvpn_password # Service credentials, NOT your account password
|
||||
```
|
||||
|
||||
#### Server Mapping
|
||||
|
||||
You can optionally pin specific servers using `server_map`:
|
||||
|
||||
```yaml
|
||||
proxy_providers:
|
||||
windscribevpn:
|
||||
username: openvpn_username
|
||||
password: openvpn_password
|
||||
server_map:
|
||||
us: us-central-096.totallyacdn.com # Force specific US server
|
||||
gb: uk-london-001.totallyacdn.com # Force specific UK server
|
||||
```
|
||||
|
||||
Once configured, use `--proxy windscribevpn:us` or `--proxy windscribevpn:gb` etc. to connect through Windscribe.
|
||||
|
||||
You can also select specific servers by number (e.g., `--proxy windscribevpn:sg007`) or filter by city
|
||||
(e.g., `--proxy windscribevpn:ca:toronto`).
|
||||
|
||||
### gluetun (dict)
|
||||
|
||||
Docker-managed VPN proxy supporting 50+ VPN providers via Gluetun. See [GLUETUN.md](GLUETUN.md) for full
|
||||
configuration and usage details.
|
||||
|
||||
```yaml
|
||||
proxy_providers:
|
||||
gluetun:
|
||||
providers:
|
||||
windscribe:
|
||||
vpn_type: openvpn
|
||||
credentials:
|
||||
username: "YOUR_OPENVPN_USERNAME"
|
||||
password: "YOUR_OPENVPN_PASSWORD"
|
||||
```
|
||||
|
||||
Usage: `--proxy gluetun:windscribe:us`
|
||||
|
||||
---
|
||||
|
||||
## headers (dict)
|
||||
|
||||
Case-Insensitive dictionary of headers that all Services begin their Request Session state with.
|
||||
All requests will use these unless changed explicitly or implicitly via a Server response.
|
||||
These should be sane defaults and anything that would only be useful for some Services should not
|
||||
be put here.
|
||||
|
||||
Avoid headers like 'Accept-Encoding' as that would be a compatibility header that the underlying
|
||||
HTTP backend (rnet) will set for you as part of its browser impersonation profile.
|
||||
|
||||
I recommend using,
|
||||
|
||||
```yaml
|
||||
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/128.0.0.0 Safari/537.36"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTTP Session Backend
|
||||
|
||||
envied uses [`rnet`](https://github.com/0x676e67/rnet) (Rust + BoringSSL) for HTTP with TLS
|
||||
fingerprinting. `RnetSession` is a drop-in `requests.Session` replacement and is what
|
||||
`self.session` exposes to services. It supports:
|
||||
|
||||
- Browser/app impersonation via named `rnet.Impersonate` presets (Chrome, Edge, Firefox, Safari,
|
||||
OkHttp, etc.) — picks JA3, ALPN, HTTP/2 SETTINGS and header order to match the chosen client.
|
||||
- Native rnet proxy support (HTTP, HTTPS, SOCKS5) — used by all proxy providers below.
|
||||
- Cookie-jar and `requests`-style `data=` / `json=` / `headers=` kwargs for compatibility.
|
||||
|
||||
The legacy `curl_cffi` backend has been removed. The config key is still spelled
|
||||
`curl_impersonate` for backward compatibility, but its value now selects an rnet preset.
|
||||
|
||||
### curl_impersonate (dict)
|
||||
|
||||
```yaml
|
||||
curl_impersonate:
|
||||
browser: Chrome131 # exact rnet.Impersonate preset name
|
||||
```
|
||||
|
||||
`browser` must be an exact `rnet.Impersonate` preset name (e.g. `Chrome131`, `Chrome124`,
|
||||
`Edge101`, `Firefox133`, `Safari18`, `OkHttp4_12`). See the rnet README for the full list.
|
||||
Default when unset: `Chrome131`.
|
||||
|
||||
---
|
||||
@@ -0,0 +1,287 @@
|
||||
# Output & Naming Configuration
|
||||
|
||||
This document covers output file organization and naming configuration options.
|
||||
|
||||
## filenames (dict)
|
||||
|
||||
Override the default filenames used across envied.
|
||||
The filenames use various variables that are replaced during runtime.
|
||||
|
||||
The following filenames are available and may be overridden:
|
||||
|
||||
- `log` - Log filenames. Uses `{name}` and `{time}` variables.
|
||||
- `debug_log` - Debug log filenames. Uses `{service}` and `{time}` variables.
|
||||
- `config` - Service configuration filenames.
|
||||
- `root_config` - Root configuration filename.
|
||||
- `chapters` - Chapter export filenames. Uses `{title}` and `{random}` variables.
|
||||
- `subtitle` - Subtitle export filenames. Uses `{id}` and `{language}` variables.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
filenames:
|
||||
log: "envied_{name}_{time}.log"
|
||||
debug_log: "envied_debug_{service}_{time}.jsonl"
|
||||
config: "config.yaml"
|
||||
root_config: "envied.yaml"
|
||||
chapters: "Chapters_{title}_{random}.txt"
|
||||
subtitle: "Subtitle_{id}_{language}.srt"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## output_template (dict)
|
||||
|
||||
Configure custom output filename templates for movies, series, and songs.
|
||||
This is **required** in your `envied.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?}`
|
||||
|
||||
```yaml
|
||||
output_template:
|
||||
# Scene-style (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}'
|
||||
|
||||
# Plex-friendly (space-separated)
|
||||
# movies: '{title} ({year}) {quality}'
|
||||
# series: '{title} {season_episode} {episode_name?}'
|
||||
# songs: '{track_number}. {title}'
|
||||
```
|
||||
|
||||
Example outputs:
|
||||
- Scene movies: `Example.Movie.2024.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG`
|
||||
- Scene movies (REPACK): `Example.Movie.2024.REPACK.2160p.EXAMPLE.WEB-DL.DDP5.1.H.265-TAG`
|
||||
- Scene series: `Example.Show.2024.S01E01.Pilot.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG`
|
||||
- Plex movies: `Example Movie (2024) 1080p`
|
||||
|
||||
### folder (optional)
|
||||
|
||||
Controls the folder name for downloaded content. Uses the same template variables as the file templates above.
|
||||
|
||||
If not configured, the default folder naming is used:
|
||||
- Movies: `Title (Year)`
|
||||
- Series: Derived from the `series` template with episode-specific variables removed
|
||||
- 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.
|
||||
|
||||
```yaml
|
||||
output_template:
|
||||
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}'
|
||||
|
||||
# Scene-style folder (single template, applies to all kinds)
|
||||
folder: '{title}.{year?}.{repack?}.{edition?}.{lang_tag?}.{quality}.{source}.WEB-DL.{dual?}.{multi?}.{audio_full}.{atmos?}.{hdr?}.{hfr?}.{video}-{tag}'
|
||||
|
||||
# Plex-friendly folder
|
||||
# folder: '{title} ({year?})'
|
||||
|
||||
# Per-kind folder templates
|
||||
# folder:
|
||||
# movies: '{title} ({year})'
|
||||
# series: '{title} ({year?})'
|
||||
# songs: '{artist} - {album} ({year?})'
|
||||
```
|
||||
|
||||
Example outputs:
|
||||
- Scene folder: `Example.Show.2024.S01.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG/`
|
||||
- Plex folder: `Example Show (2024)/`
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## language_tags (dict)
|
||||
|
||||
Automatically adds language-based identifiers (e.g., `DANiSH`, `NORDiC`, `DKsubs`) to output filenames
|
||||
based on audio and subtitle track languages. Use `{lang_tag?}` in your `output_template` to place the tag.
|
||||
|
||||
Rules are evaluated in order; the first matching rule wins. All conditions within a single rule
|
||||
must match (AND logic). If no rules match, `{lang_tag?}` is cleanly removed from the filename.
|
||||
|
||||
### Conditions
|
||||
|
||||
| Condition | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `audio` | string | Matches if any selected audio track has this language |
|
||||
| `subs_contain` | string | Matches if any selected subtitle has this language |
|
||||
| `subs_contain_all` | list | Matches if subtitles include ALL listed languages |
|
||||
|
||||
Language matching uses fuzzy matching (e.g., `en` matches `en-US`, `en-GB`).
|
||||
|
||||
### Example: Nordic tagging
|
||||
|
||||
```yaml
|
||||
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
|
||||
|
||||
output_template:
|
||||
movies: '{title}.{year?}.{lang_tag?}.{quality}.{source}.WEB-DL.{audio_full}.{video}-{tag}'
|
||||
```
|
||||
|
||||
Example outputs:
|
||||
- Danish audio: `Example.Show.S01E01.DANiSH.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG`
|
||||
- English audio + multiple Nordic subs: `Example.Show.S01E01.NORDiC.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG`
|
||||
- English audio + Danish subs only: `Example.Show.S01E01.DKsubs.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG`
|
||||
- No matching languages: `Example.Show.S01E01.1080p.EXAMPLE.WEB-DL.DDP5.1.H.264-TAG`
|
||||
|
||||
### Example: Other regional tags
|
||||
|
||||
```yaml
|
||||
language_tags:
|
||||
rules:
|
||||
- audio: nl
|
||||
tag: DUTCH
|
||||
- audio: de
|
||||
tag: GERMAN
|
||||
- audio: fr
|
||||
subs_contain: en
|
||||
tag: ENGFR
|
||||
- audio: fr
|
||||
tag: FRENCH
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## unicode_filenames (bool)
|
||||
|
||||
Allow Unicode characters in output filenames. When `false`, Unicode characters are transliterated
|
||||
to ASCII equivalents. Default: `false`.
|
||||
|
||||
---
|
||||
|
||||
## tag (str)
|
||||
|
||||
Group or Username to postfix to the end of download filenames following a dash.
|
||||
Use `{tag}` in your output template to include it.
|
||||
For example, `tag: "J0HN"` will have `-J0HN` at the end of all download filenames.
|
||||
|
||||
---
|
||||
|
||||
## tag_group_name (bool)
|
||||
|
||||
Enable/disable tagging downloads with your group name when `tag` is set. Default: `true`.
|
||||
|
||||
---
|
||||
|
||||
## tag_imdb_tmdb (bool)
|
||||
|
||||
Enable/disable tagging downloaded files with IMDB/TMDB/TVDB identifiers (when available). Default: `true`.
|
||||
|
||||
---
|
||||
|
||||
## muxing (dict)
|
||||
|
||||
- `set_title`
|
||||
Set the container title to `Show SXXEXX Episode Name` or `Movie (Year)`. Default: `true`
|
||||
- `merge_audio`
|
||||
Merge all audio tracks into each output file. Default: `true`
|
||||
- `true`: All selected audio tracks are muxed into one MKV per quality.
|
||||
- `false`: Separate MKV per (quality, audio_codec) combination.
|
||||
For example: `Title.1080p.AAC.mkv`, `Title.1080p.EC3.mkv`.
|
||||
|
||||
Note: The `--split-audio` CLI flag overrides this setting. When `--split-audio` is passed,
|
||||
`merge_audio` is effectively set to `false` for that run.
|
||||
|
||||
- `default_language` (dict)
|
||||
Override which track is flagged as the default in the muxed MKV, regardless
|
||||
of the title's original language. Useful when you always want your player to
|
||||
open on a specific language (e.g. always default to Polish audio even on
|
||||
English originals). Only affects the MKV `--default-track` flag — track
|
||||
selection (`-l`, `--alang`, etc.) is unchanged. All keys are optional; each
|
||||
track type falls back to its previous default rule when the configured
|
||||
language isn't present in the manifest.
|
||||
|
||||
- `audio`: BCP-47 tag (e.g. `pl`, `en`, `pt-BR`). Wins over `is_original_lang`.
|
||||
The `--original-flag` continues to mark the true original-audio track.
|
||||
- `video`: BCP-47 tag. Wins over the title-language / first-track rule.
|
||||
- `subtitle`: BCP-47 tag. Wins over the "forced sub matching audio" rule.
|
||||
|
||||
Languages are matched with the same close-match logic used elsewhere
|
||||
(`pt` matches `pt-BR`, etc.). Supports per-service overrides like the rest
|
||||
of `muxing`.
|
||||
|
||||
```yaml
|
||||
muxing:
|
||||
default_language:
|
||||
audio: pl
|
||||
video: pl
|
||||
subtitle: pl
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## chapter_fallback_name (str)
|
||||
|
||||
The Chapter Name to use when exporting a Chapter without a Name.
|
||||
The default is no fallback name at all and no Chapter name will be set.
|
||||
|
||||
The fallback name can use the following variables in f-string style:
|
||||
|
||||
- `{i}`: The Chapter number starting at 1.
|
||||
E.g., `"Chapter {i}"`: "Chapter 1", "Intro", "Chapter 3".
|
||||
- `{j}`: A number starting at 1 that increments any time a Chapter has no title.
|
||||
E.g., `"Chapter {j}"`: "Chapter 1", "Intro", "Chapter 2".
|
||||
|
||||
These are formatted with f-strings, directives are supported.
|
||||
For example, `"Chapter {i:02}"` will result in `"Chapter 01"`.
|
||||
|
||||
---
|
||||
|
||||
## directories (dict)
|
||||
|
||||
Override the default directories used across envied.
|
||||
The directories are set to common values by default.
|
||||
|
||||
The following directories are available and may be overridden,
|
||||
|
||||
- `commands` - CLI Command Classes.
|
||||
- `services` - Service Classes.
|
||||
- `vaults` - Vault Classes.
|
||||
- `fonts` - Font files (ttf or otf).
|
||||
- `downloads` - Downloads.
|
||||
- `temp` - Temporary files or conversions during download.
|
||||
- `cache` - Expiring data like Authorization tokens, or other misc data.
|
||||
- `cookies` - Expiring Cookie data.
|
||||
- `logs` - Logs.
|
||||
- `exports` - JSON sidecar exports written when `--export` is used on `dl`.
|
||||
- `wvds` - Widevine Devices.
|
||||
- `prds` - PlayReady Devices.
|
||||
- `dcsl` - Device Certificate Status List.
|
||||
|
||||
Notes:
|
||||
|
||||
- `services` accepts either a single directory or a list of directories to search for service modules.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
directories:
|
||||
downloads: "D:/Downloads/envied"
|
||||
temp: "D:/Temp/envied"
|
||||
```
|
||||
|
||||
There are directories not listed that cannot be modified as they are crucial to the operation of envied.
|
||||
|
||||
---
|
||||
@@ -0,0 +1,246 @@
|
||||
# Service Integration & Authentication Configuration
|
||||
|
||||
This document covers service-specific configuration, authentication, and metadata integration options.
|
||||
|
||||
## services (dict)
|
||||
|
||||
Configuration data for each Service. The Service will have the data within this section merged into the per-service
|
||||
`config.yaml` (located in the service's directory) before being provided to the Service class.
|
||||
|
||||
Think of this config to be used for more sensitive configuration data, like user or device-specific API keys, IDs,
|
||||
device attributes, and so on. A per-service `config.yaml` file is typically shared and not meant to be modified,
|
||||
so use this for any sensitive configuration data.
|
||||
|
||||
The Key is the Service Tag, but can take any arbitrary form for its value. It's expected to begin as either a list or
|
||||
a dictionary.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
EXAMPLE:
|
||||
client:
|
||||
auth_scheme: MESSO
|
||||
# ... more sensitive data
|
||||
```
|
||||
|
||||
### Per-Service Configuration Overrides
|
||||
|
||||
You can override many global configuration options on a per-service basis by nesting them under the
|
||||
service tag in the `services` section. Supported override keys include: `dl`, `subtitle`, `muxing`,
|
||||
`headers`, `proxy_map`, `title_map`, and more.
|
||||
|
||||
Overrides are merged with global config (not replaced) -- only specified keys are overridden, others
|
||||
use global defaults. CLI arguments always take priority over service-specific config.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
services:
|
||||
RATE_LIMITED_SERVICE:
|
||||
dl:
|
||||
downloads: 2 # Limit concurrent track downloads
|
||||
workers: 4 # Reduce workers to avoid rate limits
|
||||
headers:
|
||||
User-Agent: "..." # Service-specific UA override
|
||||
```
|
||||
|
||||
Note: envied uses a single unified `requests`-based downloader. The legacy `aria2c`,
|
||||
`n_m3u8dl_re`, and `curl_impersonate` override sections have been removed.
|
||||
|
||||
### title_map (dict)
|
||||
|
||||
Rewrites service-provided titles before naming and output. Some services name a title differently
|
||||
from how you want it stored, which can break library matching (e.g. a regional variant reusing the
|
||||
international name). Keys are the exact title string the service returns; values are the desired
|
||||
output title.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
EXAMPLE:
|
||||
title_map:
|
||||
Service Title: Desired Title
|
||||
```
|
||||
|
||||
Episodes are matched on their show title, Movies and Songs on their name. The remap is applied
|
||||
after the title cache (so edits take effect without a cache reset) and before any `--enrich`
|
||||
override (so an explicit enrich still wins).
|
||||
|
||||
It applies on the local `dl` path, the `import` command, and the remote client (`dl --remote`).
|
||||
For remote services the **client's** `title_map` is applied to the titles returned by the server,
|
||||
so you can rename titles for services you don't have installed locally. The server sends raw
|
||||
titles and does not remap, leaving the final name fully under the client's control.
|
||||
|
||||
### Service Class Conventions
|
||||
|
||||
Each service directory under `envied/services/` exports a class extending
|
||||
`envied.core.service.Service`. The class name must match the directory name (the service tag).
|
||||
|
||||
Key class variables (defined on `Service` or by service-level idiom):
|
||||
|
||||
- `ALIASES: tuple[str, ...]` — alternative tags accepted on the CLI. Empty by default.
|
||||
- `GEOFENCE: tuple[str, ...]` — ISO country codes the service is available in. Empty == no geofence.
|
||||
- `TITLE_RE: str` — regex (with named groups, e.g. `(?P<id>...)`, `(?P<type>...)`) used by the
|
||||
service to parse the CLI title argument. Service-level idiom, not declared on the base class.
|
||||
- `NO_SUBTITLES: bool` — service-level idiom indicating the service has no subtitle tracks.
|
||||
|
||||
`self.*` helpers available after `super().__init__(ctx)`:
|
||||
|
||||
- `self.session` — pre-configured HTTP session (`requests.Session`, or `RnetSession` when TLS
|
||||
impersonation is active). Cookies, headers, proxies pre-applied.
|
||||
- `self.config` — merged service config (per-service `config.yaml` plus the `services.<TAG>` block
|
||||
from `envied.yaml`).
|
||||
- `self.log` — `logging.Logger` named for the service class.
|
||||
- `self.cache` — generic `Cacher` for arbitrary key/value persistence.
|
||||
- `self.title_cache` — specialized `TitleCacher` for title metadata.
|
||||
- `self.track_request` — `TrackRequest` built from CLI flags. Fields: `codecs: list[Video.Codec]`,
|
||||
`ranges: list[Video.Range]` (defaults to `[SDR]`), `best_available: bool`. Services may
|
||||
read or rewrite these (e.g. force HEVC for HDR ranges).
|
||||
- `self.credential` — set during `authenticate()`; `None` if cookies-only.
|
||||
- `self.current_region` — lowercase ISO country code from proxy/geolocation, or `None`.
|
||||
- `self.request_input(prompt: str) -> str` — interactive prompt. Falls through to `input()`
|
||||
locally; under `serve`, the attached `InputBridge` relays the prompt to the remote client.
|
||||
|
||||
Driving CLI flags (parsed into `self.track_request`):
|
||||
|
||||
- `-v` / `--vcodec` — comma-separated `Video.Codec` list (e.g. `H264,H265`).
|
||||
- `-a` / `--acodec` — comma-separated audio codec list.
|
||||
- `-r` / `--range` — comma-separated `Video.Range` list (`SDR`, `HDR10`, `HDR10+`, `DV`,
|
||||
`HYBRID`). Defaults to `[SDR]`.
|
||||
- `-q` / `--quality` — resolution list.
|
||||
- `--vbitrate-range` / `--abitrate-range` — `MIN-MAX` kbps windows.
|
||||
|
||||
---
|
||||
|
||||
## credentials (dict[str, str|list|dict])
|
||||
|
||||
Specify login credentials to use for each Service, and optionally per-profile.
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
EXAMPLE: jane@example.tld:LoremIpsum100 # directly
|
||||
EXAMPLE2: # or per-profile, optionally with a default
|
||||
default: jane@example.tld:LoremIpsum99 # <-- used by default if -p/--profile is not used
|
||||
james: james@example.tld:TheFriend97
|
||||
john: john@example.tld:LoremIpsum98
|
||||
EXAMPLE3: # the `default` key is not necessary, but no credential will be used by default
|
||||
john: john@example.tld:SecretPassword123
|
||||
```
|
||||
|
||||
The value should be in string form, i.e. `john@example.tld:password123` or `john:password123`.
|
||||
Any arbitrary values can be used on the left (username/password/phone) and right (password/secret).
|
||||
You can also specify these in list form, i.e., `["john@example.tld", ":PasswordWithAColon"]`.
|
||||
|
||||
If you specify multiple credentials with keys like the `EXAMPLE2` and `EXAMPLE3` example above, then you should
|
||||
use a `default` key or no credential will be loaded automatically unless you use `-p/--profile`. You
|
||||
do not have to use a `default` key at all.
|
||||
|
||||
Please be aware that this information is sensitive and to keep it safe. Do not share your config.
|
||||
|
||||
---
|
||||
|
||||
## tmdb_api_key (str)
|
||||
|
||||
API key for The Movie Database (TMDB). This is used for tagging downloaded files with TMDB,
|
||||
IMDB and TVDB identifiers. Leave empty to disable automatic lookups.
|
||||
|
||||
To obtain a TMDB API key:
|
||||
|
||||
1. Create an account at <https://www.themoviedb.org/>
|
||||
2. Go to <https://www.themoviedb.org/settings/api> to register for API access
|
||||
3. Fill out the API application form with your project details
|
||||
4. Once approved, you'll receive your API key
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
tmdb_api_key: cf66bf18956kca5311ada3bebb84eb9a # Not a real key
|
||||
```
|
||||
|
||||
**Note**: Keep your API key secure and do not share it publicly. This key is used by the `core/providers/tmdb.py` metadata provider to fetch metadata from TMDB for proper file tagging and ID enrichment.
|
||||
|
||||
---
|
||||
|
||||
## simkl_client_id (str)
|
||||
|
||||
Client ID for SIMKL API integration. SIMKL is used as a metadata source for improved title matching and tagging,
|
||||
especially when a TMDB API key is not configured.
|
||||
|
||||
To obtain a SIMKL Client ID:
|
||||
|
||||
1. Create an account at <https://simkl.com/>
|
||||
2. Go to <https://simkl.com/settings/developer/>
|
||||
3. Register a new application to receive your Client ID
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
simkl_client_id: "your_client_id_here"
|
||||
```
|
||||
|
||||
**Note**: While optional, having a SIMKL Client ID improves metadata lookup reliability. SIMKL serves as an alternative or fallback metadata source to TMDB. This is used by the `core/providers/simkl.py` metadata provider.
|
||||
|
||||
---
|
||||
|
||||
## ipinfo_api_key (str)
|
||||
|
||||
Optional API token for [ipinfo.io](https://ipinfo.io). When set, envied uses the free authenticated **Lite** endpoint (`https://api.ipinfo.io/lite/me`), which has substantially higher rate limits than the anonymous endpoint and returns richer fields (ASN, organization name, continent). Leave empty to use the anonymous ipinfo.io endpoint, with [ip-api.in](https://ip-api.in) as a final fallback.
|
||||
|
||||
To obtain an ipinfo.io token:
|
||||
|
||||
1. Sign up for a free account at <https://ipinfo.io/signup>
|
||||
2. Copy the token from your dashboard
|
||||
|
||||
For example,
|
||||
|
||||
```yaml
|
||||
ipinfo_api_key: "12a3b45cd678ef" # Not a real key
|
||||
```
|
||||
|
||||
**Note**: The token is only ever sent to `api.ipinfo.io` as a per-request `Authorization` header — it is never attached to your session for service requests. Used by `core/utils/ip_info.py` for region detection and proxy verification.
|
||||
|
||||
---
|
||||
|
||||
## title_cache_enabled (bool)
|
||||
|
||||
Enable/disable caching of title metadata to reduce redundant API calls. Default: `true`.
|
||||
|
||||
---
|
||||
|
||||
## title_cache_time (int)
|
||||
|
||||
Cache duration in seconds for title metadata. Default: `1800` (30 minutes).
|
||||
|
||||
---
|
||||
|
||||
## title_cache_max_retention (int)
|
||||
|
||||
Maximum retention time in seconds for serving slightly stale cached title metadata when API calls fail.
|
||||
Default: `86400` (24 hours). Effective retention is `min(title_cache_time + grace, title_cache_max_retention)`.
|
||||
|
||||
---
|
||||
|
||||
## debug (bool)
|
||||
|
||||
Enable structured JSON debug logging for troubleshooting and service development. Default: `false`.
|
||||
|
||||
When enabled (via config or the `--debug` CLI flag):
|
||||
- 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/envied_debug_{service}_{timestamp}.jsonl`
|
||||
|
||||
---
|
||||
|
||||
## debug_keys (bool)
|
||||
|
||||
Log decryption keys in debug logs. Default: `false`.
|
||||
|
||||
When `true`, actual content encryption keys (CEKs) are included in debug log output. Useful for
|
||||
debugging key retrieval and decryption issues.
|
||||
|
||||
**Security note:** Passwords, tokens, cookies, and session tokens are always redacted regardless
|
||||
of this setting. Only content keys (`content_key`, `key` fields) are affected. Key IDs (`kid`),
|
||||
key counts, and other metadata are always logged.
|
||||
|
||||
---
|
||||
@@ -0,0 +1,73 @@
|
||||
# Subtitle Processing Configuration
|
||||
|
||||
This document covers subtitle processing and formatting options under the top-level `subtitle:` key in `envied.yaml`.
|
||||
|
||||
For the canonical example, see `envied/envied-example.yaml`.
|
||||
|
||||
## subtitle (dict)
|
||||
|
||||
Control subtitle conversion, SDH (hearing-impaired) stripping, formatting preservation, and output behavior.
|
||||
|
||||
- `conversion_method`: How to convert subtitles between formats. Default: `auto`.
|
||||
- `auto`: Smart routing - subby for WebVTT/fVTT/SAMI; for SSA/ASS/MicroDVD/MPL2/TMP use SubtitleEdit when available, otherwise pysubs2; standard pycaption/SubtitleEdit pipeline for everything else.
|
||||
- `subby`: Always use subby with `CommonIssuesFixer` (falls back to standard if the source codec isn't supported by subby).
|
||||
- `subtitleedit`: Prefer SubtitleEdit when available; otherwise fall back to the standard pycaption pipeline.
|
||||
- `pycaption`: Use only the pycaption library (no SubtitleEdit, no subby). Limited to SRT, TTML, and WebVTT outputs.
|
||||
- `pysubs2`: Use pysubs2 (supports SRT, SSA, ASS, WebVTT, TTML, SAMI, MicroDVD, MPL2, TMP).
|
||||
|
||||
- `sdh_method`: How to strip SDH cues. Default: `auto`.
|
||||
- `auto`: Try subby for SRT first, then SubtitleEdit (when `conversion_method` is `auto`/`subtitleedit` and the binary is available), then subtitle-filter as the final fallback.
|
||||
- `subby`: Use subby's `SDHStripper`. **Only operates on SRT**; for other codecs the call returns without stripping.
|
||||
- `subtitleedit`: Use SubtitleEdit's `/RemoveTextForHI` when the binary is available; otherwise falls through to subtitle-filter.
|
||||
- `filter-subs`: Use the `subtitle-filter` library directly (`rm_fonts`, `rm_ast`, `rm_music`, `rm_effects`, `rm_names`, `rm_author`).
|
||||
|
||||
- `strip_sdh`: Enable/disable automatic SDH stripping for tracks flagged as SDH. Default: `true`.
|
||||
|
||||
- `convert_before_strip`: When falling through to the subtitle-filter path, auto-convert non-SRT subtitles to SRT first for better compatibility. Default: `true`. Has no effect when SubtitleEdit handles stripping directly.
|
||||
|
||||
- `preserve_formatting`: Keep original subtitle tags and positioning during WebVTT processing. When `true`, sanitized WebVTT is written back without round-tripping through pycaption, preserving tags like `<i>`, `<b>`, and `line:` positioning. Default: `true`.
|
||||
|
||||
- `output_mode`: Controls how subtitles are included in the output. Default: `mux`.
|
||||
- `mux`: Embed subtitles in the MKV container only.
|
||||
- `sidecar`: Save subtitles as separate files only (not muxed).
|
||||
- `both`: Embed in the MKV container and save as sidecar files.
|
||||
|
||||
- `sidecar_format`: Format for sidecar subtitle files (used when `output_mode` is `sidecar` or `both`). Default: `srt`.
|
||||
- `srt`: SubRip.
|
||||
- `vtt`: WebVTT.
|
||||
- `ass`: Advanced SubStation Alpha.
|
||||
- `original`: Keep the subtitle in its current format without conversion.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
subtitle:
|
||||
conversion_method: auto
|
||||
sdh_method: auto
|
||||
strip_sdh: true
|
||||
convert_before_strip: true
|
||||
preserve_formatting: true
|
||||
output_mode: mux
|
||||
sidecar_format: srt
|
||||
```
|
||||
|
||||
## WebVTT Sanitization (automatic, not configurable)
|
||||
|
||||
After download, WebVTT and segmented WebVTT (`fVTT`/`WVTT`) tracks pass through a fixed sanitization pipeline before any conversion or muxing:
|
||||
|
||||
1. **Segment merge** — segmented DASH/HLS WebVTT is stitched via `merge_segmented_webvtt` (uses pysubs2 for lenient parsing when `conversion_method` is `auto` or `pysubs2`, otherwise pycaption directly).
|
||||
2. **Negative timestamps** — `sanitize_webvtt_timestamps` rewrites `-HH:MM:SS.mmm` cues to `00:00:00.000`.
|
||||
3. **Cue identifiers** — `sanitize_webvtt_cue_identifiers` strips letter+digit IDs (e.g. `Q0`, `S12`) on their own line before a timing line, which otherwise confuse parsers like pysubs2.
|
||||
4. **Overlapping cues** — `merge_overlapping_webvtt_cues` collapses cues with start times within 50 ms and matching end times into a single multi-line cue, ordered by `line:` percentage (lower % = higher on screen = first line).
|
||||
5. **Fallback hardening** — when `preserve_formatting` is `false` and the first pycaption parse fails, `sanitize_webvtt` retries with a `WEBVTT` header guard, hour-padded timings, and another negative-timestamp pass; if that still fails, the sanitized text is written as-is.
|
||||
|
||||
`sanitize_broken_webvtt` and `space_webvtt_headers` additionally run inside `Subtitle.parse()` to drop malformed `-->` lines and reflow merged-segment headers. `merge_same_cues` and `filter_unwanted_cues` (drops ` `/whitespace-only cues) run only on the pycaption path.
|
||||
|
||||
These behaviors are intentional and have no config knobs — they apply to every WebVTT track regardless of `conversion_method`.
|
||||
|
||||
## Related
|
||||
|
||||
- Filename sanitization (e.g. parenthesis handling, unidecode bracket artifacts from PR #105) lives in `envied/core/utilities.py::sanitize_filename` and is governed by `output_template`, not the `subtitle:` config block.
|
||||
- Subtitle codec support and the conversion matrix are defined in `envied/core/tracks/subtitle.py`.
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user