mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-06-22 17:07:23 +00:00
feat(drm): add native DASH ClearKey (org.w3.clearkey) support
unshackle's DASH parser only recognised Widevine and PlayReady ContentProtection, so services using W3C EME ClearKey had to fake a Widevine object and monkey-patch get_content_keys. Add a first-class ClearKeyCENC DRM type so services just implement a license callback. - ClearKeyCENC (core/drm/clearkey_cenc.py): KID-based, no CDM/PSSH; builds the W3C JSON license request (unpadded base64url), parses the JWK Set response (dict/str/bytes), falls back to POSTing the manifest Laurl when the service returns None, decrypts via the same shaka/ mp4decrypt CENC path as Widevine - DASH.get_drm emits ClearKeyCENC for scheme e2719d58-...; KID from own or sibling mp4protection cenc:default_KID, Laurl across dashif/legacy/ bare namespaces - track.download dispatches prepare_drm for ClearKeyCENC; dl.prepare_drm gains a clearkey branch (cache/vault lookup, license-failure tolerated when content_keys pre-populated, vault push, export) - Service.get_clearkey_license base callback (default None -> Laurl); drm_from_dict reconstructs ClearKeyCENC for export/import round-trip - EXAMPLE service + config demo the callback - Tests: tests/core/test_clearkey_cenc.py and an export round-trip case - Docs: DRM_CONFIG.md ClearKey section
This commit is contained in:
@@ -14,7 +14,9 @@ from types import SimpleNamespace
|
||||
from uuid import UUID
|
||||
|
||||
from unshackle.commands.dl import dl
|
||||
from unshackle.core.drm import drm_from_dict
|
||||
from unshackle.core.drm.clearkey import ClearKey
|
||||
from unshackle.core.drm.clearkey_cenc import ClearKeyCENC
|
||||
from unshackle.core.import_service import ImportService
|
||||
from unshackle.core.titles import Movie
|
||||
from unshackle.core.tracks import Audio, Chapter, Subtitle, Video
|
||||
@@ -162,6 +164,25 @@ def test_drm_free_export_roundtrips_through_import_service(tmp_path: Path) -> No
|
||||
assert [c.name for c in svc.get_chapters(movie)] == [None, "Intro"]
|
||||
|
||||
|
||||
def test_clearkey_cenc_exports_drm_and_keys(tmp_path: Path) -> None:
|
||||
"""A licensed ClearKeyCENC exports its system dict and KID:KEY map, and the
|
||||
exported DRM dict plus keys rebuild a decrypt-ready instance via drm_from_dict."""
|
||||
export = tmp_path / "export.json"
|
||||
title = make_title()
|
||||
video = title.tracks.videos[0]
|
||||
drm = ClearKeyCENC(kids=[KID], laurl="https://license.example.test/ck", content_keys={KID: "cc" * 16})
|
||||
|
||||
make_dl().write_export(export, title, video, drm)
|
||||
|
||||
track = read_export(export)["titles"]["movie-1"]["tracks"]["v1"]
|
||||
assert track["drm"] == [{"system": "ClearKeyCENC", "kids": [KID.hex], "laurl": "https://license.example.test/ck"}]
|
||||
assert track["keys"] == {KID.hex: "cc" * 16}
|
||||
|
||||
rebuilt = drm_from_dict({**track["drm"][0], "content_keys": track["keys"]})
|
||||
assert isinstance(rebuilt, ClearKeyCENC)
|
||||
assert rebuilt.content_keys == {KID: "cc" * 16}
|
||||
|
||||
|
||||
def test_keyless_content_keys_writes_no_keys_entry(tmp_path: Path) -> None:
|
||||
"""A DRM object with empty content_keys must not create an empty keys map."""
|
||||
export = tmp_path / "export.json"
|
||||
|
||||
Reference in New Issue
Block a user