mirror of
				https://github.com/devine-dl/pywidevine.git
				synced 2025-11-04 03:44:50 +00:00 
			
		
		
		
	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:
 | 
			
		||||
            raise InvalidInitData("The init_data must not be empty.")
 | 
			
		||||
        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:
 | 
			
		||||
            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")
 | 
			
		||||
        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):
 | 
			
		||||
        self._box = box
 | 
			
		||||
    def __init__(self, data: Union[Container, str, bytes], strict: bool = False):
 | 
			
		||||
        """
 | 
			
		||||
        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
 | 
			
		||||
    def from_playready_pssh(box: Container) -> Container:
 | 
			
		||||
@ -78,62 +152,6 @@ class PSSH:
 | 
			
		||||
        )))
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
    def get_key_ids(box: Container) -> list[UUID]:
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
@ -164,7 +164,7 @@ class RemoteCdm(Cdm):
 | 
			
		||||
        if not init_data:
 | 
			
		||||
            raise InvalidInitData("The init_data must not be empty.")
 | 
			
		||||
        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:
 | 
			
		||||
            raise InvalidInitData(str(e))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user