mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-06-22 08:57:25 +00:00
FairPlay HLS (SAMPLE-AES/cbcs) ships a content KID in its skd:// key but no PlayReady/Widevine PSSH. Synthesize a PlayReady header from that KID so a PlayReady CDM can license and decrypt it whenever the backend is multi-DRM. - FairPlay DRM (PlayReady subclass) + FairPlay.from_kid; fairplay_kid_from_skd extracts the KID from GUID / ?KID= / 32-hex skd forms (core/drm/fairplay.py) - build_pr_header_from_kid synthesizes a WRMHEADER/PRO (cbcs->AESCBC v4.3, cenc->AESCTR v4.0); PlayReady.from_track falls back to a tenc KID when no PSSH - Service.get_fairplay_license hook (defaults to get_playready_license) - dl.py routes FairPlay tracks to get_fairplay_license through the PlayReady CDM - EXAMPLE service + config.yaml document the bridge end to end - tests for the synthesized header and skd KID extraction
59 lines
2.0 KiB
Python
59 lines
2.0 KiB
Python
"""Tests for the FairPlay->PlayReady KID bridge.
|
|
|
|
FairPlay/cbcs HLS ships a tenc DEFAULT_KID but no PlayReady/Widevine PSSH.
|
|
``build_pr_header_from_kid`` synthesizes a PlayReady Object (PRO) from that bare
|
|
KID so a PlayReady license can be requested keyed on it. These pins ensure the
|
|
synthesized header parses back through pyplayready and yields the same KID.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from uuid import UUID
|
|
|
|
import pytest
|
|
from pyplayready.system.pssh import PSSH
|
|
|
|
from unshackle.core.drm.playready import PlayReady, build_pr_header_from_kid
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"kid",
|
|
[
|
|
UUID("279926a3-d9b9-4b6f-8b2c-1a2b3c4d5e6f"),
|
|
UUID("00000000-0000-0000-0000-000000000001"),
|
|
UUID("ffffffff-ffff-ffff-ffff-ffffffffffff"),
|
|
],
|
|
)
|
|
def test_synth_header_parses_and_round_trips_kid(kid: UUID) -> None:
|
|
pssh_b64 = build_pr_header_from_kid(kid)
|
|
|
|
# pyplayready parses the synthesized PRO as a single WRMHEADER record.
|
|
parsed = PSSH(pssh_b64)
|
|
assert len(parsed.wrm_headers) == 1
|
|
|
|
# PlayReady DRM object recovers the original KID from the synthesized header.
|
|
drm = PlayReady(pssh=parsed, pssh_b64=pssh_b64)
|
|
assert [k.hex for k in drm.kids] == [kid.hex]
|
|
|
|
|
|
def test_synth_header_is_v4_0_aesctr() -> None:
|
|
import base64
|
|
|
|
pssh_b64 = build_pr_header_from_kid(UUID("279926a3-d9b9-4b6f-8b2c-1a2b3c4d5e6f"))
|
|
xml = base64.b64decode(pssh_b64).decode("utf-16-le", errors="ignore")
|
|
assert "PlayReadyHeader" in xml
|
|
assert 'version="4.0.0.0"' in xml
|
|
assert "<ALGID>AESCTR</ALGID>" in xml
|
|
|
|
|
|
def test_cbcs_header_v4_3_round_trips_kid() -> None:
|
|
kid = UUID("279926a3-d9b9-4b6f-8b2c-1a2b3c4d5e6f")
|
|
pssh_b64 = build_pr_header_from_kid(kid, scheme="cbcs", version="4.3")
|
|
drm = PlayReady(pssh=PSSH(pssh_b64), pssh_b64=pssh_b64)
|
|
assert [k.hex for k in drm.kids] == [kid.hex]
|
|
|
|
|
|
def test_cbcs_requires_v4_3() -> None:
|
|
with pytest.raises(ValueError, match="4.3"):
|
|
build_pr_header_from_kid(UUID(int=1), scheme="cbcs", version="4.0")
|