mirror of
				https://github.com/devine-dl/pywidevine.git
				synced 2025-11-03 19:34:49 +00:00 
			
		
		
		
	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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user