Improve recursive_quality behaviour

This commit is contained in:
Xoconoch
2025-08-10 19:52:41 -06:00
parent 9d63bdc9fb
commit 4031c2ff96
3 changed files with 112 additions and 51 deletions

View File

@@ -187,15 +187,15 @@ class Download_JOB:
# Post-process each returned media in the chunk # Post-process each returned media in the chunk
for idx in range(len(chunk_medias)): for idx in range(len(chunk_medias)):
if "errors" in chunk_medias[idx]: if "errors" in chunk_medias[idx]:
c_media_json = cls.__get_url(non_episode_tracks[len(non_episode_medias) + idx], quality_download) # Do not fallback to legacy URL; mark as unavailable for this quality
chunk_medias[idx] = c_media_json chunk_medias[idx] = {"media": []}
else: else:
if not chunk_medias[idx]['media']: if not chunk_medias[idx]['media']:
c_media_json = cls.__get_url(non_episode_tracks[len(non_episode_medias) + idx], quality_download) # Do not fallback to legacy URL; mark as unavailable
chunk_medias[idx] = c_media_json chunk_medias[idx] = {"media": []}
elif len(chunk_medias[idx]['media'][0]['sources']) == 1: 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) # Keep single-source media as-is; do not fallback
chunk_medias[idx] = c_media_json pass
non_episode_medias.extend(chunk_medias) non_episode_medias.extend(chunk_medias)
except NoRightOnMedia: except NoRightOnMedia:
for c_track in tokens_chunk: for c_track in tokens_chunk:
@@ -585,7 +585,9 @@ class EASY_DW:
if filesize == 0: if filesize == 0:
song = self.__song_metadata['music'] song = self.__song_metadata['music']
artist = self.__song_metadata['artist'] 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.__quality_download = 'MP3_320'
self.__file_format = '.mp3' self.__file_format = '.mp3'
self.__song_path = self.__song_path.rsplit('.', 1)[0] + '.mp3' self.__song_path = self.__song_path.rsplit('.', 1)[0] + '.mp3'
@@ -598,51 +600,97 @@ class EASY_DW:
# Continue with the normal download process. # Continue with the normal download process.
try: try:
media_list = self.__infos_dw['media_url']['media'] media_list = self.__infos_dw['media_url']['media']
song_link = media_list[0]['sources'][0]['url']
# 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: try:
crypted_audio = API_GW.song_exist(song_link) crypted_audio = API_GW.song_exist(song_link)
except TrackNotFound: 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'] song = self.__song_metadata['music']
artist = self.__song_metadata['artist'] artist = self.__song_metadata['artist']
if self.__file_format == '.flac': 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...") logger.warning(f"\n{song} - {artist} is not available in FLAC format. Trying MP3...")
self.__quality_download = 'MP3_320' self.__quality_download = 'MP3_320'
self.__file_format = '.mp3' self.__file_format = '.mp3'
self.__song_path = self.__song_path.rsplit('.', 1)[0] + '.mp3' self.__song_path = self.__song_path.rsplit('.', 1)[0] + '.mp3'
media = Download_JOB.check_sources( media = Download_JOB.check_sources([self.__infos_dw], 'MP3_320')
[self.__infos_dw], 'MP3_320'
)
if media: if media:
self.__infos_dw['media_url'] = media[0] self.__infos_dw['media_url'] = media[0]
song_link = media[0]['media'][0]['sources'][0]['url'] 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) 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: else:
raise TrackNotFound(f"Track {song} - {artist} not available in MP3 after FLAC attempt failed (media not found for MP3).") raise TrackNotFound(f"Track {song} - {artist} not available in MP3 after FLAC attempt failed (media not found for MP3).")
else: else:
if not self.__recursive_quality: 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.") raise QualityNotFound(f"Quality {self.__quality_download} not found for {song} - {artist} and recursive quality search is disabled.")
for c_quality in qualities: for c_quality in qualities:
if self.__quality_download == c_quality: if self.__quality_download == c_quality:
continue continue
media = Download_JOB.check_sources( media = Download_JOB.check_sources([self.__infos_dw], c_quality)
[self.__infos_dw], c_quality
)
if media: if media:
self.__infos_dw['media_url'] = media[0] self.__infos_dw['media_url'] = media[0]
song_link = media[0]['media'][0]['sources'][0]['url'] 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: try:
crypted_audio = API_GW.song_exist(song_link) crypted_audio = API_GW.song_exist(song_link)
if crypted_audio:
self.__c_quality = qualities[c_quality] self.__c_quality = qualities[c_quality]
self.__set_quality() self.__set_quality()
last_error = None
break break
except TrackNotFound: except Exception as e_try_src3:
if c_quality == "MP3_128": last_error = e_try_src3
raise TrackNotFound(f"Error with {song} - {artist}. All available qualities failed, last attempt was {c_quality}. Link: {self.__link}")
continue 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) c_crypted_audio = crypted_audio.iter_content(2048)

View File

@@ -13,6 +13,8 @@ from requests import (
post as req_post, post as req_post,
) )
from deezspot.libutils.logging_utils import logger from deezspot.libutils.logging_utils import logger
import re
from urllib.parse import urlparse, urlunparse
class API_GW: class API_GW:
@@ -271,28 +273,39 @@ class API_GW:
if song_link and 'spreaker.com' in song_link: if song_link and 'spreaker.com' in song_link:
return req_get(song_link, stream=True) return req_get(song_link, stream=True)
crypted_audio = req_get(song_link) try:
crypted_audio = req_get(song_link, stream=True, timeout=15)
if len(crypted_audio.content) == 0: if len(crypted_audio.content) == 0:
raise TrackNotFound raise TrackNotFound
return crypted_audio 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 @classmethod
def get_medias_url(cls, tracks_token, quality): def get_medias_url(cls, tracks_token, quality):
others_qualities = [] # Only request the specific desired quality to avoid unexpected fallbacks
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)
json_data = { json_data = {
"license_token": cls.__license_token, "license_token": cls.__license_token,
"media": [ "media": [
@@ -303,7 +316,7 @@ class API_GW:
"cipher": "BF_CBC_STRIPE", "cipher": "BF_CBC_STRIPE",
"format": quality "format": quality
} }
] + others_qualities ]
} }
], ],
"track_tokens": tracks_token "track_tokens": tracks_token

View File

@@ -38,7 +38,7 @@ class QualityNotFound(Exception):
if not msg: if not msg:
self.msg = ( self.msg = (
f"The {quality} quality doesn't exist :)\ 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: else:
self.msg = msg self.msg = msg