Remove Cdm raw param, Improve PSSH.get_as_box()
The Cdm no longer requires you to specify if it's raw or not thanks to changes in PSSH.get_as_box() now supporting both dynamically. It will parse the data and if its not a box, it will use the provided data in a newly crafted box.
This commit is contained in:
parent
8f7cacb10a
commit
b5ac0f45a2
|
@ -60,7 +60,7 @@ class Cdm:
|
||||||
NUM_OF_SESSIONS = 0
|
NUM_OF_SESSIONS = 0
|
||||||
MAX_NUM_OF_SESSIONS = 50 # most common limit
|
MAX_NUM_OF_SESSIONS = 50 # most common limit
|
||||||
|
|
||||||
def __init__(self, device: Device, pssh: Union[Container, bytes, str], raw: bool = False):
|
def __init__(self, device: Device, pssh: Union[Container, bytes, str]):
|
||||||
"""
|
"""
|
||||||
Open a Widevine Content Decryption Module (CDM) session.
|
Open a Widevine Content Decryption Module (CDM) session.
|
||||||
|
|
||||||
|
@ -69,9 +69,6 @@ class Cdm:
|
||||||
more device-specific information.
|
more device-specific information.
|
||||||
pssh: Protection System Specific Header Box or Init Data. This should be a
|
pssh: Protection System Specific Header Box or Init Data. This should be a
|
||||||
compliant mp4 pssh box, or just the init data (Widevine Cenc Header).
|
compliant mp4 pssh box, or just the init data (Widevine Cenc Header).
|
||||||
raw: This should be set to True if the PSSH data provided is arbitrary data.
|
|
||||||
E.g., a PSSH Box where the init data is not a Widevine Cenc Header, or
|
|
||||||
is simply arbitrary data.
|
|
||||||
|
|
||||||
Devices have a limit on how many sessions can be open and active concurrently.
|
Devices have a limit on how many sessions can be open and active concurrently.
|
||||||
The limit is different for each device and security level, most commonly 50.
|
The limit is different for each device and security level, most commonly 50.
|
||||||
|
@ -92,10 +89,6 @@ class Cdm:
|
||||||
self.NUM_OF_SESSIONS += 1
|
self.NUM_OF_SESSIONS += 1
|
||||||
|
|
||||||
self.device = device
|
self.device = device
|
||||||
self.init_data = pssh
|
|
||||||
|
|
||||||
if not raw:
|
|
||||||
# we only want the init_data of the pssh box
|
|
||||||
self.init_data = PSSH.get_as_box(pssh).init_data
|
self.init_data = PSSH.get_as_box(pssh).init_data
|
||||||
|
|
||||||
self.session_id = get_random_bytes(16)
|
self.session_id = get_random_bytes(16)
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import binascii
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
|
import construct
|
||||||
from construct import Container
|
from construct import Container
|
||||||
from google.protobuf.message import DecodeError
|
from google.protobuf.message import DecodeError
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
@ -78,36 +80,59 @@ class PSSH:
|
||||||
return box
|
return box
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_as_box(data: Union[Container, bytes, str]) -> Container:
|
def get_as_box(data: Union[Container, bytes, str], strict: bool = False) -> Container:
|
||||||
"""
|
"""
|
||||||
Get the possibly arbitrary data as a parsed PSSH mp4 box.
|
Get possibly arbitrary data as a parsed PSSH mp4 box.
|
||||||
If the data is just Widevine PSSH Data (init data) then it will be crafted
|
|
||||||
into a new PSSH mp4 box.
|
Parameters:
|
||||||
If the data could not be recognized as a PSSH box of some form of encoding
|
data: PSSH mp4 box, Widevine Cenc Header (init data), or arbitrary data to
|
||||||
it will raise a ValueError.
|
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.
|
||||||
|
construct.ConstructError: 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):
|
if isinstance(data, str):
|
||||||
|
try:
|
||||||
data = base64.b64decode(data)
|
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):
|
if isinstance(data, bytes):
|
||||||
if base64.b64encode(data).startswith(b"CAES"): # likely widevine pssh data
|
try:
|
||||||
|
data = Box.parse(data)
|
||||||
|
except construct.ConstructError:
|
||||||
|
if strict:
|
||||||
try:
|
try:
|
||||||
cenc_header = WidevinePsshData()
|
cenc_header = WidevinePsshData()
|
||||||
cenc_header.ParseFromString(data)
|
if cenc_header.MergeFromString(data) < len(data):
|
||||||
|
raise DecodeError()
|
||||||
except DecodeError:
|
except DecodeError:
|
||||||
# not actually init data after all
|
raise DecodeError(f"Could not parse data as a PSSH mp4 box nor a Widevine Cenc Header.")
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
|
data = cenc_header.SerializeToString()
|
||||||
data = Box.parse(Box.build(dict(
|
data = Box.parse(Box.build(dict(
|
||||||
type=b"pssh",
|
type=b"pssh",
|
||||||
version=0,
|
version=0,
|
||||||
flags=0,
|
flags=0,
|
||||||
system_ID=PSSH.SystemId.Widevine,
|
system_ID=PSSH.SystemId.Widevine,
|
||||||
init_data=cenc_header.SerializeToString()
|
init_data=data
|
||||||
)))
|
)))
|
||||||
data = Box.parse(data)
|
else:
|
||||||
if isinstance(data, Container):
|
raise ValueError(f"Data is an unexpected type, expected bytes got {data!r}.")
|
||||||
|
|
||||||
return data
|
return data
|
||||||
raise ValueError(f"Unrecognized PSSH data: {data!r}")
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_key_ids(box: Container) -> list[UUID]:
|
def get_key_ids(box: Container) -> list[UUID]:
|
||||||
|
|
Loading…
Reference in New Issue