PSSH: Add `new()` class method to craft boxes manually

This commit is contained in:
rlaphoenix 2022-08-05 05:36:15 +01:00
parent fc47bbb436
commit 0537c9666c
1 changed files with 82 additions and 1 deletions

View File

@ -2,7 +2,8 @@ from __future__ import annotations
import base64 import base64
import binascii import binascii
from typing import Union import string
from typing import Union, Optional
from uuid import UUID from uuid import UUID
import construct import construct
@ -98,6 +99,86 @@ class PSSH:
self.key_ids = box.key_IDs self.key_ids = box.key_IDs
self.init_data = box.init_data self.init_data = box.init_data
@classmethod
def new(
cls,
key_ids: Optional[list[Union[UUID, str, bytes]]] = None,
init_data: Optional[Union[WidevinePsshData, str, bytes]] = None,
version: int = 0,
flags: int = 0
) -> PSSH:
"""Craft a new version 0 or 1 PSSH Box."""
if key_ids is not None:
if not isinstance(key_ids, list):
raise TypeError(f"Expected key_ids to be a list not {key_ids!r}")
if init_data is not None:
if not isinstance(init_data, (WidevinePsshData, str, bytes)):
raise TypeError(f"Expected init_data to be a {WidevinePsshData}, base64, or bytes, not {init_data!r}")
if not isinstance(version, int):
raise TypeError(f"Expected version to be an int not {version!r}")
if version not in (0, 1):
raise ValueError(f"Invalid version, must be either 0 or 1, not {version}.")
if not isinstance(flags, int):
raise TypeError(f"Expected flags to be an int not {flags!r}")
if flags < 0:
raise ValueError(f"Invalid flags, cannot be less than 0.")
if version == 0:
if key_ids is not None:
raise ValueError("Version 0 PSSH boxes must use init_data only, not key_ids.")
if init_data is None:
raise ValueError("Version 0 PSSH boxes must use init_data but it wasn't provided.")
elif version == 1:
# TODO: I cannot tell if they need either init_data or key_ids exclusively, or both is fine
# So for now I will just make sure at least one is supplied
if init_data is None and key_ids is None:
raise ValueError("Version 1 PSSH boxes must use either init_data or key_ids but neither were provided")
if key_ids is not None:
# ensure key_ids are bytes, supports hex, base64, and bytes
key_ids = [
(
x.bytes if isinstance(x, UUID) else
bytes.fromhex(x) if all(c in string.hexdigits for c in x) else
base64.b64decode(x) if isinstance(x, str) else
x
)
for x in key_ids
]
if not all(isinstance(x, bytes) for x in key_ids):
not_bytes = [x for x in key_ids if not isinstance(x, bytes)]
raise TypeError(
"Expected all of key_ids to be a UUID, hex, base64, or bytes, but one or more are not, "
f"{not_bytes!r}"
)
if init_data is not None:
if isinstance(init_data, WidevinePsshData):
init_data = init_data.SerializeToString()
elif isinstance(init_data, str):
if all(c in string.hexdigits for c in init_data):
init_data = bytes.fromhex(init_data)
else:
init_data = base64.b64decode(init_data)
elif not isinstance(init_data, bytes):
raise TypeError(
f"Expecting init_data to be {WidevinePsshData}, hex, base64, or bytes, not {init_data!r}"
)
box = Box.parse(Box.build(dict(
type=b"pssh",
version=version,
flags=flags,
system_ID=PSSH.SystemId.Widevine,
key_ids=[key_ids, b""][key_ids is None],
init_data=[init_data, b""][init_data is None]
)))
return cls(box)
@staticmethod @staticmethod
def from_playready_pssh(box: Container) -> Container: def from_playready_pssh(box: Container) -> Container:
""" """