feat(dl): load the DRM system a cdm entry lists first

The first of the widevine / playready config keys in a cdm entry sets which device loads first and which DRM system licenses a track that offers the two. An entry that lists playready first changes from Widevine to PlayReady.
This commit is contained in:
imSp4rky
2026-10-13 12:44:11 -06:00
parent af4e6d5d47
commit 2ba442f50e
5 changed files with 34 additions and 3 deletions
+3
View File
@@ -495,6 +495,9 @@ unshackle dl --skip-dl --all-drm --export EXAMPLE 81234567
- The `cdm` mapping must name a device for each system. A single device name, or `--cdm`,
stops the run with an error that names the missing system.
- The order of the `widevine` and `playready` config keys in the `cdm` entry sets the order
of the two systems, as in a run without the flag. Each track licenses first with the
system of the first config key.
- A Key Vault is read first, as in a run without the flag. A DRM system sends a challenge
only for a KID that no Key Vault and no earlier track of the run gave a content key
for. When the first system returns all the KIDs of the second, the second sends no
+5
View File
@@ -45,6 +45,11 @@ are `widevine` / `playready`. unshackle matches the quality keys first, but only
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`.
When an entry holds `widevine` and `playready`, their order sets which device unshackle loads
first. A track that offers the two DRM systems licenses with that system. A track that offers
only the other system uses the other device. A `widevine` / `playready` dict in a quality key
does not set the order.
A quality key can also hold a dict of `widevine` / `playready` devices, and so can the
top-level `default` entry:
+21 -1
View File
@@ -17,7 +17,7 @@ from pywidevine.pssh import PSSH
from rich.table import Table
from unshackle.commands.dl import dl
from unshackle.core.api.handlers import validate_download_parameters
from unshackle.core.api.handlers import load_full_cdm, validate_download_parameters
from unshackle.core.cdm.detect import cdm_type_stub, is_widevine_cdm
from unshackle.core.drm import PlayReady, Widevine
from unshackle.core.manifests import HLS
@@ -286,3 +286,23 @@ def test_the_init_segment_adds_the_drm_system_the_track_does_not_have(monkeypatc
Track.add_init_drm(track, b"init", None)
assert track.drm == [wv, pr]
@pytest.mark.parametrize(
("entry", "expected"),
[
({"playready": "pr_device", "widevine": "wv_device"}, "pr_device"),
({"widevine": "wv_device", "playready": "pr_device"}, "wv_device"),
],
)
def test_the_first_drm_system_of_the_config_entry_loads_first(
cmd: dl, monkeypatch: pytest.MonkeyPatch, entry: dict, expected: str
) -> None:
monkeypatch.setattr("unshackle.commands.dl.config.cdm", {"EXAMPLE": entry})
monkeypatch.setattr("unshackle.core.cdm.load_cdm", lambda name, **_: name)
monkeypatch.setattr("unshackle.core.config.config.cdm", {"EXAMPLE": entry})
cmd.vaults = None
assert cmd.get_cdm("EXAMPLE") == expected
assert load_full_cdm("EXAMPLE", None) == expected
assert cmd.get_cdm("EXAMPLE", drm="widevine") == "wv_device"
+3 -1
View File
@@ -5580,6 +5580,7 @@ class dl:
"""
Get CDM for a specified service (either Local or Remote CDM).
Now supports quality-based selection when the caller gives a quality.
With no ``drm``, an entry gives the DRM system that it lists first.
Raises a ValueError if there is a problem getting a CDM.
"""
# A per-request override (set by the REST API per job) takes precedence over the
@@ -5660,7 +5661,8 @@ class dl:
"pr": "playready",
"playready": "playready",
}.get(drm.lower())
cdm_name = lower_keys.get(drm_key or "widevine") or lower_keys.get("playready")
first = next(key for key in lower_keys if key in ("widevine", "playready"))
cdm_name = lower_keys.get(drm_key or first) or lower_keys.get("playready")
else:
cdm_name = cdm_name.get(profile) or cdm_name.get("default") or ci_get(config.cdm, "default")
if not cdm_name:
+2 -1
View File
@@ -156,7 +156,8 @@ def load_full_cdm(service: str, profile: Optional[str], cdm_type: Optional[str]
drm_key = {"wv": "widevine", "widevine": "widevine", "pr": "playready", "playready": "playready"}.get(
cdm_type.lower()
)
cdm_name = lower_keys.get(drm_key or "widevine") or lower_keys.get("playready")
first = next(key for key in lower_keys if key in ("widevine", "playready"))
cdm_name = lower_keys.get(drm_key or first) or lower_keys.get("playready")
else:
cdm_name = cdm_name.get(profile) or cdm_name.get("default") or ci_get(app_config.cdm, "default")