Added spotify_metadata support for albums

This commit is contained in:
Xoconoch
2025-08-16 12:31:46 -06:00
parent 4bcdff39a1
commit d81c57e357
3 changed files with 114 additions and 11 deletions

View File

@@ -1,4 +1,3 @@
#!/usr/bin/python3
import os import os
import json import json
import requests import requests
@@ -277,6 +276,32 @@ class EASY_DW:
# Maintain legacy attribute expected elsewhere # Maintain legacy attribute expected elsewhere
self.__song_metadata = self.__song_metadata_dict self.__song_metadata = self.__song_metadata_dict
self.__download_type = "track" self.__download_type = "track"
# Backfill missing album fields when using Spotify minimal track objects
try:
if use_spotify and 'album' not in self.__song_metadata_dict and getattr(preferences, 'spotify_album_obj', None):
spo_album = preferences.spotify_album_obj
# Basic album fields
self.__song_metadata_dict['album'] = getattr(spo_album, 'title', '')
if getattr(spo_album, 'artists', None):
self.__song_metadata_dict['ar_album'] = artist_separator.join([getattr(a, 'name', '') for a in spo_album.artists])
self.__song_metadata_dict['nb_tracks'] = getattr(spo_album, 'total_tracks', 0)
# Total discs if available
if hasattr(spo_album, 'total_discs') and getattr(spo_album, 'total_discs'):
self.__song_metadata_dict['nb_discs'] = getattr(spo_album, 'total_discs')
# Year from release date
from deezspot.libutils.metadata_converter import _format_release_date, _get_best_image_url
self.__song_metadata_dict['year'] = _format_release_date(getattr(spo_album, 'release_date', None), 'spotify')
# IDs
if getattr(spo_album, 'ids', None):
self.__song_metadata_dict['upc'] = getattr(spo_album.ids, 'upc', None)
self.__song_metadata_dict['album_id'] = getattr(spo_album.ids, 'spotify', None)
# Prefer Spotify album image
if getattr(spo_album, 'images', None):
img_url = _get_best_image_url(spo_album.images, 'spotify')
if img_url:
self.__song_metadata_dict['image'] = img_url
except Exception:
pass
self.__c_quality = qualities[self.__quality_download] self.__c_quality = qualities[self.__quality_download]
self.__fallback_ids = self.__ids self.__fallback_ids = self.__ids
@@ -1120,6 +1145,9 @@ class DW_ALBUM:
album_obj: albumCbObject = self.__preferences.song_metadata album_obj: albumCbObject = self.__preferences.song_metadata
self.__song_metadata = self._album_object_to_dict(album_obj) self.__song_metadata = self._album_object_to_dict(album_obj)
self.__song_metadata['artist_separator'] = getattr(self.__preferences, 'artist_separator', '; ') self.__song_metadata['artist_separator'] = getattr(self.__preferences, 'artist_separator', '; ')
# New: Spotify metadata context for album-level tagging
self.__use_spotify = getattr(self.__preferences, 'spotify_metadata', False)
self.__spotify_album_obj = getattr(self.__preferences, 'spotify_album_obj', None)
def dw(self) -> Album: def dw(self) -> Album:
from deezspot.deezloader.deegw_api import API_GW from deezspot.deezloader.deegw_api import API_GW
@@ -1132,19 +1160,40 @@ class DW_ALBUM:
md5_image = infos_dw[0]['ALB_PICTURE'] md5_image = infos_dw[0]['ALB_PICTURE']
image_bytes = API.choose_img(md5_image, size="1400x1400") image_bytes = API.choose_img(md5_image, size="1400x1400")
# Convert album object to dictionary for legacy functions # Choose album_dict source: Spotify if requested and available, else Deezer
album_dict = self._album_object_to_dict(album_obj) artist_separator = getattr(self.__preferences, 'artist_separator', '; ')
album_dict['image'] = image_bytes if self.__use_spotify and self.__spotify_album_obj:
album_dict = album_object_to_dict(self.__spotify_album_obj, source_type='spotify', artist_separator=artist_separator)
else:
album_dict = self._album_object_to_dict(album_obj)
# Prefer Spotify image if requested and available
if self.__use_spotify and self.__spotify_album_obj and getattr(self.__spotify_album_obj, 'images', None):
from deezspot.libutils.metadata_converter import _get_best_image_url
spo_img_url = _get_best_image_url(self.__spotify_album_obj.images, 'spotify')
if spo_img_url:
album_dict['image'] = spo_img_url
else:
album_dict['image'] = image_bytes
else:
album_dict['image'] = image_bytes
album = Album(self.__ids) album = Album(self.__ids)
album.image = image_bytes # album.image should be raw bytes; fetch URL if needed
if isinstance(album_dict['image'], bytes):
album.image = album_dict['image']
else:
try:
from deezspot.libutils.taggers import fetch_and_process_image
album.image = fetch_and_process_image(album_dict['image']) or API.choose_img(md5_image, size="1400x1400")
except Exception:
album.image = API.choose_img(md5_image, size="1400x1400")
album.md5_image = md5_image album.md5_image = md5_image
album.nb_tracks = album_obj.total_tracks album.nb_tracks = album_obj.total_tracks
album.album_name = album_obj.title album.album_name = album_obj.title
album.upc = album_obj.ids.upc album.upc = album_obj.ids.upc
album.tags = album_dict album.tags = album_dict
tracks = album.tracks tracks = album.tracks
medias = Download_JOB.check_sources(infos_dw, self.__quality_download) medias = Download_JOB.check_sources(infos_dw, self.__quality_download)
album_base_directory = get_album_directory( album_base_directory = get_album_directory(
@@ -1159,6 +1208,20 @@ class DW_ALBUM:
save_cover_image(album.image, album_base_directory, "cover.jpg") save_cover_image(album.image, album_base_directory, "cover.jpg")
total_tracks = len(infos_dw) total_tracks = len(infos_dw)
# If spotify album metadata is requested and available, build a map of spotify track objects by ISRC or index
spotify_tracks_by_isrc = {}
spotify_tracks_in_order = []
if self.__use_spotify and self.__spotify_album_obj and getattr(self.__spotify_album_obj, 'tracks', None):
# The spotify album object contains trackAlbumObjects with ids (including isrc)
for spo_track in self.__spotify_album_obj.tracks:
spotify_tracks_in_order.append(spo_track)
try:
isrc_val = getattr(spo_track.ids, 'isrc', None)
if isrc_val:
spotify_tracks_by_isrc[isrc_val.upper()] = spo_track
except Exception:
pass
for a, album_track_obj in enumerate(album_obj.tracks): for a, album_track_obj in enumerate(album_obj.tracks):
c_infos_dw_item = infos_dw[a] c_infos_dw_item = infos_dw[a]
@@ -1197,6 +1260,25 @@ class DW_ALBUM:
c_preferences.track_number = a + 1 # For progress reporting only c_preferences.track_number = a + 1 # For progress reporting only
c_preferences.total_tracks = total_tracks c_preferences.total_tracks = total_tracks
c_preferences.link = f"https://deezer.com/track/{c_preferences.ids}" c_preferences.link = f"https://deezer.com/track/{c_preferences.ids}"
# Inject Spotify per-track object if available without extra API calls
if self.__use_spotify and spotify_tracks_in_order:
spo_track_for_tag = None
# Prefer ISRC matching if possible
deezer_isrc = None
try:
deezer_isrc = c_infos_dw_item.get('ISRC') or c_infos_dw_item.get('isrc')
deezer_isrc = deezer_isrc.upper() if isinstance(deezer_isrc, str) else None
except Exception:
deezer_isrc = None
if deezer_isrc and deezer_isrc in spotify_tracks_by_isrc:
spo_track_for_tag = spotify_tracks_by_isrc[deezer_isrc]
else:
# Fallback to index position
if a < len(spotify_tracks_in_order):
spo_track_for_tag = spotify_tracks_in_order[a]
if spo_track_for_tag:
c_preferences.spotify_metadata = True
c_preferences.spotify_track_obj = spo_track_for_tag
current_track_object = EASY_DW(c_infos_dw_item, c_preferences, parent='album').easy_dw() current_track_object = EASY_DW(c_infos_dw_item, c_preferences, parent='album').easy_dw()
except Exception as e: except Exception as e:

View File

@@ -251,7 +251,9 @@ class DeeLogin:
save_cover=stock_save_cover, save_cover=stock_save_cover,
market=stock_market, market=stock_market,
playlist_context=None, playlist_context=None,
artist_separator: str = "; " artist_separator: str = "; ",
spotify_metadata: bool = False,
spotify_album_obj=None
) -> Album: ) -> Album:
link_is_valid(link_album) link_is_valid(link_album)
@@ -299,6 +301,8 @@ class DeeLogin:
preferences.save_cover = save_cover preferences.save_cover = save_cover
preferences.market = market preferences.market = market
preferences.artist_separator = artist_separator preferences.artist_separator = artist_separator
preferences.spotify_metadata = bool(spotify_metadata)
preferences.spotify_album_obj = spotify_album_obj
if playlist_context: if playlist_context:
preferences.json_data = playlist_context['json_data'] preferences.json_data = playlist_context['json_data']
@@ -637,11 +641,24 @@ class DeeLogin:
save_cover=stock_save_cover, save_cover=stock_save_cover,
market=stock_market, market=stock_market,
playlist_context=None, playlist_context=None,
artist_separator: str = "; " artist_separator: str = "; ",
spotify_metadata: bool = False
) -> Album: ) -> Album:
link_dee = self.convert_spoty_to_dee_link_album(link_album) link_dee = self.convert_spoty_to_dee_link_album(link_album)
spotify_album_obj = None
if spotify_metadata:
try:
# Fetch full Spotify album with tracks once and convert to albumObject
from deezspot.spotloader.__spo_api__ import tracking_album as spo_tracking_album
spo_ids = get_ids(link_album)
spotify_album_json = Spo.get_album(spo_ids)
if spotify_album_json:
spotify_album_obj = spo_tracking_album(spotify_album_json)
except Exception:
spotify_album_obj = None
album = self.download_albumdee( album = self.download_albumdee(
link_dee, output_dir, link_dee, output_dir,
quality_download, recursive_quality, quality_download, recursive_quality,
@@ -658,7 +675,9 @@ class DeeLogin:
save_cover=save_cover, save_cover=save_cover,
market=market, market=market,
playlist_context=playlist_context, playlist_context=playlist_context,
artist_separator=artist_separator artist_separator=artist_separator,
spotify_metadata=spotify_metadata,
spotify_album_obj=spotify_album_obj
) )
return album return album

View File

@@ -26,4 +26,6 @@ class Preferences:
# New: when True, use Spotify metadata for tagging in spo flows # New: when True, use Spotify metadata for tagging in spo flows
self.spotify_metadata: bool = False self.spotify_metadata: bool = False
# New: optional Spotify trackObject to use when spotify_metadata is True # New: optional Spotify trackObject to use when spotify_metadata is True
self.spotify_track_obj = None self.spotify_track_obj = None
# New: optional Spotify albumObject (from spotloader tracking_album) for album-level spotify_metadata
self.spotify_album_obj = None