feat(dl): add --cdm to pin one CDM device for a run

Overrides the cdm config mapping (and its quality/DRM sub-entries) for the whole run; the REST API cdm field now flows through the same param.
This commit is contained in:
imSp4rky
2026-08-24 16:37:48 -06:00
parent 8c43250c9b
commit e804d09f0a
7 changed files with 21 additions and 6 deletions
+1
View File
@@ -186,6 +186,7 @@ order the service used, then renumbers the episodes into the order you asked for
| Flag | Description |
|---|---|
| `--cdm-only` / `--vaults-only` | Use only the CDM, or only Key Vaults, for key acquisition. |
| `--cdm <name>` | Use the named CDM device from the `cdm` config mapping for this run, ignoring the service/default mapping. |
| `--skip-dl` | Skip the download but still retrieve keys. |
| `--export` | Export track info and keys to a JSON file in the exports directory. |
+2
View File
@@ -751,6 +751,7 @@ By default, unshackle checks your **key vaults** first and only asks a **CDM** t
a content key when the vault misses. You can force one side or the other:
- `--cdm-only`: only use the CDM (skip vaults).
- `--cdm <name>`: use the named CDM device for this run, ignoring the `cdm` config mapping (including its quality and Widevine/PlayReady sub-entries).
- `--vaults-only`: only use key vaults and never license through the CDM. A missing
content key fails.
@@ -945,6 +946,7 @@ authoritative list.
| `--workers` / `--downloads` / `--slow` | | Concurrency and pacing. |
| `--list` / `--list-titles` / `--skip-dl` | | Dry runs. |
| `--cdm-only` / `--vaults-only` | | Content key source control. |
| `--cdm <name>` | | Pin one CDM device, bypassing the `cdm` config mapping. |
| `--export` | | Export track info and keys to JSON. |
| `--tmdb` / `--imdb` / `--tvdb` / `--anilist` / `--enrich` | | Metadata overrides. |
| `--tvdb-order` | | Renumber episodes to a TVDB season order. |
+1
View File
@@ -498,6 +498,7 @@ control this trade-off on the `dl` command:
|------|--------|
| `--vaults-only` | Never send a license request; use only cached/vault keys. Fails if a content key is missing. |
| `--cdm-only` | Ignore vaults for sourcing keys and always perform a fresh license request. |
| `--cdm <name>` | Use the named CDM device for this run instead of the one the `cdm` config maps for the service. |
See [key vaults](vaults.md) for how to configure vault backends.
+3 -2
View File
@@ -9,8 +9,9 @@ the [DRM & CDM guide](../../guide/drm-and-cdm.md). Device files themselves live
- **Type:** `dict` &nbsp;·&nbsp; **Default:** `{}`
Maps a service tag to the CDM (Widevine/PlayReady device) to use, with a `default` fallback.
Resolution is case-insensitive: a per-request API override wins, then the per-service entry,
then `default`.
Resolution is case-insensitive: an override (`dl --cdm <name>`, or the `cdm` field of an API
job) wins, then the per-service entry, then `default`. An override pins one device for the whole
run and skips any quality or Widevine/PlayReady sub-entries.
```yaml
cdm:
+10
View File
@@ -833,6 +833,13 @@ class dl:
default=None,
help="Only use CDM, or only use Key Vaults for retrieval of Decryption Keys.",
)
@click.option(
"--cdm",
"cdm_name",
type=str,
default=None,
help="Use this CDM device for the run, overriding the cdm config mapping.",
)
@click.option("--no-proxy", is_flag=True, default=False, help="Force disable all proxy use.")
@click.option(
"--no-proxy-download",
@@ -1201,6 +1208,7 @@ class dl:
f"Applied service-specific '{config_key}' overrides for {self.service}: {override_value}"
)
self.cdm_override = ctx.params.get("cdm_name")
cdm_only = ctx.params.get("cdm_only")
if cdm_only:
@@ -1504,6 +1512,8 @@ class dl:
self.service_anime = bool(getattr(service, "ANIME", False))
self.service_daily = bool(getattr(service, "DAILY", False))
self.server_cdm = getattr(service, "_server_cdm", False)
if self.server_cdm and self.cdm_override:
self.log.warning("--cdm is ignored: this remote service licenses with the server CDM")
self._remote_service = service if hasattr(service, "_server_cdm") else None
start_time = time.time()
+1 -4
View File
@@ -440,6 +440,7 @@ def perform_download(
"no_proxy": params.get("no_proxy", False),
"no_proxy_download": params.get("no_proxy_download", False),
"profile": params.get("profile"),
"cdm_name": params.get("cdm"),
"repack": params.get("repack", False),
"tag": params.get("tag"),
"tmdb_id": params.get("tmdb_id"),
@@ -484,10 +485,6 @@ def perform_download(
daily=params.get("daily", False),
output_dir=Path(params["output_dir"]) if params.get("output_dir") else None,
)
# Per-request CDM override (a device name in the WVDs dir); get_cdm() takes it first.
if params.get("cdm"):
dl_instance.cdm_override = params["cdm"]
# Per-request credential ("user:pass"); feed it into the map get_credentials() reads so a
# client can authenticate without anything being persisted to disk. Without a profile,
# get_credentials() falls back to "default", so store it there too rather than dropping it
+3
View File
@@ -757,6 +757,9 @@ async def download(request: web.Request) -> web.Response:
cdm_only:
type: boolean
description: Only use CDM for content key retrieval (true) or only vaults (false) (default - None)
cdm:
type: string
description: CDM device name on the server to license with, overriding the cdm config mapping. Requires serve.cdm_overrides to allow it (default - None)
proxy:
type: string
description: Proxy URI or country code (default - None)