@@ -578,74 +578,117 @@ def get_text(self):
578578 text = f"{ date_str } { self .text or self .filename } "
579579 return text
580580
581- async def mpd_video_helper (self , mpd_obj = None ):
582- mpd = mpd_obj or await self .parse_mpd
581+
582+ @async_cached_property
583+ async def _video_adaptation_set (self ):
584+ """Finds and caches the first video/mp4 AdaptationSet."""
585+ mpd = await self .parse_mpd
583586 if not mpd :
584587 return None
585- allowed = quality .get_allowed_qualities ()
586588 for period in mpd .periods :
587589 for adapt_set in filter (
588590 lambda x : x .mime_type == "video/mp4" , period .adaptation_sets
589591 ):
590- kId = None
591- for prot in adapt_set .content_protections :
592- if prot .value is None :
593- kId = prot .pssh [0 ].pssh
594- break
595-
596- representations = sorted (
597- adapt_set .representations , key = lambda x : x .height , reverse = True
598- )
599- selected_repr = None
600- for quality_val in allowed :
601- if quality_val .lower () == "source" :
602- selected_repr = representations [0 ]
603- break
604- for r in representations :
605- if str (r .height ) == quality_val :
606- selected_repr = r
607- break
608- if selected_repr :
609- break
610-
611- selected_repr = (
612- selected_repr or representations [0 ]
613- ) # Fallback to highest
614-
615- origname = f"{ selected_repr .base_urls [0 ].base_url_value } "
616- return {
617- "origname" : origname ,
618- "pssh" : kId ,
619- "type" : "video" ,
620- "name" : f"tempvid_{ self .id } _{ self .post_id } " ,
621- "ext" : "mp4" ,
622- }
592+ return adapt_set
593+ return None
623594
624- async def mpd_audio_helper (self , mpd_obj = None ):
625- mpd = mpd_obj or await self .parse_mpd
595+ @async_cached_property
596+ async def _audio_adaptation_set (self ):
597+ """Finds and caches the first audio/mp4 AdaptationSet."""
598+ mpd = await self .parse_mpd
626599 if not mpd :
627600 return None
628601 for period in mpd .periods :
629602 for adapt_set in filter (
630603 lambda x : x .mime_type == "audio/mp4" , period .adaptation_sets
631604 ):
632- kId = None
633- for prot in adapt_set .content_protections :
634- if prot .value is None :
635- kId = prot .pssh [0 ].pssh
636- sensitive .add_sensitive_pattern (kId , "pssh_code" )
637- break
638-
639- # Typically there's only one audio representation
640- repr = adapt_set .representations [0 ]
641- origname = f"{ repr .base_urls [0 ].base_url_value } "
642- return {
643- "origname" : origname ,
644- "pssh" : kId ,
645- "type" : "audio" ,
646- "name" : f"tempaudio_{ self .id } _{ self .post_id } " ,
647- "ext" : "mp4" ,
648- }
605+ return adapt_set
606+ return None
607+
608+ @async_cached_property
609+ async def selected_video_representation (self ):
610+ """
611+ Finds and caches the specific video Representation object
612+ based on user's quality settings.
613+ """
614+ adapt_set = await self ._video_adaptation_set
615+ if not adapt_set :
616+ return None
617+
618+ representations = sorted (
619+ adapt_set .representations , key = lambda x : x .height , reverse = True
620+ )
621+ if not representations :
622+ return None
623+
624+ allowed = quality .get_allowed_qualities ()
625+ # Build a map of height -> representation
626+ # e.g., {'1080': <Repr obj>, '720': <Repr obj>}
627+ available_map = {str (r .height ): r for r in representations }
628+
629+ # Loop from LOWEST to HIGHEST allowed
630+ for qual in allowed :
631+ if qual in available_map :
632+ return available_map [qual ] # Return first (lowest) match
633+
634+ # Fallback: return highest available
635+ return representations [0 ]
636+
637+ @async_cached_property
638+ async def selected_audio_representation (self ):
639+ """Finds and caches the specific audio Representation object."""
640+ adapt_set = await self ._audio_adaptation_set
641+ if not adapt_set or not adapt_set .representations :
642+ return None
643+ # Audio typically only has one representation
644+ return adapt_set .representations [0 ]
645+
646+ async def mpd_video_helper (self , mpd_obj = None ):
647+ # We ignore mpd_obj because our helper properties are cached
648+ adapt_set = await self ._video_adaptation_set
649+ selected_repr = await self .selected_video_representation
650+
651+ if not adapt_set or not selected_repr :
652+ return None
653+
654+ kId = None
655+ for prot in adapt_set .content_protections :
656+ if prot .value is None :
657+ kId = prot .pssh [0 ].pssh
658+ break
659+
660+ origname = f"{ selected_repr .base_urls [0 ].base_url_value } "
661+ return {
662+ "origname" : origname ,
663+ "pssh" : kId ,
664+ "type" : "video" ,
665+ "name" : f"tempvid_{ self .id } _{ self .post_id } " ,
666+ "ext" : "mp4" ,
667+ }
668+
669+ async def mpd_audio_helper (self , mpd_obj = None ):
670+ # We ignore mpd_obj because our helper properties are cached
671+ adapt_set = await self ._audio_adaptation_set
672+ selected_repr = await self .selected_audio_representation
673+
674+ if not adapt_set or not selected_repr :
675+ return None
676+
677+ kId = None
678+ for prot in adapt_set .content_protections :
679+ if prot .value is None :
680+ kId = prot .pssh [0 ].pssh
681+ sensitive .add_sensitive_pattern (kId , "pssh_code" )
682+ break
683+
684+ origname = f"{ selected_repr .base_urls [0 ].base_url_value } "
685+ return {
686+ "origname" : origname ,
687+ "pssh" : kId ,
688+ "type" : "audio" ,
689+ "name" : f"tempaudio_{ self .id } _{ self .post_id } " ,
690+ "ext" : "mp4" ,
691+ }
649692
650693 def normal_quality_helper (self ):
651694 if self .mediatype != "videos" :
@@ -664,25 +707,11 @@ def normal_quality_helper(self):
664707 return "source"
665708
666709 async def alt_quality_helper (self , mpd_obj = None ):
667- mpd = mpd_obj or await self .parse_mpd
668- if not mpd :
710+ # We ignore mpd_obj because our helper properties are cached
711+ selected_repr = await self .selected_video_representation
712+ if not selected_repr :
669713 return None
670-
671- allowed = quality .get_allowed_qualities ()
672- for period in mpd .periods :
673- for adapt_set in filter (
674- lambda x : x .mime_type == "video/mp4" , period .adaptation_sets
675- ):
676- representations = sorted (
677- adapt_set .representations , key = lambda x : x .height , reverse = True
678- )
679- for qual in allowed :
680- if qual .lower () == "source" :
681- return str (representations [0 ].height )
682- for r in representations :
683- if str (r .height ) == qual :
684- return str (r .height )
685- return str (representations [0 ].height ) # Fallback to best
714+ return str (selected_repr .height )
686715
687716 async def _get_final_filename_async (self ):
688717 filename = self .filename or str (self .id )
@@ -697,4 +726,4 @@ async def _get_final_filename_async(self):
697726 except Exception as e :
698727 log .error (f"Error creating final filename: { e } " )
699728
700- return filename
729+ return filename
0 commit comments