mirror of
				https://github.com/devine-dl/devine.git
				synced 2025-11-04 03:44:49 +00:00 
			
		
		
		
	Improve readability and documentation of DASH's to_tracks function
This commit is contained in:
		
							parent
							
								
									e079febe79
								
							
						
					
					
						commit
						e7dc138c0f
					
				@ -19,6 +19,7 @@ from uuid import UUID
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
from langcodes import Language, tag_is_valid
 | 
			
		||||
from lxml.etree import Element
 | 
			
		||||
from pywidevine.cdm import Cdm as WidevineCdm
 | 
			
		||||
from pywidevine.pssh import PSSH
 | 
			
		||||
from requests import Session
 | 
			
		||||
@ -87,12 +88,17 @@ class DASH:
 | 
			
		||||
 | 
			
		||||
        return cls(manifest, url)
 | 
			
		||||
 | 
			
		||||
    def to_tracks(self, language: Union[str, Language], period_filter: Optional[Callable] = None) -> Tracks:
 | 
			
		||||
    def to_tracks(
 | 
			
		||||
        self,
 | 
			
		||||
        language: Optional[Union[str, Language]] = None,
 | 
			
		||||
        period_filter: Optional[Callable] = None
 | 
			
		||||
    ) -> Tracks:
 | 
			
		||||
        """
 | 
			
		||||
        Convert an MPEG-DASH MPD (Media Presentation Description) document to Video, Audio and Subtitle Track objects.
 | 
			
		||||
        Convert an MPEG-DASH document to Video, Audio and Subtitle Track objects.
 | 
			
		||||
 | 
			
		||||
        Parameters:
 | 
			
		||||
            language: Language you expect the Primary Track to be in.
 | 
			
		||||
            language: The Title's Original Recorded Language. It will also be used as a fallback
 | 
			
		||||
                track language value if the manifest does not list language information.
 | 
			
		||||
            period_filter: Filter out period's within the manifest.
 | 
			
		||||
 | 
			
		||||
        All Track URLs will be a list of segment URLs.
 | 
			
		||||
@ -104,171 +110,107 @@ class DASH:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            for adaptation_set in period.findall("AdaptationSet"):
 | 
			
		||||
                trick_mode = any(
 | 
			
		||||
                    x.get("schemeIdUri") == "http://dashif.org/guidelines/trickmode"
 | 
			
		||||
                    for x in (
 | 
			
		||||
                            adaptation_set.findall("EssentialProperty") +
 | 
			
		||||
                            adaptation_set.findall("SupplementalProperty")
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
                if trick_mode:
 | 
			
		||||
                if self.is_trick_mode(adaptation_set):
 | 
			
		||||
                    # we don't want trick mode streams (they are only used for fast-forward/rewind)
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                descriptive = any(
 | 
			
		||||
                    (x.get("schemeIdUri"), x.get("value")) == ("urn:mpeg:dash:role:2011", "descriptive")
 | 
			
		||||
                    for x in adaptation_set.findall("Accessibility")
 | 
			
		||||
                ) or any(
 | 
			
		||||
                    (x.get("schemeIdUri"), x.get("value")) == ("urn:tva:metadata:cs:AudioPurposeCS:2007", "1")
 | 
			
		||||
                    for x in adaptation_set.findall("Accessibility")
 | 
			
		||||
                )
 | 
			
		||||
                forced = any(
 | 
			
		||||
                    x.get("schemeIdUri") == "urn:mpeg:dash:role:2011"
 | 
			
		||||
                    and x.get("value") in ("forced-subtitle", "forced_subtitle")
 | 
			
		||||
                    for x in adaptation_set.findall("Role")
 | 
			
		||||
                )
 | 
			
		||||
                cc = any(
 | 
			
		||||
                    (x.get("schemeIdUri"), x.get("value")) == ("urn:mpeg:dash:role:2011", "caption")
 | 
			
		||||
                    for x in adaptation_set.findall("Role")
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
                for rep in adaptation_set.findall("Representation"):
 | 
			
		||||
                    codecs = rep.get("codecs") or adaptation_set.get("codecs")
 | 
			
		||||
                    get = partial(self._get, adaptation_set=adaptation_set, representation=rep)
 | 
			
		||||
                    findall = partial(self._findall, adaptation_set=adaptation_set, representation=rep, both=True)
 | 
			
		||||
 | 
			
		||||
                    content_type = adaptation_set.get("contentType") or \
 | 
			
		||||
                        adaptation_set.get("mimeType") or \
 | 
			
		||||
                        rep.get("contentType") or \
 | 
			
		||||
                        rep.get("mimeType")
 | 
			
		||||
                    if not content_type:
 | 
			
		||||
                        raise ValueError("No content type value could be found")
 | 
			
		||||
                    content_type = content_type.split("/")[0]
 | 
			
		||||
                    codecs = get("codecs")
 | 
			
		||||
                    content_type = get("contentType")
 | 
			
		||||
                    mime_type = get("mimeType")
 | 
			
		||||
 | 
			
		||||
                    if content_type.startswith("image"):
 | 
			
		||||
                        # we don't want what's likely thumbnails for the seekbar
 | 
			
		||||
                        continue
 | 
			
		||||
                    if content_type == "application":
 | 
			
		||||
                        # possibly application/mp4 which could be mp4-boxed subtitles
 | 
			
		||||
                    if not content_type and mime_type:
 | 
			
		||||
                        content_type = mime_type.split("/")[0]
 | 
			
		||||
                    if not content_type and not mime_type:
 | 
			
		||||
                        raise ValueError("Unable to determine the format of a Representation, cannot continue...")
 | 
			
		||||
 | 
			
		||||
                    if mime_type == "application/mp4" or content_type == "application":
 | 
			
		||||
                        # likely mp4-boxed subtitles
 | 
			
		||||
                        # TODO: It may not actually be subtitles
 | 
			
		||||
                        try:
 | 
			
		||||
                            Subtitle.Codec.from_mime(codecs)
 | 
			
		||||
                            real_codec = Subtitle.Codec.from_mime(codecs)
 | 
			
		||||
                            content_type = "text"
 | 
			
		||||
                            mime_type = f"application/mp4; codecs='{real_codec.value.lower()}'"
 | 
			
		||||
                        except ValueError:
 | 
			
		||||
                            raise ValueError(f"Unsupported content type '{content_type}' with codecs of '{codecs}'")
 | 
			
		||||
 | 
			
		||||
                    if content_type == "text":
 | 
			
		||||
                        mime = adaptation_set.get("mimeType")
 | 
			
		||||
                        if mime and not mime.endswith("/mp4"):
 | 
			
		||||
                            codecs = mime.split("/")[1]
 | 
			
		||||
                    if content_type == "text" and mime_type and "/mp4" not in mime_type:
 | 
			
		||||
                        # mimeType likely specifies the subtitle codec better than `codecs`
 | 
			
		||||
                        codecs = mime_type.split("/")[1]
 | 
			
		||||
 | 
			
		||||
                    supplements = rep.findall("SupplementalProperty") + adaptation_set.findall("SupplementalProperty")
 | 
			
		||||
 | 
			
		||||
                    joc = next((
 | 
			
		||||
                        x.get("value")
 | 
			
		||||
                        for x in supplements
 | 
			
		||||
                        if x.get("schemeIdUri") == "tag:dolby.com,2018:dash:EC3_ExtensionComplexityIndex:2018"
 | 
			
		||||
                    ), None)
 | 
			
		||||
 | 
			
		||||
                    if rep.get("id") is not None:
 | 
			
		||||
                        rep_id_lang = re.match(r"\w+_(\w+)=\d+", rep.get("id"))
 | 
			
		||||
                        if rep_id_lang:
 | 
			
		||||
                            rep_id_lang = rep_id_lang.group(1)
 | 
			
		||||
                    else:
 | 
			
		||||
                        rep_id_lang = None
 | 
			
		||||
 | 
			
		||||
                    track_lang = DASH.get_language(rep.get("lang"), adaptation_set.get("lang"), rep_id_lang, language)
 | 
			
		||||
                    if not track_lang:
 | 
			
		||||
                        raise ValueError(
 | 
			
		||||
                            "One or more Tracks had no Language information. "
 | 
			
		||||
                            "The provided fallback language is not valid or is `None` or `und`."
 | 
			
		||||
                    if content_type == "video":
 | 
			
		||||
                        track_type = Video
 | 
			
		||||
                        track_codec = Video.Codec.from_codecs(codecs)
 | 
			
		||||
                        track_args = dict(
 | 
			
		||||
                            range_=self.get_video_range(
 | 
			
		||||
                                codecs,
 | 
			
		||||
                                findall("SupplementalProperty"),
 | 
			
		||||
                                findall("EssentialProperty")
 | 
			
		||||
                            ),
 | 
			
		||||
                            bitrate=get("bandwidth") or None,
 | 
			
		||||
                            width=get("width") or 0,
 | 
			
		||||
                            height=get("height") or 0,
 | 
			
		||||
                            fps=get("frameRate") or rep.find("SegmentBase", {}).get("timescale") or None
 | 
			
		||||
                        )
 | 
			
		||||
                    elif content_type == "audio":
 | 
			
		||||
                        track_type = Audio
 | 
			
		||||
                        track_codec = Audio.Codec.from_codecs(codecs)
 | 
			
		||||
                        track_args = dict(
 | 
			
		||||
                            bitrate=get("bandwidth") or None,
 | 
			
		||||
                            channels=next(iter(
 | 
			
		||||
                                rep.xpath("AudioChannelConfiguration/@value")
 | 
			
		||||
                                or adaptation_set.xpath("AudioChannelConfiguration/@value")
 | 
			
		||||
                            ), None),
 | 
			
		||||
                            joc=self.get_ddp_complexity_index(adaptation_set, rep),
 | 
			
		||||
                            descriptive=self.is_descriptive(adaptation_set)
 | 
			
		||||
                        )
 | 
			
		||||
                    elif content_type == "text":
 | 
			
		||||
                        track_type = Subtitle
 | 
			
		||||
                        track_codec = Subtitle.Codec.from_codecs(codecs or "vtt")
 | 
			
		||||
                        track_args = dict(
 | 
			
		||||
                            forced=self.is_forced(adaptation_set),
 | 
			
		||||
                            cc=self.is_closed_caption(adaptation_set)
 | 
			
		||||
                        )
 | 
			
		||||
                    elif content_type == "image":
 | 
			
		||||
                        # we don't want what's likely thumbnails for the seekbar
 | 
			
		||||
                        continue
 | 
			
		||||
                    else:
 | 
			
		||||
                        raise ValueError(f"Unknown Track Type '{content_type}'")
 | 
			
		||||
 | 
			
		||||
                    track_lang = self.get_language(adaptation_set, rep, fallback=language)
 | 
			
		||||
                    if not track_lang:
 | 
			
		||||
                        msg = "Language information could not be derived from a Representation."
 | 
			
		||||
                        if language is None:
 | 
			
		||||
                            msg += " No fallback language was provided when calling DASH.to_tracks()."
 | 
			
		||||
                        elif not tag_is_valid((str(language) or "").strip()) or str(language).startswith("und"):
 | 
			
		||||
                            msg += f" The fallback language provided is also invalid: {language}"
 | 
			
		||||
                        raise ValueError(msg)
 | 
			
		||||
 | 
			
		||||
                    # for some reason it's incredibly common for services to not provide
 | 
			
		||||
                    # a good and actually unique track ID, sometimes because of the lang
 | 
			
		||||
                    # dialect not being represented in the id, or the bitrate, or such.
 | 
			
		||||
                    # this combines all of them as one and hashes it to keep it small(ish).
 | 
			
		||||
                    track_id = md5("{codec}-{lang}-{bitrate}-{base_url}-{extra}".format(
 | 
			
		||||
                    track_id = md5("{codec}-{lang}-{bitrate}-{base_url}-{ids}-{track_args}".format(
 | 
			
		||||
                        codec=codecs,
 | 
			
		||||
                        lang=track_lang,
 | 
			
		||||
                        bitrate=rep.get("bandwidth") or 0,  # subs may not state bandwidth
 | 
			
		||||
                        bitrate=get("bitrate"),
 | 
			
		||||
                        base_url=(rep.findtext("BaseURL") or "").split("?")[0],
 | 
			
		||||
                        extra=(adaptation_set.get("audioTrackId") or "") + (rep.get("id") or "") +
 | 
			
		||||
                              (period.get("id") or "")
 | 
			
		||||
                        ids=[get("audioTrackId"), get("id"), period.get("id")],
 | 
			
		||||
                        track_args=track_args
 | 
			
		||||
                    ).encode()).hexdigest()
 | 
			
		||||
 | 
			
		||||
                    if content_type == "video":
 | 
			
		||||
                        track_type = Video
 | 
			
		||||
                        track_codec = Video.Codec.from_codecs(codecs)
 | 
			
		||||
                    elif content_type == "audio":
 | 
			
		||||
                        track_type = Audio
 | 
			
		||||
                        track_codec = Audio.Codec.from_codecs(codecs)
 | 
			
		||||
                    elif content_type == "text":
 | 
			
		||||
                        track_type = Subtitle
 | 
			
		||||
                        track_codec = Subtitle.Codec.from_codecs(codecs or "vtt")
 | 
			
		||||
                    else:
 | 
			
		||||
                        raise ValueError(f"Unknown Track Type '{content_type}'")
 | 
			
		||||
 | 
			
		||||
                    tracks.add(track_type(
 | 
			
		||||
                        id_=track_id,
 | 
			
		||||
                        url=(self.url, self.manifest, rep, adaptation_set, period),
 | 
			
		||||
                        codec=track_codec,
 | 
			
		||||
                        language=track_lang,
 | 
			
		||||
                        is_original_lang=not track_lang or not language or is_close_match(track_lang, [language]),
 | 
			
		||||
                        is_original_lang=language and is_close_match(track_lang, [language]),
 | 
			
		||||
                        descriptor=Video.Descriptor.MPD,
 | 
			
		||||
                        extra=(rep, adaptation_set),
 | 
			
		||||
                        # video track args
 | 
			
		||||
                        **(dict(
 | 
			
		||||
                            range_=(
 | 
			
		||||
                                Video.Range.DV
 | 
			
		||||
                                if codecs.startswith(("dva1", "dvav", "dvhe", "dvh1")) else
 | 
			
		||||
                                Video.Range.from_cicp(
 | 
			
		||||
                                    primaries=next((
 | 
			
		||||
                                        int(x.get("value"))
 | 
			
		||||
                                        for x in (
 | 
			
		||||
                                            adaptation_set.findall("SupplementalProperty")
 | 
			
		||||
                                            + adaptation_set.findall("EssentialProperty")
 | 
			
		||||
                                        )
 | 
			
		||||
                                        if x.get("schemeIdUri") == "urn:mpeg:mpegB:cicp:ColourPrimaries"
 | 
			
		||||
                                    ), 0),
 | 
			
		||||
                                    transfer=next((
 | 
			
		||||
                                        int(x.get("value"))
 | 
			
		||||
                                        for x in (
 | 
			
		||||
                                            adaptation_set.findall("SupplementalProperty")
 | 
			
		||||
                                            + adaptation_set.findall("EssentialProperty")
 | 
			
		||||
                                        )
 | 
			
		||||
                                        if x.get("schemeIdUri") == "urn:mpeg:mpegB:cicp:TransferCharacteristics"
 | 
			
		||||
                                    ), 0),
 | 
			
		||||
                                    matrix=next((
 | 
			
		||||
                                        int(x.get("value"))
 | 
			
		||||
                                        for x in (
 | 
			
		||||
                                            adaptation_set.findall("SupplementalProperty")
 | 
			
		||||
                                            + adaptation_set.findall("EssentialProperty")
 | 
			
		||||
                                        )
 | 
			
		||||
                                        if x.get("schemeIdUri") == "urn:mpeg:mpegB:cicp:MatrixCoefficients"
 | 
			
		||||
                                    ), 0)
 | 
			
		||||
                                )
 | 
			
		||||
                            ),
 | 
			
		||||
                            bitrate=rep.get("bandwidth"),
 | 
			
		||||
                            width=int(rep.get("width") or 0) or adaptation_set.get("width"),
 | 
			
		||||
                            height=int(rep.get("height") or 0) or adaptation_set.get("height"),
 | 
			
		||||
                            fps=(
 | 
			
		||||
                                rep.get("frameRate") or
 | 
			
		||||
                                adaptation_set.get("frameRate") or
 | 
			
		||||
                                (
 | 
			
		||||
                                    rep.find("SegmentBase").get("timescale") if
 | 
			
		||||
                                    rep.find("SegmentBase") is not None else None
 | 
			
		||||
                                )
 | 
			
		||||
                            )
 | 
			
		||||
                        ) if track_type is Video else dict(
 | 
			
		||||
                            bitrate=rep.get("bandwidth"),
 | 
			
		||||
                            channels=next(iter(
 | 
			
		||||
                                rep.xpath("AudioChannelConfiguration/@value")
 | 
			
		||||
                                or adaptation_set.xpath("AudioChannelConfiguration/@value")
 | 
			
		||||
                            ), None),
 | 
			
		||||
                            joc=joc,
 | 
			
		||||
                            descriptive=descriptive
 | 
			
		||||
                        ) if track_type is Audio else dict(
 | 
			
		||||
                            forced=forced,
 | 
			
		||||
                            cc=cc
 | 
			
		||||
                        ) if track_type is Subtitle else {})
 | 
			
		||||
                        **track_args
 | 
			
		||||
                    ))
 | 
			
		||||
 | 
			
		||||
            # only get tracks from the first main-content period
 | 
			
		||||
@ -590,7 +532,76 @@ class DASH:
 | 
			
		||||
            save_dir.rmdir()
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_language(*options: Any) -> Optional[Language]:
 | 
			
		||||
    def _get(
 | 
			
		||||
        item: str,
 | 
			
		||||
        adaptation_set: Element,
 | 
			
		||||
        representation: Optional[Element] = None
 | 
			
		||||
    ) -> Optional[Any]:
 | 
			
		||||
        """Helper to get a requested item from the Representation, otherwise from the AdaptationSet."""
 | 
			
		||||
        adaptation_set_item = adaptation_set.get(item)
 | 
			
		||||
        if representation is None:
 | 
			
		||||
            return adaptation_set_item
 | 
			
		||||
 | 
			
		||||
        representation_item = representation.get(item)
 | 
			
		||||
        if representation_item is not None:
 | 
			
		||||
            return representation_item
 | 
			
		||||
 | 
			
		||||
        return adaptation_set_item
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _findall(
 | 
			
		||||
        item: str,
 | 
			
		||||
        adaptation_set: Element,
 | 
			
		||||
        representation: Optional[Element] = None,
 | 
			
		||||
        both: bool = False
 | 
			
		||||
    ) -> list[Any]:
 | 
			
		||||
        """
 | 
			
		||||
        Helper to get all requested items from the Representation, otherwise from the AdaptationSet.
 | 
			
		||||
        Optionally, you may pass both=True to keep both values (where available).
 | 
			
		||||
        """
 | 
			
		||||
        adaptation_set_items = adaptation_set.findall(item)
 | 
			
		||||
        if representation is None:
 | 
			
		||||
            return adaptation_set_items
 | 
			
		||||
 | 
			
		||||
        representation_items = representation.findall(item)
 | 
			
		||||
 | 
			
		||||
        if both:
 | 
			
		||||
            return representation_items + adaptation_set_items
 | 
			
		||||
 | 
			
		||||
        if representation_items:
 | 
			
		||||
            return representation_items
 | 
			
		||||
 | 
			
		||||
        return adaptation_set_items
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_language(
 | 
			
		||||
        adaptation_set: Element,
 | 
			
		||||
        representation: Optional[Element] = None,
 | 
			
		||||
        fallback: Optional[Union[str, Language]] = None
 | 
			
		||||
    ) -> Optional[Language]:
 | 
			
		||||
        """
 | 
			
		||||
        Get Language (if any) from the AdaptationSet or Representation.
 | 
			
		||||
 | 
			
		||||
        A fallback language may be provided if no language information could be
 | 
			
		||||
        retrieved.
 | 
			
		||||
        """
 | 
			
		||||
        options = []
 | 
			
		||||
 | 
			
		||||
        if representation is not None:
 | 
			
		||||
            options.append(representation.get("lang"))
 | 
			
		||||
            # derive language from somewhat common id string format
 | 
			
		||||
            # the format is typically "{rep_id}_{lang}={bitrate}" or similar
 | 
			
		||||
            rep_id = representation.get("id")
 | 
			
		||||
            if rep_id:
 | 
			
		||||
                m = re.match(r"\w+_(\w+)=\d+", rep_id)
 | 
			
		||||
                if m:
 | 
			
		||||
                    options.append(m.group(1))
 | 
			
		||||
 | 
			
		||||
        options.append(adaptation_set.get("lang"))
 | 
			
		||||
 | 
			
		||||
        if fallback:
 | 
			
		||||
            options.append(fallback)
 | 
			
		||||
 | 
			
		||||
        for option in options:
 | 
			
		||||
            option = (str(option) or "").strip()
 | 
			
		||||
            if not tag_is_valid(option) or option.startswith("und"):
 | 
			
		||||
@ -598,7 +609,82 @@ class DASH:
 | 
			
		||||
            return Language.get(option)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_drm(protections) -> list[Widevine]:
 | 
			
		||||
    def get_video_range(
 | 
			
		||||
        codecs: str,
 | 
			
		||||
        all_supplemental_props: list[Element],
 | 
			
		||||
        all_essential_props: list[Element]
 | 
			
		||||
    ) -> Video.Range:
 | 
			
		||||
        if codecs.startswith(("dva1", "dvav", "dvhe", "dvh1")):
 | 
			
		||||
            return Video.Range.DV
 | 
			
		||||
 | 
			
		||||
        return Video.Range.from_cicp(
 | 
			
		||||
            primaries=next((
 | 
			
		||||
                int(x.get("value"))
 | 
			
		||||
                for x in all_supplemental_props + all_essential_props
 | 
			
		||||
                if x.get("schemeIdUri") == "urn:mpeg:mpegB:cicp:ColourPrimaries"
 | 
			
		||||
            ), 0),
 | 
			
		||||
            transfer=next((
 | 
			
		||||
                int(x.get("value"))
 | 
			
		||||
                for x in all_supplemental_props + all_essential_props
 | 
			
		||||
                if x.get("schemeIdUri") == "urn:mpeg:mpegB:cicp:TransferCharacteristics"
 | 
			
		||||
            ), 0),
 | 
			
		||||
            matrix=next((
 | 
			
		||||
                int(x.get("value"))
 | 
			
		||||
                for x in all_supplemental_props + all_essential_props
 | 
			
		||||
                if x.get("schemeIdUri") == "urn:mpeg:mpegB:cicp:MatrixCoefficients"
 | 
			
		||||
            ), 0)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def is_trick_mode(adaptation_set: Element) -> bool:
 | 
			
		||||
        """Check if contents of Adaptation Set is a Trick-Mode stream."""
 | 
			
		||||
        essential_props = adaptation_set.findall("EssentialProperty")
 | 
			
		||||
        supplemental_props = adaptation_set.findall("SupplementalProperty")
 | 
			
		||||
 | 
			
		||||
        return any(
 | 
			
		||||
            prop.get("schemeIdUri") == "http://dashif.org/guidelines/trickmode"
 | 
			
		||||
            for prop in essential_props + supplemental_props
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def is_descriptive(adaptation_set: Element) -> bool:
 | 
			
		||||
        """Check if contents of Adaptation Set is Descriptive."""
 | 
			
		||||
        return any(
 | 
			
		||||
            (x.get("schemeIdUri"), x.get("value")) in (
 | 
			
		||||
                ("urn:mpeg:dash:role:2011", "descriptive"),
 | 
			
		||||
                ("urn:tva:metadata:cs:AudioPurposeCS:2007", "1")
 | 
			
		||||
            )
 | 
			
		||||
            for x in adaptation_set.findall("Accessibility")
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def is_forced(adaptation_set: Element) -> bool:
 | 
			
		||||
        """Check if contents of Adaptation Set is a Forced Subtitle."""
 | 
			
		||||
        return any(
 | 
			
		||||
            x.get("schemeIdUri") == "urn:mpeg:dash:role:2011"
 | 
			
		||||
            and x.get("value") in ("forced-subtitle", "forced_subtitle")
 | 
			
		||||
            for x in adaptation_set.findall("Role")
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def is_closed_caption(adaptation_set: Element) -> bool:
 | 
			
		||||
        """Check if contents of Adaptation Set is a Closed Caption Subtitle."""
 | 
			
		||||
        return any(
 | 
			
		||||
            (x.get("schemeIdUri"), x.get("value")) == ("urn:mpeg:dash:role:2011", "caption")
 | 
			
		||||
            for x in adaptation_set.findall("Role")
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_ddp_complexity_index(adaptation_set: Element, representation: Optional[Element]) -> Optional[int]:
 | 
			
		||||
        """Get the DD+ Complexity Index (if any) from the AdaptationSet or Representation."""
 | 
			
		||||
        return next((
 | 
			
		||||
            int(x.get("value"))
 | 
			
		||||
            for x in DASH._findall("SupplementalProperty", adaptation_set, representation, both=True)
 | 
			
		||||
            if x.get("schemeIdUri") == "tag:dolby.com,2018:dash:EC3_ExtensionComplexityIndex:2018"
 | 
			
		||||
        ), None)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_drm(protections: list[Element]) -> list[Widevine]:
 | 
			
		||||
        drm = []
 | 
			
		||||
 | 
			
		||||
        for protection in protections:
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user