diff --git a/docs/guide/drm-and-cdm.md b/docs/guide/drm-and-cdm.md index 852e161e..b9df2c1e 100644 --- a/docs/guide/drm-and-cdm.md +++ b/docs/guide/drm-and-cdm.md @@ -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 diff --git a/docs/reference/configuration/drm.md b/docs/reference/configuration/drm.md index f177f92e..4170b4cc 100644 --- a/docs/reference/configuration/drm.md +++ b/docs/reference/configuration/drm.md @@ -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: diff --git a/tests/core/test_all_drm.py b/tests/core/test_all_drm.py index 57164a2c..ce5079af 100644 --- a/tests/core/test_all_drm.py +++ b/tests/core/test_all_drm.py @@ -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" diff --git a/unshackle/commands/dl.py b/unshackle/commands/dl.py index f4bd8986..1b3fa164 100644 --- a/unshackle/commands/dl.py +++ b/unshackle/commands/dl.py @@ -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: diff --git a/unshackle/core/api/handlers.py b/unshackle/core/api/handlers.py index a4d13754..c13c3b27 100644 --- a/unshackle/core/api/handlers.py +++ b/unshackle/core/api/handlers.py @@ -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")