mirror of
https://git.gay/ready-dl/pyplayready.git
synced 2025-07-21 15:16:44 +00:00
Initial Commit
This commit is contained in:
parent
de14f87c5c
commit
dc9fc1e217
14 changed files with 1636 additions and 0 deletions
87
pyplayready/pssh.py
Normal file
87
pyplayready/pssh.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
import base64
|
||||
from typing import Union
|
||||
from uuid import UUID
|
||||
|
||||
from construct import Struct, Int32ul, Int16ul, Array, this, Bytes, PaddedString, Switch, Int32ub, Const, Container
|
||||
|
||||
|
||||
class _PlayreadyPSSHStructs:
|
||||
PSSHBox = Struct(
|
||||
"length" / Int32ub,
|
||||
"pssh" / Const(b"pssh"),
|
||||
"fullbox" / Int32ub,
|
||||
"system_id" / Bytes(16),
|
||||
"data_length" / Int32ub,
|
||||
"data" / Bytes(this.data_length)
|
||||
)
|
||||
|
||||
PlayreadyObject = Struct(
|
||||
"type" / Int16ul,
|
||||
"length" / Int16ul,
|
||||
"data" / Switch(
|
||||
this.type,
|
||||
{
|
||||
1: PaddedString(this.length, "utf16")
|
||||
},
|
||||
default=Bytes(this.length)
|
||||
)
|
||||
)
|
||||
|
||||
PlayreadyHeader = Struct(
|
||||
"length" / Int32ul,
|
||||
"record_count" / Int16ul,
|
||||
"records" / Array(this.record_count, PlayreadyObject)
|
||||
)
|
||||
|
||||
|
||||
class PSSH:
|
||||
SYSTEM_ID = UUID(hex="9a04f07998404286ab92e65be0885f95")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: Union[str, bytes]
|
||||
):
|
||||
"""Represents a PlayReady PSSH"""
|
||||
if not data:
|
||||
raise ValueError("Data must not be empty")
|
||||
|
||||
if isinstance(data, str):
|
||||
try:
|
||||
data = base64.b64decode(data)
|
||||
except Exception as e:
|
||||
raise Exception(f"Could not decode data as Base64, {e}")
|
||||
|
||||
try:
|
||||
if self._is_playready_pssh_box(data):
|
||||
pssh_box = _PlayreadyPSSHStructs.PSSHBox.parse(data)
|
||||
if bool(self._is_utf_16(pssh_box.data)):
|
||||
self.wrm_headers = [pssh_box.data.decode("utf-16-le")]
|
||||
elif bool(self._is_utf_16(pssh_box.data[6:])):
|
||||
self.wrm_headers = [pssh_box.data[6:].decode("utf-16-le")]
|
||||
elif bool(self._is_utf_16(pssh_box.data[10:])):
|
||||
self.wrm_headers = [pssh_box.data[10:].decode("utf-16-le")]
|
||||
else:
|
||||
self.wrm_headers = list(self._get_wrm_headers(_PlayreadyPSSHStructs.PlayreadyHeader.parse(pssh_box.data)))
|
||||
elif bool(self._is_utf_16(data)):
|
||||
self.wrm_headers = [data.decode("utf-16-le")]
|
||||
elif bool(self._is_utf_16(data[6:])):
|
||||
self.wrm_headers = [data[6:].decode("utf-16-le")]
|
||||
elif bool(self._is_utf_16(data[10:])):
|
||||
self.wrm_headers = [data[10:].decode("utf-16-le")]
|
||||
else:
|
||||
self.wrm_headers = list(self._get_wrm_headers(_PlayreadyPSSHStructs.PlayreadyHeader.parse(data)))
|
||||
except Exception:
|
||||
raise Exception("Could not parse data as a PSSH Box nor a PlayReadyHeader")
|
||||
|
||||
def _is_playready_pssh_box(self, data: bytes) -> bool:
|
||||
return data[12:28] == self.SYSTEM_ID.bytes
|
||||
|
||||
@staticmethod
|
||||
def _is_utf_16(data: bytes) -> bool:
|
||||
return all(map(lambda i: data[i] == 0, range(1, len(data), 2)))
|
||||
|
||||
@staticmethod
|
||||
def _get_wrm_headers(wrm_header: Container):
|
||||
for record in wrm_header.records:
|
||||
if record.type == 1:
|
||||
yield record.data
|
Loading…
Add table
Add a link
Reference in a new issue