From 4031c2ff969105eb8c992c5e451301d450a1712c Mon Sep 17 00:00:00 2001 From: Xoconoch Date: Sun, 10 Aug 2025 19:52:41 -0600 Subject: [PATCH] Improve recursive_quality behaviour --- deezspot/deezloader/__download__.py | 108 ++++++++++++++++++++-------- deezspot/deezloader/deegw_api.py | 53 ++++++++------ deezspot/exceptions.py | 2 +- 3 files changed, 112 insertions(+), 51 deletions(-) diff --git a/deezspot/deezloader/__download__.py b/deezspot/deezloader/__download__.py index fea9288..b1d831a 100644 --- a/deezspot/deezloader/__download__.py +++ b/deezspot/deezloader/__download__.py @@ -187,15 +187,15 @@ class Download_JOB: # Post-process each returned media in the chunk for idx in range(len(chunk_medias)): if "errors" in chunk_medias[idx]: - c_media_json = cls.__get_url(non_episode_tracks[len(non_episode_medias) + idx], quality_download) - chunk_medias[idx] = c_media_json + # Do not fallback to legacy URL; mark as unavailable for this quality + chunk_medias[idx] = {"media": []} else: if not chunk_medias[idx]['media']: - c_media_json = cls.__get_url(non_episode_tracks[len(non_episode_medias) + idx], quality_download) - chunk_medias[idx] = c_media_json + # Do not fallback to legacy URL; mark as unavailable + chunk_medias[idx] = {"media": []} elif len(chunk_medias[idx]['media'][0]['sources']) == 1: - c_media_json = cls.__get_url(non_episode_tracks[len(non_episode_medias) + idx], quality_download) - chunk_medias[idx] = c_media_json + # Keep single-source media as-is; do not fallback + pass non_episode_medias.extend(chunk_medias) except NoRightOnMedia: for c_track in tokens_chunk: @@ -585,7 +585,9 @@ class EASY_DW: if filesize == 0: song = self.__song_metadata['music'] artist = self.__song_metadata['artist'] - # Switch quality settings to MP3_320. + if not self.__recursive_quality: + raise QualityNotFound(f"FLAC not available for {song} - {artist} and recursive quality search is disabled.") + # Fallback to MP3_320 if recursive_quality is enabled self.__quality_download = 'MP3_320' self.__file_format = '.mp3' self.__song_path = self.__song_path.rsplit('.', 1)[0] + '.mp3' @@ -598,51 +600,97 @@ class EASY_DW: # Continue with the normal download process. try: media_list = self.__infos_dw['media_url']['media'] - song_link = media_list[0]['sources'][0]['url'] - try: - crypted_audio = API_GW.song_exist(song_link) - except TrackNotFound: + # Try all sources for the requested quality before attempting any fallback + crypted_audio = None + last_error = None + for media_entry in media_list: + sources = media_entry.get('sources') or [] + for src in sources: + song_link = src.get('url') + if not song_link: + continue + try: + crypted_audio = API_GW.song_exist(song_link) + if crypted_audio: + last_error = None + break + except Exception as e_try_src: + last_error = e_try_src + continue + if crypted_audio: + break + + if not crypted_audio: song = self.__song_metadata['music'] artist = self.__song_metadata['artist'] if self.__file_format == '.flac': + if not self.__recursive_quality: + raise QualityNotFound(f"FLAC not available for {song} - {artist} and recursive quality search is disabled.") logger.warning(f"\n⚠ {song} - {artist} is not available in FLAC format. Trying MP3...") self.__quality_download = 'MP3_320' self.__file_format = '.mp3' self.__song_path = self.__song_path.rsplit('.', 1)[0] + '.mp3' - media = Download_JOB.check_sources( - [self.__infos_dw], 'MP3_320' - ) + media = Download_JOB.check_sources([self.__infos_dw], 'MP3_320') if media: self.__infos_dw['media_url'] = media[0] - song_link = media[0]['media'][0]['sources'][0]['url'] - crypted_audio = API_GW.song_exist(song_link) + media_list = self.__infos_dw['media_url']['media'] + # Try all sources for fallback quality + for media_entry in media_list: + sources = media_entry.get('sources') or [] + for src in sources: + song_link = src.get('url') + if not song_link: + continue + try: + crypted_audio = API_GW.song_exist(song_link) + if crypted_audio: + last_error = None + break + except Exception as e_try_src2: + last_error = e_try_src2 + continue + if crypted_audio: + break + if not crypted_audio: + raise TrackNotFound(f"Track {song} - {artist} not available in MP3 after FLAC attempt failed (all sources unreachable). Last error: {last_error}") else: raise TrackNotFound(f"Track {song} - {artist} not available in MP3 after FLAC attempt failed (media not found for MP3).") else: if not self.__recursive_quality: - # msg was not defined, provide a more specific message raise QualityNotFound(f"Quality {self.__quality_download} not found for {song} - {artist} and recursive quality search is disabled.") for c_quality in qualities: if self.__quality_download == c_quality: continue - media = Download_JOB.check_sources( - [self.__infos_dw], c_quality - ) + media = Download_JOB.check_sources([self.__infos_dw], c_quality) if media: self.__infos_dw['media_url'] = media[0] - song_link = media[0]['media'][0]['sources'][0]['url'] - try: - crypted_audio = API_GW.song_exist(song_link) - self.__c_quality = qualities[c_quality] - self.__set_quality() - break - except TrackNotFound: - if c_quality == "MP3_128": - raise TrackNotFound(f"Error with {song} - {artist}. All available qualities failed, last attempt was {c_quality}. Link: {self.__link}") - continue + media_list = self.__infos_dw['media_url']['media'] + # Try all sources for alternative quality + for media_entry in media_list: + sources = media_entry.get('sources') or [] + for src in sources: + song_link = src.get('url') + if not song_link: + continue + try: + crypted_audio = API_GW.song_exist(song_link) + if crypted_audio: + self.__c_quality = qualities[c_quality] + self.__set_quality() + last_error = None + break + except Exception as e_try_src3: + last_error = e_try_src3 + continue + if crypted_audio: + break + if crypted_audio: + break + if not crypted_audio: + raise TrackNotFound(f"Error with {song} - {artist}. All available qualities failed. Last error: {last_error}. Link: {self.__link}") c_crypted_audio = crypted_audio.iter_content(2048) diff --git a/deezspot/deezloader/deegw_api.py b/deezspot/deezloader/deegw_api.py index c0f9cb9..5cd3f4d 100644 --- a/deezspot/deezloader/deegw_api.py +++ b/deezspot/deezloader/deegw_api.py @@ -13,6 +13,8 @@ from requests import ( post as req_post, ) from deezspot.libutils.logging_utils import logger +import re +from urllib.parse import urlparse, urlunparse class API_GW: @@ -271,28 +273,39 @@ class API_GW: if song_link and 'spreaker.com' in song_link: return req_get(song_link, stream=True) - crypted_audio = req_get(song_link) - - if len(crypted_audio.content) == 0: - raise TrackNotFound - - return crypted_audio + try: + crypted_audio = req_get(song_link, stream=True, timeout=15) + if len(crypted_audio.content) == 0: + raise TrackNotFound + return crypted_audio + except Exception as e: + # DNS fallback across dzcdn proxy hosts (e-cdns-proxy-0..7) + try: + parsed = urlparse(song_link) + host = parsed.netloc + if re.search(r"e-cdns-proxy-\d+\.dzcdn\.net", host): + m = re.search(r"e-cdns-proxy-(\d+)\.dzcdn\.net", host) + original_idx = int(m.group(1)) if m else -1 + for i in range(0, 8): + if i == original_idx: + continue + new_host = re.sub(r"e-cdns-proxy-\d+\.dzcdn\.net", f"e-cdns-proxy-{i}.dzcdn.net", host) + new_url = urlunparse((parsed.scheme, new_host, parsed.path, parsed.params, parsed.query, parsed.fragment)) + try: + alt_resp = req_get(new_url, stream=True, timeout=15) + if len(alt_resp.content) == 0: + continue + return alt_resp + except Exception: + continue + except Exception: + pass + # If all fallbacks failed, re-raise as TrackNotFound/Connection error + raise @classmethod def get_medias_url(cls, tracks_token, quality): - others_qualities = [] - - for c_quality in qualities: - if c_quality == quality: - continue - - c_quality_set = { - "cipher": "BF_CBC_STRIPE", - "format": c_quality - } - - others_qualities.append(c_quality_set) - + # Only request the specific desired quality to avoid unexpected fallbacks json_data = { "license_token": cls.__license_token, "media": [ @@ -303,7 +316,7 @@ class API_GW: "cipher": "BF_CBC_STRIPE", "format": quality } - ] + others_qualities + ] } ], "track_tokens": tracks_token diff --git a/deezspot/exceptions.py b/deezspot/exceptions.py index 9e7cce5..efed12d 100644 --- a/deezspot/exceptions.py +++ b/deezspot/exceptions.py @@ -38,7 +38,7 @@ class QualityNotFound(Exception): if not msg: self.msg = ( f"The {quality} quality doesn't exist :)\ - \nThe qualities have to be FLAC or MP3_320 or MP3_256 or MP3_128" + \nThe qualities have to be FLAC, MP3_320 or MP3_128" ) else: self.msg = msg