diff --git a/docs/dev/rest-api/endpoints.md b/docs/dev/rest-api/endpoints.md index f4f196f4..41706fdb 100644 --- a/docs/dev/rest-api/endpoints.md +++ b/docs/dev/rest-api/endpoints.md @@ -1077,6 +1077,7 @@ Get the content keys for the DRM. The `mode` field selects one of two modes. | `pssh` | Base64 PSSH (server-CDM mode). | | `drm_type` | `widevine` (default) or `playready`. | | `mode` | `proxy` (default) or `server_cdm`. | +| `clear_tracks` (response) | Requested track ids that carry no DRM, so they have no keys. Absent when every requested track is encrypted. | | `vault_keys` (response) | KIDs whose content key came from a server vault, not the CDM. The client proves such a content key with a decode before it trusts it; the field is absent when every content key came from the CDM. | | Status | Error code | Meaning | diff --git a/tests/remote/unit/test_server_cdm_key_verification.py b/tests/remote/unit/test_server_cdm_key_verification.py index 3104809b..0ca26a0c 100644 --- a/tests/remote/unit/test_server_cdm_key_verification.py +++ b/tests/remote/unit/test_server_cdm_key_verification.py @@ -146,3 +146,25 @@ async def test_server_tells_a_remote_vault_only_when_it_served_the_pair(monkeypa assert local.flagged == [("SVC", KID, "v", "sqlite")] assert source.flagged == [("SVC", KID, "v", "sqlite")] assert bystander.flagged == [] + + +def test_resolve_server_keys_does_not_warn_for_clear_tracks(monkeypatch): + """A requested track the server lists in clear_tracks carries no DRM, so its + missing keys are expected and draw no warning.""" + svc = remote_service() + warnings: list = [] + svc.log = SimpleNamespace(warning=lambda msg, *a, **k: warnings.append(msg), debug=lambda *a, **k: None) + svc._server_cdm = True + svc.drain_server_logs = lambda: None + svc.client = SimpleNamespace(post=lambda ep, data: {"keys": {}, "clear_tracks": ["a-1"]}) + + video_track = SimpleNamespace(id="v-1", drm=None) + audio_track = SimpleNamespace(id="a-1", drm=None) + + class Tracks(list): + videos = [video_track] + audio = [audio_track] + + svc.resolve_server_keys(SimpleNamespace(tracks=Tracks([video_track, audio_track]))) + + assert warnings == ["Server CDM returned no content keys for track v-1"] diff --git a/unshackle/core/api/handlers.py b/unshackle/core/api/handlers.py index 87d403f1..f78821db 100644 --- a/unshackle/core/api/handlers.py +++ b/unshackle/core/api/handlers.py @@ -4177,6 +4177,7 @@ async def session_license_handler( all_keys: Dict[str, Dict[str, str]] = {} vault_keys: list[str] = [] + clear_tracks: list[str] = [] drm_types: Dict[str, str] = {} keys_by_pssh: Dict[tuple, Dict[str, str]] = {} sources_by_pssh: Dict[tuple, Dict[str, str]] = {} @@ -4250,7 +4251,8 @@ async def session_license_handler( init_data = fetch_init_segment(track, svc_session) ensure_track_drm(track, svc_session, init_data) if not track.drm: - warn(f"Track {sanitize_log(tid[:12])} carries no DRM, so it has no keys to resolve") + log.info(f"Track {sanitize_log(tid[:12])} carries no DRM, so it has no keys to resolve") + clear_tracks.append(tid) continue title = find_title_for_track(tid, session) @@ -4311,6 +4313,8 @@ async def session_license_handler( actual_drm_type = track_drm_type response: Dict[str, Any] = {"keys": all_keys} + if clear_tracks: + response["clear_tracks"] = clear_tracks if vault_keys: response["vault_keys"] = vault_keys if actual_drm_type: diff --git a/unshackle/core/api/routes.py b/unshackle/core/api/routes.py index 99b2576f..1683dd7d 100644 --- a/unshackle/core/api/routes.py +++ b/unshackle/core/api/routes.py @@ -1833,7 +1833,8 @@ async def session_license(request: web.Request) -> web.Response: License response. In server_cdm mode `keys` maps KID to content key and `vault_keys`, an array of KID hex strings that may be absent and may repeat a KID shared by several tracks, lists the content keys a server vault supplied, which the client has to prove - before it trusts them. + before it trusts them. `clear_tracks`, absent when empty, lists the requested track ids + that carry no DRM and so have no keys. '404': description: Remote session or track not found """ diff --git a/unshackle/core/remote_service.py b/unshackle/core/remote_service.py index 136a67da..c148c237 100644 --- a/unshackle/core/remote_service.py +++ b/unshackle/core/remote_service.py @@ -1178,6 +1178,7 @@ class RemoteService: ) self.drain_server_logs() keys_by_track = resp.get("keys", {}) + clear_tracks = set(resp.get("clear_tracks", [])) vault_kids = set(resp.get("vault_keys", [])) for track_keys in keys_by_track.values(): self.note_vault_keys(track_keys, vault_kids) @@ -1189,7 +1190,7 @@ class RemoteService: for track in title.tracks: track_keys = keys_by_track.get(str(track.id), {}) if not track_keys: - if str(track.id) in track_ids: + if str(track.id) in track_ids and str(track.id) not in clear_tracks: self.log.warning(f"Server CDM returned no content keys for track {track.id}") continue