feat(drm): let a track pin the DRM system it licenses with

Adds Track.drm_preference so a service can pin a track to Widevine or
PlayReady when a manifest advertises both but only one will license it.
Honoured in DASH/HLS selection, the dl licence path and the remote/API
track round-trip. Also fixes quality-keyed CDM maps sending audio to the
default entry, and a licence callable binding the CDM before the swap.
This commit is contained in:
imSp4rky
2026-08-23 16:32:23 -06:00
parent d8739b3561
commit 1aa6315dc3
8 changed files with 193 additions and 39 deletions
@@ -7,6 +7,7 @@ from langcodes import Language
from unshackle.core.api.errors import APIError, APIErrorCode
from unshackle.core.api.handlers import (
drm_preference_name,
sanitize_log,
search_handler,
serialize_audio_track,
@@ -161,6 +162,31 @@ def test_serialize_audio_track_basic() -> None:
assert d["descriptive"] is False
def test_serialize_video_track_drm_preference_round_trip() -> None:
d = serialize_video_track(video())
assert d["drm_preference"] is None
v = video()
v.drm_preference = "pr"
assert serialize_video_track(v)["drm_preference"] == "pr"
def test_serialize_audio_track_drm_preference() -> None:
a = audio()
a.drm_preference = "widevine"
assert serialize_audio_track(a)["drm_preference"] == "widevine"
def test_drm_preference_name_maps_short_forms() -> None:
v = video()
assert drm_preference_name(v) is None
v.drm_preference = "pr"
assert drm_preference_name(v) == "playready"
v.drm_preference = "wv"
assert drm_preference_name(v) == "widevine"
v.drm_preference = "widevine"
assert drm_preference_name(v) == "widevine"
def test_serialize_subtitle_track_basic() -> None:
d = serialize_subtitle_track(subtitle(forced=True))
assert d["id"] == "sub-001"
@@ -108,6 +108,23 @@ def test_build_tracks_aggregates() -> None:
assert len(t.subtitles) == 1
def test_build_tracks_restores_drm_preference() -> None:
data = {
"video": [{"id": "v", "codec": "AVC", "width": 1280, "height": 720, "drm_preference": "playready"}],
"audio": [{"id": "a", "codec": "AAC", "channels": 2, "drm_preference": "wv"}],
"subtitles": [],
}
t = build_tracks(data)
assert t.videos[0].drm_preference == "playready"
assert t.audio[0].drm_preference == "wv"
def test_build_tracks_ignores_unknown_drm_preference() -> None:
data = {"video": [{"id": "v", "codec": "AVC", "width": 1, "height": 1, "drm_preference": "fairplay"}]}
t = build_tracks(data)
assert t.videos[0].drm_preference is None
def test_match_track_by_id() -> None:
a = deserialize_video({"id": "v1", "codec": "AVC", "width": 1920, "height": 1080})
b = deserialize_video({"id": "v2", "codec": "AVC", "width": 1280, "height": 720})