A remote CDM tried base64 first on any licence not starting with "<?xml", which can decode XML text into garbage. Also lists L3 as a device_name.
8.9 KiB
DRM & CDM
These are the config keys. For concepts, device provisioning, and the wvd/prd commands see
the DRM & CDM guide. Device files themselves live in
directories.wvds (.wvd) and directories.prds (.prd).
cdm
- Type:
dict· Default:{}
Maps a service tag to the CDM (Widevine/PlayReady device) to use, with a default fallback.
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.
Over --remote, an override also makes your device license the remote session, not the server
CDM. A per-service entry does the same when it names a device without a quality: a device name,
a widevine or playready sub-entry, or a profile or default sub-entry. A per-service entry
that selects only by quality, or only for a different profile, loads no device when the run
starts. unshackle then shows a warning, and the server decides who licenses the remote session.
With only the top-level default entry, the server CDM licenses when the API key has it. See
remote_services.
cdm:
default: chromecdm_l3
EXAMPLE2: android_l1
A value may also be a nested dict for advanced selection by quality, DRM system, or profile:
cdm:
EXAMPLE1:
">=1080": android_l1 # by track height
"<1080": chromecdm_l3
widevine: android_l1 # by DRM system
playready: sl3000
default: chromecdm_l3
Quality keys can hold 1080, >=1080, >720, <=576, <480 style comparisons. DRM keys
are widevine / playready. unshackle matches the quality keys first, but only when a video
height is known. If the value is still a dict after that, unshackle uses
widevine/playready when present, or else the credential profile name, then default.
A quality key can also hold a dict of widevine / playready devices, and so can the
top-level default entry:
cdm:
default:
widevine: chromecdm_l3
playready: sl2000
EXAMPLE1:
">=1080": { widevine: android_l1, playready: sl3000 }
"<1080": { widevine: chromecdm_l3, playready: sl2000 }
A quality key that holds one device name applies to both DRM systems. When a track needs the
other system, unshackle uses the widevine / playready config key of the entry. dl --all-drm
needs a device for each system from one of these shapes.
unshackle finds the resolved name in remote_cdm by name first. If no entry
matches, unshackle loads it as a local device file, in this order: <name>.prd in
directories.prds, <name>.prd in directories.wvds, then <name>.wvd in
directories.wvds.
remote_cdm
- Type:
list[dict]· Default:[]
A list of remote CDM definitions. unshackle matches each entry by its name (referenced from
cdm), and its type selects the backend:
type |
Backend | Key fields |
|---|---|---|
decrypt_labs |
Decrypt Labs KeyXtractor | host, device_name, secret (fields) |
custom_api |
Fully YAML-configurable remote API | host (required), device, auth, endpoints, request_mapping, response_mapping, caching, timeout (fields) |
(none), Device Type: PLAYREADY |
pyplayready RemoteCdm |
host, secret, device_name, security_level (default 3000) |
| (none), otherwise | pywidevine RemoteCdm |
host, secret, device_name, device_type, system_id, security_level (default 3000) |
remote_cdm:
- name: keyxtractor
type: decrypt_labs
device_name: L1
- name: my_wv_server
host: https://cdm.example.com
secret: s3cr3t
device_name: android_l1
device_type: ANDROID
system_id: 26830
security_level: 1
For the two pywidevine/pyplayready backends, field names are read case-insensitively in both
styles: Device Type/device_type, System ID/system_id, Security Level/security_level,
Host/host, Secret/secret, Device Name/device_name.
!!! note "Serve APIs behind a proxy"
unshackle does not send upstream's HEAD request or examine the Server response
header, so the server can operate behind a reverse proxy or a CDN that removes or
changes that header. Thus a host that is not a serve API fails at the first licence
request, not when unshackle loads the CDM. On a redirect to a different host or port,
unshackle removes the secret from the request.
!!! warning "PlayReady host needs a /playready suffix"
pyplayready's RemoteCdm treats host as a base URL and appends its own endpoint paths,
so a PlayReady entry's host must include the trailing /playready segment (for example
https://cdm.example.com/playready). Without it the server returns 404 rather than a
configuration error.
decrypt_labs fields
| Field | Default | Notes |
|---|---|---|
host |
https://keyxtractor.decryptlabs.com |
|
device_name |
ChromeCDM |
ChromeCDM, L1, L2, L3 (Widevine) or SL2, SL3 (PlayReady). The host decides which of these names it serves. |
secret |
from decrypt_labs_api_key |
Sent as the decrypt-labs-api-key header. An error is raised if neither is set. |
system_id |
26830 (Widevine), 0 (PlayReady) |
|
security_level |
Widevine 3; PlayReady 2000 for SL2, else 3000 |
unshackle uses PlayReady mode when device_type is PLAYREADY or device_name starts with SL.
custom_api fields
| Field | Type | Notes |
|---|---|---|
host |
str | Required. Base URL of the API. |
device |
dict | name, type (CHROME, ANDROID, PLAYREADY), system_id, security_level |
auth |
dict | type (default header): header uses header_name (default Authorization) and key; bearer uses bearer_token or key; basic uses username and password. custom_headers is merged in for any type. |
endpoints |
dict | get_request and decrypt_response, each {path, method, timeout} |
request_mapping |
dict | Per endpoint: param_names, static_params, conditional_params, transforms, nested_params, exclude_params |
response_mapping |
dict | Per endpoint: fields, transforms, response_types, success_conditions, error_fields, key_fields |
caching |
dict | enabled, use_vaults, check_cached_first |
legacy |
dict | Legacy mode options. |
timeout |
int | Default 30. Request timeout in seconds. |
Transforms cover base64/hex/JSON encoding and kid:key parsing. unshackle uses PlayReady mode
when device.type is PLAYREADY or the device name is SL2/SL3.
remote_cdm:
- name: my_custom
type: custom_api
host: https://cdm.example.com
device:
name: ChromeCDM
type: CHROME
system_id: 26830
security_level: 3
auth:
type: bearer
key: your-token
endpoints:
get_request:
path: /get-challenge
method: POST
decrypt_response:
path: /get-keys
method: POST
timeout: 30
decryption
- Type:
strordict· Default:"shaka"
Selects the tool that physically decrypts CENC-encrypted tracks. unshackle compares the value
case-insensitively. "mp4decrypt" selects Bento4's mp4decrypt, and anything else
(including "shaka") selects shaka-packager. When the value is a dict, unshackle compares the
service tag case-insensitively and uses the default entry as the fallback.
unshackle decrypts HLS AES-128 ClearKey in-process and ignores this setting.
=== "Global"
```yaml
decryption: shaka
```
=== "Per-service"
```yaml
decryption:
default: shaka
EXAMPLE1: mp4decrypt
```
decrypt_segments
- Type:
bool· Default:false
Decrypts each fragmented MP4 segment as it arrives, instead of decrypting the whole track after the download ends. The decryption runs during the download, so the wait after the last segment goes away. The output file is the same either way.
This works with mp4decrypt only. Set decryption to mp4decrypt for the
service (or as the default); with shaka the option has no effect, because shaka-packager
cannot decrypt a segment without its init segment. It applies to DASH tracks and to HLS
tracks that use one init segment, one EXT-X-KEY and no EXT-X-DISCONTINUITY. Every other
track keeps the whole-file decryption.
!!! note "No resume"
A track that decrypts segment by segment always downloads from the start.
[`continue_downloads`](download.md#continue_downloads) cannot reuse its segments, because a reused segment
is already decrypted.
decrypt_segments: true