- **[`serve`](#serve)**: run the local CDM + REST API server.
- **[`util`](#util)**: helper media utilities.
!!! tip "Getting help on any command"
Every command accepts `-h`, `--help`, or `-?`. For example `unshackle dl --help` or `unshackle wvd new --help`. Service-specific arguments for `dl` and `search` (such as a title ID or search query) are defined by each service, so check `unshackle dl SERVICE --help` for those.
---
## Root command
```
unshackle [OPTIONS] COMMAND [ARGS]...
```
> unshackle: Modular Movie, TV, and Music Archival Software.
Every invocation prints a banner and, when `update_checks` is enabled in your config, checks for a newer release.
| Option | Description |
|---|---|
| `-v`, `--version` | Print version information and exit. |
| `-d`, `--debug` | Enable `DEBUG`-level logs and JSON debug logging. Silences noisy HTTP libraries unless `debug_requests` is set in config. |
| `-h`, `--help`, `-?` | Show help and exit. |
Commands are auto-discovered, so third-party command modules dropped into the commands directory appear automatically.
---
## `dl`
The download command. It is itself a group whose **subcommands are the installed streaming services**, so an invocation always has three layers:
```
unshackle dl [OPTIONS] SERVICE [SERVICE ARGS...]
```
-`OPTIONS`: every flag below, parsed at the `dl` level.
-`SERVICE`: a service tag (e.g. `EXAMPLE1`, `EXAMPLE2`). Tags are case-insensitive and honour each service's aliases (`example+`, `EXAMPLE2`, etc. all resolve to the same tag).
unshackle dl -w S01-S03 -v H.265 -a EC3 SERVICE TITLE-ID
```
!!! note "Config-driven defaults"
Any `dl` flag can be given a default under the `dl:` section of `unshackle.yaml`, and per-service under `services.<TAG>.dl`. Explicit command-line values (and environment values) always win over both; service defaults only fill in options you did not set. See the [configuration file](../getting-started/configuration-file.md) guide.
| `-ad`, `--audio-description` | Include descriptive (audio-description) tracks. |
| `--skip-subtitle-errors` | Skip a failed subtitle instead of aborting the title. Video/audio failures remain fatal. |
### Output, muxing & files
| Flag | Description |
|---|---|
| `--split-audio` | Write a separate output file per audio codec instead of merging. Defaults to config `muxing.merge_audio`. |
| `--merge-video` | Mux all selected video tracks into one file. Defaults to config `muxing.merge_video`. |
| `-o`, `--output` | Override the output directory for this run. |
| `--no-folder` | Disable folder creation for TV shows. |
| `--no-source` | Remove the source tag from the filename/path. |
| `--no-mux` | Do not mux; keep individual track files. |
| `--tag` | Group tag override. |
| `--repack` | Add a `REPACK` tag to the filename. |
### Metadata & tagging
| Flag | Description |
|---|---|
| `--tmdb` | TMDB ID (integer). |
| `--imdb` | IMDb ID, e.g. `tt1375666`. |
| `--animeapi` | AnimeAPI ID, e.g. `mal:12345` or `anilist:98765` (defaults to MAL). Back-fills TMDB/IMDb. |
| `--enrich` | Override show title/year from the external source. **Requires** one of `--tmdb`, `--imdb`, or `--animeapi`. |
### DRM, keys & decryption
| Flag | Description |
|---|---|
| `--cdm-only` / `--vaults-only` | Use only the CDM, or only Key Vaults, for key acquisition. |
| `--skip-dl` | Skip the download but still retrieve keys. |
| `--export` | Export track info and keys to a JSON file in the exports directory. |
### Network & proxy
| Flag | Description |
|---|---|
| `--proxy` | Proxy URI, a 2-letter country code resolved from configured providers, or `provider:region` (e.g. `nordvpn:ca`, `gluetun:us`, `protonvpn:de:berlin`). |
| `--no-proxy` | Force-disable all proxy use. |
| `--no-proxy-download` | Bypass the proxy for **segment downloads only** (manifest, licence, and auth stay proxied). |
| `--remote` | Use a remote unshackle server. |
| `--server` | Name a remote server from the `remote_services` config. |
`--require-subs` and `--s-lang`; `--select-titles` and `--wanted`; `--worst` requires `--quality`; `--vbitrate` and `--vbitrate-range` (and the audio equivalents) are mutually exclusive.
---
## `search`
Search a service for titles. Like `dl`, `search` is a group whose subcommands are the installed services, and it reuses `dl`'s authentication, cookie, and proxy machinery.
```
unshackle search [OPTIONS] SERVICE [QUERY...]
```
The query syntax is defined per service. Results are printed as a tree of titles with their service IDs. Feed an ID straight into `dl`.
| Option | Description |
|---|---|
| `-p`, `--profile` | Profile for credentials and cookies. |
| `--proxy` | Proxy URI, 2-letter country code, or `provider:region`. |
unshackle search --proxy nordvpn:ca SERVICE "query"
```
---
## `import`
Reconstruct a download (download → decrypt → mux) from an `--export` JSON file without re-contacting the service. It re-fetches the manifest, injects the stored keys, and then runs the normal `dl` pipeline. The service tag is read from the export file, not passed by you.
```
unshackle import EXPORT_FILE [DL_ARGS...]
```
Any `dl` options placed after the file are forwarded verbatim, so you can override quality, range, proxy, and so on.
!!! example
```shell
unshackle import export.json
unshackle import export.json -r HDR10 --proxy US
```
The export file must be a valid v2 export (created by a current build of unshackle via `dl --export`) and must contain a `service` tag.
---
## `cfg`
Read, set, delete, or list configuration values in `unshackle.yaml` without hand-editing YAML.
```
unshackle cfg [KEY] [VALUE] [OPTIONS]
```
| Argument / Option | Description |
|---|---|
| `KEY` | Dotted path into the config, e.g. `tag`, `serve.api_secret`, `directories.downloads`. |
| `VALUE` | Value to set. Parsed as a Python literal when possible, so `true`, `123`, `['a','b']`, and `{'k':1}` become real types; bare words stay strings. |
| `--unset` | Remove the configuration value. |
| `--list` | List all set configuration values. |
!!! warning "Comments are stripped"
Writing a value through `cfg` rewrites the YAML file and **removes all comments** from it. If your config relies on comments, edit it by hand instead. Setting a value and using `--unset` together is an error.
!!! example
```shell
unshackle cfg --list # print entire config
unshackle cfg serve.api_secret # read a nested key
unshackle cfg directories.downloads /mnt/dl # set a nested path
unshackle cfg tag --unset # remove a key
```
---
## `env`
Inspect and manage the project environment.
```
unshackle env COMMAND [ARGS]...
```
### `env check`
```
unshackle env check
```
Prints a dependency table (Category / Tool / Status / Required / Purpose) showing which external tools resolve on your `PATH`, and a summary of how many required tools are installed.
<small>* required; all others are optional.</small>
### `env info`
```
unshackle env info
```
Shows where the config was loaded from (or lists the candidate config locations if none was found) and prints a table of every configured directory (downloads, temp, cache, cookies, logs, exports, WVDs, PRDs, services, and more).
### `env clear`
Clear an environment directory. The directory is emptied and recreated, and the number of files and bytes freed is reported.
| Command | Description |
|---|---|
| `unshackle env clear cache [SERVICE]` | Clear the cache directory, or just one service's cache subdirectory. |
Manage Key Vaults. Vaults are configured under `key_vaults` in `unshackle.yaml`, each with a `name`, a `type` (`sqlite`, `mysql`, `http`, `api`), and type-specific options. Service tags are normalised automatically.
```
unshackle kv COMMAND [ARGS]...
```
### `kv copy TO_VAULT FROM_VAULT...`
Copy keys from one or more source vaults into a destination vault. Rows with matching KIDs are skipped unless the existing row has no key; existing data is never altered or deleted.
| Option | Description |
|---|---|
| `-s`, `--service` | Only copy data for a specific service. |
| `-l`, `--local-only` | Only copy data for services installed locally. Mutually exclusive with `--service`. |
### `kv sync VAULT...`
Ensure two or more vaults hold the same set of keys, essentially a chained bidirectional copy. Requires more than one vault. Accepts the same `--service` / `--local-only` options as `copy`.
### `kv add FILE SERVICE VAULT...`
Add content keys to one or more vaults for a service. `FILE` contains one `KID:KEY` pair per line (32 hex : 32 hex, UTF-8). Lines that don't match are skipped.
### `kv search KID`
Search configured vaults for a KID (32 hex characters, no dashes) and report any matching key.
| Option | Description |
|---|---|
| `-s`, `--service` | Limit the search to a specific service tag. |
| `-v`, `--vault` | Limit the search to a specific configured vault by name. |
!!! note
Remote vaults cannot be enumerated without a service, so pass `--service` when searching them.
### `kv prepare VAULT...`
Create service tables on vaults that use tables, for every installed service, where they don't already exist.
Manage Widevine Device (`.wvd`) files. Devices live in the configured WVDs directory.
```
unshackle wvd COMMAND [ARGS]...
```
| Command | Description |
|---|---|
| `wvd add PATHS...` | Validate and move one or more `.wvd` files into the WVDs directory. |
| `wvd delete NAMES...` | Delete `.wvd` files by name (without extension). Prompts for confirmation. |
| `wvd parse PATH` | Parse a `.wvd` and print its System ID, security level, type, flags, and client info. Relative paths resolve against the WVDs directory. |
| `wvd dump WVD_PATHS... OUT_DIR` | Extract a device's contents (metadata, private key, client ID, VMP) into `OUT_DIR/<name>/`. With no paths, dumps every WVD in the WVDs directory. |
| `wvd new NAME PRIVATE_KEY CLIENT_ID [FILE_HASHES]` | Create a new `.wvd` from a PEM private key and a `ClientIdentification` blob, optionally with a VMP (`FileHashes`) blob. |
`wvd new` options:
| Option | Default | Description |
|---|---|---|
| `-t`, `--type` | `Android` | Device type. |
| `-l`, `--level` | `1` | Security level (1-3). |
| `-o`, `--output` | WVDs dir | Output directory. |
Manage PlayReady Device (`.prd`) files. Devices live in the configured PRDs directory. Built on `pyplayready`.
```
unshackle prd COMMAND [ARGS]...
```
### `prd new PATHS...`
Create a new `.prd`. Provide either a single folder containing `zgpriv.dat` (group key) and `bgroupcert.dat` (group certificate), or two file paths (group key, then group certificate).
Reprovision an existing device by replacing its leaf certificate and keys. The device must support reprovisioning (version 3 or higher). Accepts the same `-e` / `-s` / `-o` options; defaults to overwriting the device in place.
### `prd test DEVICE`
Test a device against the Microsoft PlayReady demo server and print the returned keys.
| `--caddy` | off | Also serve through Caddy (requires the `caddy` binary and a `Caddyfile`). |
| `--api-only` | off | Serve only the REST API, not the CDM endpoints. Incompatible with `--no-widevine`/`--no-playready`. |
| `--no-widevine` | off | Disable the Widevine CDM endpoints. |
| `--no-playready` | off | Disable the PlayReady CDM endpoints. |
| `--no-key` | off | Disable API-key authentication (allows all requests). |
| `--debug-api` | off | Include tracebacks/stderr in API error responses. |
| `--debug` | off | Enable debug logging for API operations. |
| `--remote-only` | off | Expose only the remote service session endpoints (health, services, search, session). Implies `--api-only`. |
WVD files in the WVDs directory and PRD files in the PRDs directory are auto-loaded. The REST API lives under `http://<host>:<port>/api/`, with Swagger UI at `/api/docs/` and a health check at `/api/health` (exempt from auth).
!!! warning "Configure `api_secret` first"
Unless you pass `--no-key`, `serve.api_secret` must be set in your config. Requests authenticate with the `X-Secret-Key` header. Running with `--no-key` disables authentication entirely and should only be used on a trusted, private network.
Various helper media utilities. When a command is given a directory, it processes every `.mkv`/`.mp4` inside it in natural (S01E01 before S01E10) order.
```
unshackle util COMMAND [ARGS]...
```
### `util refresh-services`
```
unshackle util refresh-services
```
Force a refresh (git pull / hard reset) of all service repos configured under `directories.services`.
### `util crop PATH ASPECT`
Losslessly crop H.264/H.265 video at the bitstream level. `ASPECT` is a `W:H` target such as `2.39:1`.
| Option | Default | Description |
|---|---|---|
| `--letter` / `--pillar` | `--letter` | Crop top/bottom (`--letter`) or the sides (`--pillar`). |
| `-o`, `--offset` | `0` | Fine-tune the computed crop area if not perfectly centred. |
| `-p`, `--preview` | off | Preview the crop in MPV (or FFplay) instead of writing a file. |
### `util range PATH`
Losslessly set the video range flag to full or limited at the bitstream level.