"""Tests for ``dl.write_export`` — the ``--export`` JSON sidecar. Regression: DRM-free tracks never pass through ``prepare_drm``, so ``write_export`` must accept ``drm=None`` (and DRM systems without ``to_dict``/``content_keys`` such as ClearKey) and still record the track/manifest/chapter/attachment info that ``unshackle import`` rebuilds a download from. """ from __future__ import annotations import json from pathlib import Path from uuid import UUID from unshackle.commands.dl import dl from unshackle.core.drm.clearkey import ClearKey from unshackle.core.titles import Movie from unshackle.core.tracks import Audio, Subtitle, Video KID = UUID(hex="00000000000000000000000000000001") class StubService: """Stands in for the service class slot on Movie; never instantiated.""" class StubDRM: """Minimal licensed-DRM shape: ``to_dict`` plus filled ``content_keys``.""" def __init__(self) -> None: self.content_keys = {KID: "aa" * 16} def to_dict(self) -> dict: return {"system": "Widevine", "pssh_b64": "AAAA"} def make_dl() -> dl: # __new__ skips the CLI-driven __init__; write_export only needs `service`. instance = dl.__new__(dl) instance.service = "EXAMPLE" return instance def make_title() -> Movie: title = Movie(id_="movie-1", service=StubService, name="Example Movie", year=2024, language="en") title.tracks.add( Video( id_="v1", url="https://example.test/v1.mp4", language="en", codec=Video.Codec.AVC, range_=Video.Range.SDR, width=1920, height=1080, bitrate=5_000_000, ) ) title.tracks.add( Audio( id_="a1", url="https://example.test/a1.mp4", language="en", codec=Audio.Codec.AAC, bitrate=128_000, ) ) title.tracks.add( Subtitle( id_="s1", url="https://example.test/s1.vtt", language="en", codec=Subtitle.Codec.WebVTT, ) ) return title def read_export(path: Path) -> dict: return json.loads(path.read_text(encoding="utf8")) def test_drm_free_track_exports(tmp_path: Path) -> None: """The reported bug: DRM-free downloads produced no usable export.""" export = tmp_path / "export.json" title = make_title() video = title.tracks.videos[0] make_dl().write_export(export, title, video) doc = read_export(export) assert doc["version"] == 2 assert doc["service"] == "EXAMPLE" tinfo = doc["titles"]["movie-1"] assert tinfo["meta"]["name"] == "Example Movie" assert set(tinfo["tracks"]) == {"v1", "a1", "s1"} assert "keys" not in tinfo["tracks"]["v1"] assert "drm" not in tinfo["tracks"]["v1"] def test_clearkey_drm_exports_track_without_keys(tmp_path: Path) -> None: """ClearKey has no to_dict/content_keys; the track info must still export.""" export = tmp_path / "export.json" title = make_title() video = title.tracks.videos[0] make_dl().write_export(export, title, video, ClearKey(key="bb" * 16)) doc = read_export(export) track = doc["titles"]["movie-1"]["tracks"]["v1"] assert "keys" not in track assert "drm" not in track def test_post_download_write_keeps_licensed_keys(tmp_path: Path) -> None: """The drm=None write after download must not clobber prepare_drm's DRM/keys.""" export = tmp_path / "export.json" title = make_title() video = title.tracks.videos[0] runner = make_dl() runner.write_export(export, title, video, StubDRM()) # prepare_drm runner.write_export(export, title, video) # post-download hook track = read_export(export)["titles"]["movie-1"]["tracks"]["v1"] assert track["drm"] == [{"system": "Widevine", "pssh_b64": "AAAA"}] assert track["keys"] == {KID.hex: "aa" * 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" title = make_title() video = title.tracks.videos[0] drm = StubDRM() drm.content_keys = {} make_dl().write_export(export, title, video, drm) track = read_export(export)["titles"]["movie-1"]["tracks"]["v1"] assert track["drm"] == [{"system": "Widevine", "pssh_b64": "AAAA"}] assert "keys" not in track