2020from .parser import MPD_Parser
2121from .segments import MPD_Segments
2222from .decrypt import decrypt_with_mp4decrypt
23- from .cdm_helpher import get_widevine_keys
23+ from .cdm_helpher import get_widevine_keys , map_keys_to_representations
2424
2525
2626# FFmpeg functions
3232DOWNLOAD_SPECIFIC_SUBTITLE = config_manager .get_list ('M3U8_DOWNLOAD' , 'specific_list_subtitles' )
3333MERGE_SUBTITLE = config_manager .get_bool ('M3U8_DOWNLOAD' , 'merge_subs' )
3434CLEANUP_TMP = config_manager .get_bool ('M3U8_DOWNLOAD' , 'cleanup_tmp_folder' )
35- RETRY_LIMIT = config_manager .get_int ('REQUESTS' , 'max_retry' )
3635EXTENSION_OUTPUT = config_manager .get ("M3U8_CONVERSION" , "extension" )
3736
3837
@@ -160,7 +159,7 @@ def get_representation_by_type(self, typ):
160159
161160 def download_subtitles (self ) -> bool :
162161 """
163- Download subtitle files based on parser's selected subtitles with retry mechanism .
162+ Download subtitle files based on parser's selected subtitles.
164163 Returns True if successful or if no subtitles to download, False on critical error.
165164 """
166165 if not self .selected_subs or self .mpd_sub_list is None :
@@ -193,12 +192,11 @@ def download_subtitles(self) -> bool:
193192
194193 def download_and_decrypt (self , custom_headers = None , query_params = None , key = None ) -> bool :
195194 """
196- Download and decrypt video/audio streams. Skips download if file already exists .
195+ Download and decrypt video/audio streams using automatic key mapping based on default_KID .
197196
198197 Args:
199198 - custom_headers (dict): Optional HTTP headers for the license request.
200199 - query_params (dict): Optional query parameters to append to the license URL.
201- - license_data (str/bytes): Optional raw license data to bypass HTTP request.
202200 - key (str): Optional raw license data to bypass HTTP request.
203201 """
204202 if self .file_already_exists :
@@ -228,12 +226,26 @@ def download_and_decrypt(self, custom_headers=None, query_params=None, key=None)
228226 console .print ("[red]No keys found, cannot proceed with download." )
229227 return False
230228
229+ # Map keys to representations based on default_KID
230+ key_mapping = map_keys_to_representations (keys , self .parser .representations )
231+
232+ if not key_mapping :
233+ console .print ("[red]Could not map any keys to representations." )
234+ return False
235+
231236 # Download subtitles
232237 self .download_subtitles ()
233238
234- # Download the video to get segment count
239+ # Download and decrypt video
235240 video_rep = self .get_representation_by_type ("video" )
236241 if video_rep :
242+ video_key_info = key_mapping .get ("video" )
243+ if not video_key_info :
244+ self .error = "No key found for video representation"
245+ return False
246+
247+ console .log (f"[cyan]Using video key: [red]{ video_key_info ['kid' ]} [cyan]for representation [yellow]{ video_key_info .get ('representation_id' )} " )
248+
237249 video_downloader = MPD_Segments (tmp_folder = self .encrypted_dir , representation = video_rep , pssh = self .parser .pssh , custom_headers = custom_headers )
238250 encrypted_path = video_downloader .get_concat_path (self .encrypted_dir )
239251
@@ -266,33 +278,28 @@ def download_and_decrypt(self, custom_headers=None, query_params=None, key=None)
266278 self .current_downloader = None
267279 self .current_download_type = None
268280
269- # Try decryption with multiple keys if the first one fails
270- decrypted_path = os .path .join (self .decrypted_dir , f"video.{ extension_output } " )
271- result_path = None
272-
273- for i , key_info in enumerate (keys ):
274- KID = key_info ['kid' ]
275- KEY = key_info ['key' ]
276- console .log (f"[cyan]Trying video decryption with key: [red]{ KID } " )
277- result_path = decrypt_with_mp4decrypt ("Video" , encrypted_path , KID , KEY , output_path = decrypted_path )
278-
279- if result_path :
280- working_key = key_info # Store the working key for audio decryption
281- break
282- else :
283- console .log (f"[yellow]✗ Video decryption failed with key { i + 1 } " )
281+ # Decrypt video using the mapped key
282+ decrypted_path = os .path .join (self .decrypted_dir , f"video.{ EXTENSION_OUTPUT } " )
283+ result_path = decrypt_with_mp4decrypt ("Video" , encrypted_path , video_key_info ['kid' ], video_key_info ['key' ], output_path = decrypted_path )
284284
285285 if not result_path :
286- self .error = "Video decryption failed with all available keys "
286+ self .error = f "Video decryption failed with key { video_key_info [ 'kid' ] } "
287287 return False
288288
289289 else :
290290 self .error = "No video found"
291291 return False
292292
293- # Now download audio with segment limiting
293+ # Download and decrypt audio
294294 audio_rep = self .get_representation_by_type ("audio" )
295295 if audio_rep :
296+ audio_key_info = key_mapping .get ("audio" )
297+ if not audio_key_info :
298+ self .error = "No key found for audio representation"
299+ return False
300+
301+ console .log (f"[cyan]Using audio key: [red]{ audio_key_info ['kid' ]} [cyan]for representation [yellow]{ audio_key_info .get ('representation_id' )} " )
302+
296303 audio_language = audio_rep .get ('language' , 'Unknown' )
297304 audio_downloader = MPD_Segments (tmp_folder = self .encrypted_dir , representation = audio_rep , pssh = self .parser .pssh , limit_segments = video_segments_count if video_segments_count > 0 else None , custom_headers = custom_headers )
298305 encrypted_path = audio_downloader .get_concat_path (self .encrypted_dir )
@@ -325,32 +332,13 @@ def download_and_decrypt(self, custom_headers=None, query_params=None, key=None)
325332 self .current_downloader = None
326333 self .current_download_type = None
327334
328- # Decrypt audio
329- decrypted_path = os .path .join (self .decrypted_dir , f"audio.{ extension_output } " )
330- result_path = decrypt_with_mp4decrypt (
331- f"Audio { audio_language } " , encrypted_path , working_key ['kid' ], working_key ['key' ], output_path = decrypted_path
332- )
335+ # Decrypt audio using the mapped key
336+ decrypted_path = os .path .join (self .decrypted_dir , f"audio.{ EXTENSION_OUTPUT } " )
337+ result_path = decrypt_with_mp4decrypt (f"Audio { audio_language } " , encrypted_path , audio_key_info ['kid' ], audio_key_info ['key' ], output_path = decrypted_path )
333338
334339 if not result_path :
335- # If the video key doesn't work for audio, try other keys
336- console .log ("[yellow]Video key failed for audio, trying other keys..." )
337-
338- for i , key_info in enumerate (keys ):
339- if key_info ['kid' ] == working_key ['kid' ]:
340- continue # Skip the already tried key
341-
342- console .log (f"[cyan]Trying audio decryption with alternative key: { key_info ['kid' ]} " )
343- result_path = decrypt_with_mp4decrypt (f"Audio { audio_language } " , encrypted_path , key_info ['kid' ], key_info ['key' ], output_path = decrypted_path )
344-
345- if result_path :
346- console .log ("[green]Audio decryption successful with alternative key" )
347- break
348- else :
349- console .log ("[yellow]Audio decryption failed with alternative key" )
350-
351- if not result_path :
352- self .error = "Audio decryption failed with all available keys"
353- return False
340+ self .error = f"Audio decryption failed with key { audio_key_info ['kid' ]} "
341+ return False
354342
355343 else :
356344 self .error = "No audio found"
@@ -415,7 +403,7 @@ def download_segments(self, clear=False):
415403 self .current_download_type = None
416404
417405 # NO DECRYPTION: just copy/move to decrypted folder
418- decrypted_path = os .path .join (self .decrypted_dir , f"video.{ extension_output } " )
406+ decrypted_path = os .path .join (self .decrypted_dir , f"video.{ EXTENSION_OUTPUT } " )
419407 if os .path .exists (encrypted_path ) and not os .path .exists (decrypted_path ):
420408 shutil .copy2 (encrypted_path , decrypted_path )
421409
@@ -459,7 +447,7 @@ def download_segments(self, clear=False):
459447 self .current_download_type = None
460448
461449 # NO DECRYPTION: just copy/move to decrypted folder
462- decrypted_path = os .path .join (self .decrypted_dir , f"audio.{ extension_output } " )
450+ decrypted_path = os .path .join (self .decrypted_dir , f"audio.{ EXTENSION_OUTPUT } " )
463451 if os .path .exists (encrypted_path ) and not os .path .exists (decrypted_path ):
464452 shutil .copy2 (encrypted_path , decrypted_path )
465453
@@ -480,8 +468,8 @@ def finalize_output(self):
480468 return output_file
481469
482470 # Definition of decrypted files
483- video_file = os .path .join (self .decrypted_dir , f"video.{ extension_output } " )
484- audio_file = os .path .join (self .decrypted_dir , f"audio.{ extension_output } " )
471+ video_file = os .path .join (self .decrypted_dir , f"video.{ EXTENSION_OUTPUT } " )
472+ audio_file = os .path .join (self .decrypted_dir , f"audio.{ EXTENSION_OUTPUT } " )
485473 output_file = self .original_output_path
486474
487475 # Set the output file path for status tracking
0 commit comments