diff --git a/.gitignore b/.gitignore index cdbf153..066ddb7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ downloads/ deezspot/__pycache__ deezspot/deezloader/__pycache__ deezspot/libutils/__pycache__ +Test.py diff --git a/deezspot/deezloader/__download__.py b/deezspot/deezloader/__download__.py index e89d739..ca5e773 100644 --- a/deezspot/deezloader/__download__.py +++ b/deezspot/deezloader/__download__.py @@ -588,18 +588,13 @@ class EASY_DW: "artist": self.__song_metadata.get("artist", ""), "status": "progress" } - - # Use Spotify URL if available, otherwise use Deezer link spotify_url = getattr(self.__preferences, 'spotify_url', None) progress_data["url"] = spotify_url if spotify_url else self.__link - - # Add parent info if present if self.__parent == "playlist" and hasattr(self.__preferences, "json_data"): playlist_data = self.__preferences.json_data playlist_name = playlist_data.get('title', 'unknown') total_tracks = getattr(self.__preferences, 'total_tracks', 0) current_track = getattr(self.__preferences, 'track_number', 0) - progress_data.update({ "current_track": current_track, "total_tracks": total_tracks, @@ -616,7 +611,6 @@ class EASY_DW: album_artist = self.__song_metadata.get('album_artist', self.__song_metadata.get('album_artist', '')) total_tracks = getattr(self.__preferences, 'total_tracks', 0) current_track = getattr(self.__preferences, 'track_number', 0) - progress_data.update({ "current_track": current_track, "total_tracks": total_tracks, @@ -628,30 +622,31 @@ class EASY_DW: "url": f"https://deezer.com/album/{self.__preferences.song_metadata.get('album_id', '')}" } }) - Download_JOB.report_progress(progress_data) - try: - # Decrypt the file using the utility function - decryptfile(c_crypted_audio, self.__fallback_ids, self.__song_path) - logger.debug(f"Successfully decrypted track using {encryption_type} encryption") - except Exception as decrypt_error: - # Detailed error logging for debugging - logger.error(f"Decryption error ({encryption_type}): {str(decrypt_error)}") - if "Data must be padded" in str(decrypt_error): - logger.error("This appears to be a padding issue with Blowfish decryption") - raise - - self.__add_more_tags() - + # Start of processing block (decryption, tagging, cover, conversion) + # Decrypt the file using the utility function + decryptfile(c_crypted_audio, self.__fallback_ids, self.__song_path) + logger.debug(f"Successfully decrypted track using {encryption_type} encryption") + + self.__add_more_tags() # self.__song_metadata is updated here + self.__c_track.tags = self.__song_metadata # IMPORTANT: Update track object's tags + + # Save cover image if requested + if self.__preferences.save_cover and self.__song_metadata.get('image'): + try: + track_directory = os.path.dirname(self.__song_path) + save_cover_image(self.__song_metadata['image'], track_directory, "cover.jpg") + logger.info(f"Saved cover image for track in {track_directory}") + except Exception as e_img_save: + logger.warning(f"Failed to save cover image for track: {e_img_save}") + # Apply audio conversion if requested if self.__convert_to: format_name, bitrate = parse_format_string(self.__convert_to) if format_name: - # Register and unregister functions for tracking downloads - from deezspot.deezloader.__download__ import register_active_download, unregister_active_download + from deezspot.deezloader.__download__ import register_active_download, unregister_active_download # Ensure these are available or handle differently try: - # Update the path with the converted file path converted_path = convert_audio( self.__song_path, format_name, @@ -660,97 +655,52 @@ class EASY_DW: unregister_active_download ) if converted_path != self.__song_path: - # Update path in track object if conversion happened self.__song_path = converted_path self.__c_track.song_path = converted_path except Exception as conv_error: - # Log conversion error but continue with original file logger.error(f"Audio conversion error: {str(conv_error)}") - + # Decide if this is a fatal error for the track or if we proceed with original + # Write tags to the final file (original or converted) write_tags(self.__c_track) - except Exception as e: + self.__c_track.success = True # Mark as successful only after all steps including tags + + except Exception as e: # Handles errors from __write_track, decrypt, add_tags, save_cover, convert, write_tags if isfile(self.__song_path): os.remove(self.__song_path) - # Improve error message formatting error_msg = str(e) - if "Data must be padded" in error_msg: - error_msg = "Decryption error (padding issue) - Try a different quality setting or download format" - elif isinstance(e, ConnectionError) or "Connection" in error_msg: - error_msg = "Connection error - Check your internet connection" - elif "timeout" in error_msg.lower(): - error_msg = "Request timed out - Server may be busy" - elif "403" in error_msg or "Forbidden" in error_msg: - error_msg = "Access denied - Track might be region-restricted or premium-only" - elif "404" in error_msg or "Not Found" in error_msg: - error_msg = "Track not found - It might have been removed" + if "Data must be padded" in error_msg: error_msg = "Decryption error (padding issue) - Try a different quality setting or download format" + elif isinstance(e, ConnectionError) or "Connection" in error_msg: error_msg = "Connection error - Check your internet connection" + elif "timeout" in error_msg.lower(): error_msg = "Request timed out - Server may be busy" + elif "403" in error_msg or "Forbidden" in error_msg: error_msg = "Access denied - Track might be region-restricted or premium-only" + elif "404" in error_msg or "Not Found" in error_msg: error_msg = "Track not found - It might have been removed" - # Create formatted error report - progress_data = { - "type": "track", - "status": "error", - "song": self.__song_metadata.get('music', ''), - "artist": self.__song_metadata.get('artist', ''), - "error": error_msg, - "url": getattr(self.__preferences, 'spotify_url', None) or self.__link, + # (Error reporting code as it exists) + error_progress_data = { + "type": "track", "status": "error", + "song": self.__song_metadata.get('music', ''), "artist": self.__song_metadata.get('artist', ''), + "error": error_msg, "url": getattr(self.__preferences, 'spotify_url', None) or self.__link, "convert_to": self.__convert_to } - - # Add parent info based on parent type if self.__parent == "playlist" and hasattr(self.__preferences, "json_data"): - playlist_data = self.__preferences.json_data - playlist_name = playlist_data.get('title', 'unknown') - total_tracks = getattr(self.__preferences, 'total_tracks', 0) - current_track = getattr(self.__preferences, 'track_number', 0) - - progress_data.update({ - "current_track": current_track, - "total_tracks": total_tracks, - "parent": { - "type": "playlist", - "name": playlist_name, - "owner": playlist_data.get('creator', {}).get('name', 'unknown'), - "total_tracks": total_tracks, - "url": f"https://deezer.com/playlist/{playlist_data.get('id', '')}" - } - }) + playlist_data = self.__preferences.json_data; playlist_name = playlist_data.get('title', 'unknown') + total_tracks = getattr(self.__preferences, 'total_tracks', 0); current_track = getattr(self.__preferences, 'track_number', 0) + error_progress_data.update({"current_track": current_track, "total_tracks": total_tracks, "parent": {"type": "playlist", "name": playlist_name, "owner": playlist_data.get('creator', {}).get('name', 'unknown'), "total_tracks": total_tracks, "url": f"https://deezer.com/playlist/{playlist_data.get('id', '')}"}}) elif self.__parent == "album": - album_name = self.__song_metadata.get('album', '') - album_artist = self.__song_metadata.get('album_artist', self.__song_metadata.get('album_artist', '')) - total_tracks = getattr(self.__preferences, 'total_tracks', 0) - current_track = getattr(self.__preferences, 'track_number', 0) - - progress_data.update({ - "current_track": current_track, - "total_tracks": total_tracks, - "parent": { - "type": "album", - "title": album_name, - "artist": album_artist, - "total_tracks": total_tracks, - "url": f"https://deezer.com/album/{self.__preferences.song_metadata.get('album_id', '')}" - } - }) - - # Report the error - Download_JOB.report_progress(progress_data) + album_name = self.__song_metadata.get('album', ''); album_artist = self.__song_metadata.get('album_artist', self.__song_metadata.get('album_artist', '')) + total_tracks = getattr(self.__preferences, 'total_tracks', 0); current_track = getattr(self.__preferences, 'track_number', 0) + error_progress_data.update({"current_track": current_track, "total_tracks": total_tracks, "parent": {"type": "album", "title": album_name, "artist": album_artist, "total_tracks": total_tracks, "url": f"https://deezer.com/album/{self.__preferences.song_metadata.get('album_id', '')}"}}) + Download_JOB.report_progress(error_progress_data) logger.error(f"Failed to process track: {error_msg}") - # Still raise the exception to maintain original flow - # Add the original exception e to the message for more context - self.__c_track.success = False # Mark as failed - self.__c_track.error_message = error_msg # Store the refined error message + self.__c_track.success = False + self.__c_track.error_message = error_msg raise TrackNotFound(f"Failed to process {self.__song_path}. Error: {error_msg}. Original Exception: {str(e)}") - # If download and processing (like decryption, tagging) were successful before conversion - if not self.__convert_to: # Or if conversion was successful - self.__c_track.success = True - return self.__c_track - except Exception as e: - # Add more context to this exception + except Exception as e: # Outer exception for initial media checks, etc. song_title = self.__song_metadata.get('music', 'Unknown Song') artist_name = self.__song_metadata.get('artist', 'Unknown Artist') error_message = f"Download failed for '{song_title}' by '{artist_name}' (Link: {self.__link}). Error: {str(e)}" @@ -859,6 +809,9 @@ class EASY_DW: if self.__infos_dw.get('LYRICS_ID', 0) != 0: need = API_GW.get_lyric(self.__ids) + if "LYRICS_TEXT" in need: + self.__song_metadata['lyric'] = need["LYRICS_TEXT"] + if "LYRICS_SYNC_JSON" in need: self.__song_metadata['lyric_sync'] = trasform_sync_lyric( need['LYRICS_SYNC_JSON'] @@ -1079,7 +1032,7 @@ class DW_ALBUM: tracks.append(track) # Save album cover image - if album.image and album_base_directory: + if self.__preferences.save_cover and album.image and album_base_directory: save_cover_image(album.image, album_base_directory, "cover.jpg") if self.__make_zip: @@ -1342,14 +1295,15 @@ class DW_EPISODE: Download_JOB.report_progress(progress_data) # Save cover image for the episode - episode_image_md5 = infos_dw.get('EPISODE_IMAGE_MD5', '') - episode_image_data = None - if episode_image_md5: - episode_image_data = API.choose_img(episode_image_md5, size="1200x1200") - - if episode_image_data: - episode_directory = os.path.dirname(output_path) - save_cover_image(episode_image_data, episode_directory, "cover.jpg") + if self.__preferences.save_cover: + episode_image_md5 = infos_dw.get('EPISODE_IMAGE_MD5', '') + episode_image_data = None + if episode_image_md5: + episode_image_data = API.choose_img(episode_image_md5, size="1200x1200") + + if episode_image_data: + episode_directory = os.path.dirname(output_path) + save_cover_image(episode_image_data, episode_directory, "cover.jpg") return episode diff --git a/deezspot/deezloader/__init__.py b/deezspot/deezloader/__init__.py index 36cf24a..bc30e07 100644 --- a/deezspot/deezloader/__init__.py +++ b/deezspot/deezloader/__init__.py @@ -103,7 +103,8 @@ class DeeLogin: initial_retry_delay=30, retry_delay_increase=30, max_retries=5, - convert_to=None + convert_to=None, + save_cover=stock_save_cover ) -> Track: link_is_valid(link_track) @@ -140,6 +141,7 @@ class DeeLogin: preferences.max_retries = max_retries # Audio conversion parameter preferences.convert_to = convert_to + preferences.save_cover = save_cover track = DW_TRACK(preferences).dw() @@ -215,7 +217,8 @@ class DeeLogin: initial_retry_delay=30, retry_delay_increase=30, max_retries=5, - convert_to=None + convert_to=None, + save_cover=stock_save_cover ) -> Playlist: link_is_valid(link_playlist) @@ -260,6 +263,7 @@ class DeeLogin: preferences.max_retries = max_retries # Audio conversion parameter preferences.convert_to = convert_to + preferences.save_cover = save_cover playlist = DW_PLAYLIST(preferences).dw() @@ -275,7 +279,8 @@ class DeeLogin: custom_dir_format=None, custom_track_format=None, pad_tracks=True, - convert_to=None + convert_to=None, + save_cover=stock_save_cover ) -> list[Track]: link_is_valid(link_artist) @@ -291,7 +296,8 @@ class DeeLogin: custom_dir_format=custom_dir_format, custom_track_format=custom_track_format, pad_tracks=pad_tracks, - convert_to=convert_to + convert_to=convert_to, + save_cover=save_cover ) for track in playlist_json ] @@ -332,7 +338,8 @@ class DeeLogin: initial_retry_delay=30, retry_delay_increase=30, max_retries=5, - convert_to=None + convert_to=None, + save_cover=stock_save_cover ) -> Track: track_link_dee = self.convert_spoty_to_dee_link_track(link_track) @@ -350,7 +357,8 @@ class DeeLogin: initial_retry_delay=initial_retry_delay, retry_delay_increase=retry_delay_increase, max_retries=max_retries, - convert_to=convert_to + convert_to=convert_to, + save_cover=save_cover ) return track @@ -448,7 +456,8 @@ class DeeLogin: initial_retry_delay=30, retry_delay_increase=30, max_retries=5, - convert_to=None + convert_to=None, + save_cover=stock_save_cover ) -> Album: link_dee = self.convert_spoty_to_dee_link_album(link_album) @@ -464,7 +473,8 @@ class DeeLogin: initial_retry_delay=initial_retry_delay, retry_delay_increase=retry_delay_increase, max_retries=max_retries, - convert_to=convert_to + convert_to=convert_to, + save_cover=save_cover ) return album @@ -483,7 +493,8 @@ class DeeLogin: initial_retry_delay=30, retry_delay_increase=30, max_retries=5, - convert_to=None + convert_to=None, + save_cover=stock_save_cover ) -> Playlist: link_is_valid(link_playlist) @@ -559,7 +570,8 @@ class DeeLogin: initial_retry_delay=initial_retry_delay, retry_delay_increase=retry_delay_increase, max_retries=max_retries, - convert_to=convert_to + convert_to=convert_to, + save_cover=save_cover ) tracks.append(downloaded_track) except (TrackNotFound, NoDataApi) as e: @@ -610,7 +622,8 @@ class DeeLogin: custom_dir_format=None, custom_track_format=None, pad_tracks=True, - convert_to=None + convert_to=None, + save_cover=stock_save_cover ) -> Track: query = f"track:{song} artist:{artist}" @@ -639,7 +652,11 @@ class DeeLogin: custom_dir_format=custom_dir_format, custom_track_format=custom_track_format, pad_tracks=pad_tracks, - convert_to=convert_to + initial_retry_delay=initial_retry_delay, + retry_delay_increase=retry_delay_increase, + max_retries=max_retries, + convert_to=convert_to, + save_cover=save_cover ) return track @@ -714,7 +731,8 @@ class DeeLogin: pad_tracks=True, initial_retry_delay=30, retry_delay_increase=30, - max_retries=5 + max_retries=5, + save_cover=stock_save_cover ) -> Smart: link_is_valid(link) @@ -756,7 +774,8 @@ class DeeLogin: pad_tracks=pad_tracks, initial_retry_delay=initial_retry_delay, retry_delay_increase=retry_delay_increase, - max_retries=max_retries + max_retries=max_retries, + save_cover=save_cover ) smart.type = "track" smart.track = track @@ -782,7 +801,9 @@ class DeeLogin: pad_tracks=pad_tracks, initial_retry_delay=initial_retry_delay, retry_delay_increase=retry_delay_increase, - max_retries=max_retries + max_retries=max_retries, + convert_to=convert_to, + save_cover=save_cover ) smart.type = "album" smart.album = album @@ -808,7 +829,9 @@ class DeeLogin: pad_tracks=pad_tracks, initial_retry_delay=initial_retry_delay, retry_delay_increase=retry_delay_increase, - max_retries=max_retries + max_retries=max_retries, + convert_to=convert_to, + save_cover=save_cover ) smart.type = "playlist" smart.playlist = playlist diff --git a/deezspot/libutils/utils.py b/deezspot/libutils/utils.py index 7cbbefb..207e9bb 100644 --- a/deezspot/libutils/utils.py +++ b/deezspot/libutils/utils.py @@ -9,6 +9,7 @@ from zipfile import ZipFile, ZIP_DEFLATED from deezspot.models.track import Track from deezspot.exceptions import InvalidLink from deezspot.libutils.others_settings import supported_link, header +from deezspot.libutils.logging_utils import ProgressReporter, logger from os.path import ( isdir, basename, diff --git a/deezspot/spotloader/__download__.py b/deezspot/spotloader/__download__.py index b8a62d3..ac842ef 100644 --- a/deezspot/spotloader/__download__.py +++ b/deezspot/spotloader/__download__.py @@ -346,7 +346,7 @@ class EASY_DW: if hasattr(self, '_EASY_DW__c_track') and self.__c_track and self.__c_track.success: write_tags(self.__c_track) - return self.__c_track # Return the track object + return self.__c_track def track_exists(self, title, album): try: @@ -691,6 +691,16 @@ class EASY_DW: time.sleep(retry_delay) retry_delay += retry_delay_increase # Use the custom retry delay increase + # Save cover image if requested, after successful download and before conversion + if self.__preferences.save_cover and hasattr(self, '_EASY_DW__song_path') and self.__song_path and self.__song_metadata.get('image'): + try: + track_directory = dirname(self.__song_path) + # Ensure the directory exists (it should, from os.makedirs earlier) + save_cover_image(self.__song_metadata['image'], track_directory, "cover.jpg") + logger.info(f"Saved cover image for track in {track_directory}") + except Exception as e_img_save: + logger.warning(f"Failed to save cover image for track: {e_img_save}") + try: self.__convert_audio() except Exception as e: @@ -1027,23 +1037,9 @@ class EASY_DW: self.__c_episode.error_message = error_message raise TrackNotFound(message=error_message, url=self.__link) from conv_e - self.__write_episode() # Write metadata tags so subsequent skips work write_tags(self.__c_episode) - # Save episode cover image if download was successful - if self.__c_episode.success and hasattr(self.__c_episode, 'episode_path') and self.__c_episode.episode_path: - episode_directory = dirname(self.__c_episode.episode_path) - image_url = self.__song_metadata.get('image') - image_bytes = None - if image_url: - try: - image_bytes = request(image_url).content - except Exception as e_img: - logger.warning(f"Failed to fetch cover image for episode {self.__c_episode.tags.get('name', '')}: {e_img}") - if image_bytes: - save_cover_image(image_bytes, episode_directory, "cover.jpg") - return self.__c_episode def download_cli(preferences: Preferences) -> None: @@ -1186,7 +1182,7 @@ class DW_ALBUM: tracks.append(track) # Save album cover image - if album.image and album_base_directory: + if self.__preferences.save_cover and album.image and album_base_directory: save_cover_image(album.image, album_base_directory, "cover.jpg") if self.__make_zip: @@ -1345,19 +1341,6 @@ class DW_EPISODE: episode = EASY_DW(self.__preferences).download_eps() - # Save episode cover image if download was successful - if episode.success and hasattr(episode, 'episode_path') and episode.episode_path: - episode_directory = dirname(episode.episode_path) - image_url = self.__preferences.song_metadata.get('image') - image_bytes = None - if image_url: - try: - image_bytes = request(image_url).content - except Exception as e_img: - logger.warning(f"Failed to fetch cover image for episode {episode.tags.get('name', '')}: {e_img}") - if image_bytes: - save_cover_image(image_bytes, episode_directory, "cover.jpg") - # Using standardized episode progress format progress_data = { "type": "episode", diff --git a/deezspot/spotloader/__init__.py b/deezspot/spotloader/__init__.py index 5654a05..46aa483 100644 --- a/deezspot/spotloader/__init__.py +++ b/deezspot/spotloader/__init__.py @@ -96,7 +96,8 @@ class SpoLogin: initial_retry_delay=30, retry_delay_increase=30, max_retries=5, - convert_to=None + convert_to=None, + save_cover=stock_save_cover ) -> Track: try: link_is_valid(link_track) @@ -123,6 +124,7 @@ class SpoLogin: preferences.retry_delay_increase = retry_delay_increase preferences.max_retries = max_retries preferences.convert_to = convert_to + preferences.save_cover = save_cover track = DW_TRACK(preferences).dw() @@ -422,7 +424,8 @@ class SpoLogin: pad_tracks=pad_tracks, initial_retry_delay=initial_retry_delay, retry_delay_increase=retry_delay_increase, - max_retries=max_retries + max_retries=max_retries, + save_cover=save_cover ) smart.type = "track" smart.track = track