fix(hls): read the key id from the init segment when the PSSH has none

An EXT-X-KEY whose Widevine PSSH holds a content id and no key id raised KIDNotFound. Covers download, licence-only, --all-drm, session keys, import and the server CDM path. Fixes #166.
This commit is contained in:
imSp4rky
2026-10-13 12:14:34 -06:00
parent b4136e1cf5
commit af4e6d5d47
6 changed files with 256 additions and 32 deletions
@@ -13,6 +13,7 @@ from uuid import UUID
import pytest
from pywidevine.pssh import PSSH as WvPSSH
from tests.core.test_hls_kid_from_init import NO_KID_PSSH
from unshackle.core.api import handlers
from unshackle.core.drm import Widevine
from unshackle.core.tracks import Track
@@ -22,13 +23,14 @@ pytestmark = pytest.mark.unit
KID = UUID("161219ec3df64e0eadbb3827c0ccc46d")
def _media_playlist() -> str:
pssh = WvPSSH.new(key_ids=[KID], system_id=WvPSSH.SystemId.Widevine).dumps()
def _media_playlist(pssh: str | None = None) -> str:
pssh = pssh or WvPSSH.new(key_ids=[KID], system_id=WvPSSH.SystemId.Widevine).dumps()
return "\n".join(
[
"#EXTM3U",
"#EXT-X-VERSION:6",
"#EXT-X-TARGETDURATION:6",
'#EXT-X-MAP:URI="init.mp4"',
f'#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,{pssh}",'
'KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",KEYFORMATVERSIONS="1"',
"#EXTINF:6.0,",
@@ -49,3 +51,21 @@ def test_hls_descriptor_alone_reaches_the_media_playlist_keys():
assert track.drm and isinstance(track.drm[0], Widevine), "media playlist DRM not read"
assert track.drm[0].kids == [KID]
assert base64.b64decode(handlers.extract_pssh_from_track(track, "widevine"))
def test_key_without_a_kid_takes_it_from_the_init_segment():
"""A PSSH that holds a content id and no Key ID is DRM, and the server must not call the track clear."""
response = SimpleNamespace(text=_media_playlist(NO_KID_PSSH), ok=True, content=b"init")
session = SimpleNamespace(get=lambda url, headers=None: response)
track = SimpleNamespace(
id="vid",
url="https://cdn.example.invalid/media.m3u8",
descriptor=Track.Descriptor.HLS,
data={},
drm=None,
get_key_id=lambda init: KID,
)
handlers.ensure_track_drm(track, session)
assert track.drm and track.drm[0].kids == [KID]