PSSH: Merge get_as_box into the Constructor
Also improves the code of it overall including documentation. The _box class instance variable has been removed and the raw box is no longer kept.
This commit is contained in:
parent
1ea57865ad
commit
fc47bbb436
|
@ -268,7 +268,7 @@ class Cdm:
|
||||||
if not init_data:
|
if not init_data:
|
||||||
raise InvalidInitData("The init_data must not be empty.")
|
raise InvalidInitData("The init_data must not be empty.")
|
||||||
try:
|
try:
|
||||||
init_data = PSSH.get_as_box(init_data).init_data
|
init_data = PSSH(init_data).init_data
|
||||||
except (ValueError, binascii.Error, DecodeError) as e:
|
except (ValueError, binascii.Error, DecodeError) as e:
|
||||||
raise InvalidInitData(str(e))
|
raise InvalidInitData(str(e))
|
||||||
|
|
||||||
|
|
|
@ -21,8 +21,82 @@ class PSSH:
|
||||||
Widevine = UUID(bytes=b"\xed\xef\x8b\xa9\x79\xd6\x4a\xce\xa3\xc8\x27\xdc\xd5\x1d\x21\xed")
|
Widevine = UUID(bytes=b"\xed\xef\x8b\xa9\x79\xd6\x4a\xce\xa3\xc8\x27\xdc\xd5\x1d\x21\xed")
|
||||||
PlayReady = UUID(bytes=b"\x9a\x04\xf0\x79\x98\x40\x42\x86\xab\x92\xe6\x5b\xe0\x88\x5f\x95")
|
PlayReady = UUID(bytes=b"\x9a\x04\xf0\x79\x98\x40\x42\x86\xab\x92\xe6\x5b\xe0\x88\x5f\x95")
|
||||||
|
|
||||||
def __init__(self, box: Container):
|
def __init__(self, data: Union[Container, str, bytes], strict: bool = False):
|
||||||
self._box = box
|
"""
|
||||||
|
Load a PSSH box or Widevine Cenc Header data as a new v0 PSSH box.
|
||||||
|
|
||||||
|
[Strict mode (strict=True)]
|
||||||
|
|
||||||
|
Supports the following forms of input data in either Base64 or Bytes form:
|
||||||
|
- Full PSSH mp4 boxes (as defined by pymp4 Box).
|
||||||
|
- Full Widevine Cenc Headers (as defined by WidevinePsshData proto).
|
||||||
|
|
||||||
|
[Lenient mode (strict=False, default)]
|
||||||
|
|
||||||
|
If the data is not supported in Strict mode, and is assumed not to be corrupt or
|
||||||
|
parsed incorrectly, the License Server likely accepts a custom init_data value
|
||||||
|
during a License Request call. This is uncommon behavior but not out of realm of
|
||||||
|
possibilities. For example, Netflix does this with it's MSL WidevineExchange
|
||||||
|
scheme.
|
||||||
|
|
||||||
|
Lenient mode will craft a new v0 PSSH box with the init_data field set to
|
||||||
|
the provided data as-is. The data will first be base64 decoded. This behavior
|
||||||
|
may not work in your scenario and if that's the case please manually craft
|
||||||
|
your own PSSH box with the init_data field to be used in License Requests.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If the data is empty.
|
||||||
|
TypeError: If the data is an unexpected type.
|
||||||
|
binascii.Error: If the data could not be decoded as Base64 if provided as a
|
||||||
|
string.
|
||||||
|
DecodeError: If the data could not be parsed as a PSSH mp4 box nor a Widevine
|
||||||
|
Cenc Header and strict mode is enabled.
|
||||||
|
"""
|
||||||
|
if not data:
|
||||||
|
raise ValueError("Data must not be empty.")
|
||||||
|
|
||||||
|
if isinstance(data, Container):
|
||||||
|
box = data
|
||||||
|
else:
|
||||||
|
if isinstance(data, str):
|
||||||
|
try:
|
||||||
|
data = base64.b64decode(data)
|
||||||
|
except (binascii.Error, binascii.Incomplete) as e:
|
||||||
|
raise binascii.Error(f"Could not decode data as Base64, {e}")
|
||||||
|
|
||||||
|
if not isinstance(data, bytes):
|
||||||
|
raise TypeError(f"Expected data to be a {Container}, bytes, or base64, not {data!r}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
box = Box.parse(data)
|
||||||
|
except (IOError, construct.ConstructError): # not a box
|
||||||
|
try:
|
||||||
|
cenc_header = WidevinePsshData()
|
||||||
|
cenc_header.ParseFromString(data)
|
||||||
|
cenc_header = cenc_header.SerializeToString()
|
||||||
|
if cenc_header != data: # not actually a WidevinePsshData
|
||||||
|
raise DecodeError()
|
||||||
|
except DecodeError: # not a widevine cenc header
|
||||||
|
if strict:
|
||||||
|
raise DecodeError(f"Could not parse data as a {Container} nor a {WidevinePsshData}.")
|
||||||
|
# Data is not a Widevine Cenc Header, it's something custom.
|
||||||
|
# The license server likely has something custom to parse it.
|
||||||
|
# See doc-string about Lenient mode for more information.
|
||||||
|
cenc_header = data
|
||||||
|
|
||||||
|
box = Box.parse(Box.build(dict(
|
||||||
|
type=b"pssh",
|
||||||
|
version=0,
|
||||||
|
flags=0,
|
||||||
|
system_ID=PSSH.SystemId.Widevine,
|
||||||
|
init_data=cenc_header
|
||||||
|
)))
|
||||||
|
|
||||||
|
self.version = box.version
|
||||||
|
self.flags = box.flags
|
||||||
|
self.system_id = box.system_ID
|
||||||
|
self.key_ids = box.key_IDs
|
||||||
|
self.init_data = box.init_data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_playready_pssh(box: Container) -> Container:
|
def from_playready_pssh(box: Container) -> Container:
|
||||||
|
@ -78,62 +152,6 @@ class PSSH:
|
||||||
)))
|
)))
|
||||||
|
|
||||||
return box
|
return box
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_as_box(data: Union[Container, bytes, str], strict: bool = False) -> Container:
|
|
||||||
"""
|
|
||||||
Get possibly arbitrary data as a parsed PSSH mp4 box.
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
data: PSSH mp4 box, Widevine Cenc Header (init data), or arbitrary data to
|
|
||||||
parse or craft into a PSSH mp4 box.
|
|
||||||
strict: Do not return a PSSH box for arbitrary data. Require the data to be
|
|
||||||
at least a PSSH mp4 box, or a Widevine Cenc Header.
|
|
||||||
|
|
||||||
Raises:
|
|
||||||
ValueError: If the data is empty, or an unexpected type.
|
|
||||||
binascii.Error: If the data could not be decoded as Base64 if provided
|
|
||||||
as a string.
|
|
||||||
DecodeError: If the data could not be parsed as a PSSH mp4 box
|
|
||||||
nor a Widevine Cenc Header while strict=True.
|
|
||||||
"""
|
|
||||||
if not data:
|
|
||||||
raise ValueError("Data must not be empty.")
|
|
||||||
|
|
||||||
if isinstance(data, Container):
|
|
||||||
return data
|
|
||||||
|
|
||||||
if isinstance(data, str):
|
|
||||||
try:
|
|
||||||
data = base64.b64decode(data)
|
|
||||||
except (binascii.Error, binascii.Incomplete) as e:
|
|
||||||
raise binascii.Error(f"Could not decode data as Base64, {e}")
|
|
||||||
|
|
||||||
if isinstance(data, bytes):
|
|
||||||
try:
|
|
||||||
data = Box.parse(data)
|
|
||||||
except (IOError, construct.ConstructError):
|
|
||||||
if strict:
|
|
||||||
try:
|
|
||||||
cenc_header = WidevinePsshData()
|
|
||||||
if cenc_header.MergeFromString(data) < len(data):
|
|
||||||
raise DecodeError()
|
|
||||||
except DecodeError:
|
|
||||||
raise DecodeError(f"Could not parse data as a PSSH mp4 box nor a Widevine Cenc Header.")
|
|
||||||
else:
|
|
||||||
data = cenc_header.SerializeToString()
|
|
||||||
data = Box.parse(Box.build(dict(
|
|
||||||
type=b"pssh",
|
|
||||||
version=0,
|
|
||||||
flags=0,
|
|
||||||
system_ID=PSSH.SystemId.Widevine,
|
|
||||||
init_data=data
|
|
||||||
)))
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Data is an unexpected type, expected bytes got {data!r}.")
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_key_ids(box: Container) -> list[UUID]:
|
def get_key_ids(box: Container) -> list[UUID]:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -164,7 +164,7 @@ class RemoteCdm(Cdm):
|
||||||
if not init_data:
|
if not init_data:
|
||||||
raise InvalidInitData("The init_data must not be empty.")
|
raise InvalidInitData("The init_data must not be empty.")
|
||||||
try:
|
try:
|
||||||
init_data = PSSH.get_as_box(init_data).init_data
|
init_data = PSSH(init_data).init_data
|
||||||
except (ValueError, binascii.Error, DecodeError) as e:
|
except (ValueError, binascii.Error, DecodeError) as e:
|
||||||
raise InvalidInitData(str(e))
|
raise InvalidInitData(str(e))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue