From e4067aa4a07b7ec340fec0c4c2f04f48ee3d2eb1 Mon Sep 17 00:00:00 2001 From: GladistonXD <50533550+GladistonXD@users.noreply.github.com> Date: Sat, 24 Jan 2026 01:57:38 -0300 Subject: [PATCH] Fix Refactor media metadata handling and improve error logging. --- votify/cli.py | 149 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 119 insertions(+), 30 deletions(-) diff --git a/votify/cli.py b/votify/cli.py index b643b25..c559de5 100644 --- a/votify/cli.py +++ b/votify/cli.py @@ -1,5 +1,7 @@ from __future__ import annotations +import traceback + import inspect import logging import time @@ -489,7 +491,7 @@ def main( ) is_premium = ( - True if force_premium else spotify_api.user_profile["product"] == "premium" + True if force_premium else spotify_api.user_profile['data']['me']['account']['product'] == "premium" ) if not lrc_only: if download_mode == DownloadMode.ARIA2C and not downloader.aria2c_path_full: @@ -566,11 +568,19 @@ def main( urls = _urls error_count = 0 + + global_playlist_metadata = None + for url_index, url in enumerate(urls, start=1): url_progress = color_text(f"URL {url_index}/{len(urls)}", colorama.Style.DIM) logger.info(f'({url_progress}) Checking "{url}"') try: url_info = downloader.get_url_info(url) + + if url_info.type == 'playlist': + if hasattr(url_info, 'name') and url_info.name: + global_playlist_metadata = {'name': url_info.name, 'owner_name': getattr(url_info, 'owner', 'Unknown')} + if url_info.type == "artist": download_queue = downloader.get_download_queue_from_artist(url_info.id) else: @@ -582,76 +592,154 @@ def main( error_count += 1 logger.error( f'({url_progress}) Failed to check "{url}"', - exc_info=no_exceptions, + exc_info=not no_exceptions, ) continue + for index, download_queue_item in enumerate(download_queue, start=1): queue_progress = color_text( f"Track {index}/{len(download_queue)} from URL {url_index}/{len(urls)}", colorama.Style.DIM, ) - media_metadata = download_queue_item.media_metadata + + media_metadata = None + if isinstance(download_queue_item, dict): + media_metadata = download_queue_item.get("media_metadata", download_queue_item) + else: + media_metadata = getattr(download_queue_item, "media_metadata", download_queue_item) + + if not isinstance(media_metadata, dict): + media_metadata = getattr(media_metadata, "__dict__", {}) + + if isinstance(media_metadata, dict) and 'track' in media_metadata and isinstance(media_metadata['track'],dict): + clean_track_metadata = media_metadata['track'] + else: + clean_track_metadata = media_metadata + + track_name = "Unknown Track" try: - logger.info( - f'({queue_progress}) Downloading "{media_metadata["name"]}"' - ) - media_id = downloader.get_media_id(media_metadata) - media_type = media_metadata["type"] - gid_metadata = downloader.get_gid_metadata(media_id, media_type) + track_name = media_metadata["data"]["trackUnion"]["name"] + except (KeyError, TypeError): + track_name = clean_track_metadata.get("name", "Unknown Track") + + is_playable = True + + if isinstance(clean_track_metadata, dict): + + if clean_track_metadata.get('isPlayable') is False: + is_playable = False + elif clean_track_metadata.get('is_playable') is False: + is_playable = False + elif clean_track_metadata.get('playable') is False: + is_playable = False + + elif 'data' in clean_track_metadata: + try: + union = clean_track_metadata['data']['trackUnion'] + if union.get('isPlayable') is False or union.get('playable') is False: + is_playable = False + except: + pass + + if not is_playable: + logger.warning(f'({queue_progress}) Skipping "{track_name}": Track unavailable/unplayable.') + continue + + try: + logger.info(f'({queue_progress}) Downloading "{track_name}"') + + try: + media_id = downloader.get_media_id(clean_track_metadata) + except ValueError: + if isinstance(download_queue_item, dict) and 'id' in download_queue_item: + media_id = download_queue_item['id'] + else: + raise + + media_metadata_for_download = media_metadata + + media_type = "track" + try: + type_from_meta = media_metadata['data']['trackUnion']['__typename'] + media_type = type_from_meta.lower() + except (KeyError, TypeError, AttributeError): + if isinstance(media_metadata, dict) and 'episode' in media_metadata: + media_type = "episode" + media_metadata_for_download = None + else: + media_type = "track" + media_metadata_for_download = None + + gid_metadata = downloader.get_gid_metadata(media_id, media_type, spotify_api.user_profile) + + def get_meta(item, key): + val = getattr(item, key, None) + if val is None and isinstance(item, dict): + val = item.get(key) + return val + + safe_album_metadata = get_meta(download_queue_item, "album_metadata") + safe_playlist_metadata = get_meta(download_queue_item, "playlist_metadata") + safe_show_metadata = get_meta(download_queue_item, "show_metadata") + + if not safe_playlist_metadata and global_playlist_metadata: + safe_playlist_metadata = global_playlist_metadata + if media_type == "track": if disable_wvd: - logger.warning( - "Widevine decryption is disabled, " - "skipping Widevine protected content" - ) + logger.warning("Widevine decryption is disabled, skipping.") continue if audio_quality in VORBIS_AUDIO_QUALITIES: - logger.warning( - "Vorbis audio quality is only supported for podcasts, " - "skipping" - ) + logger.warning("Vorbis audio quality is only supported for podcasts.") continue + if download_music_videos: downloader_music_video.download( music_video_id=media_id, - music_video_metadata=media_metadata, - album_metadata=download_queue_item.album_metadata, + music_video_metadata=media_metadata_for_download, + album_metadata=safe_album_metadata, gid_metadata=gid_metadata, - playlist_metadata=download_queue_item.playlist_metadata, + playlist_metadata=safe_playlist_metadata, playlist_track=index, ) else: downloader_song.download( track_id=media_id, - track_metadata=media_metadata, - album_metadata=download_queue_item.album_metadata, + track_metadata=media_metadata_for_download, + album_metadata=safe_album_metadata, gid_metadata=gid_metadata, - playlist_metadata=download_queue_item.playlist_metadata, + playlist_metadata=safe_playlist_metadata, + product_name=spotify_api.user_profile, playlist_track=index, ) + elif media_type == "episode": if download_podcast_videos: downloader_episode_video.download( episode_id=media_id, - episode_metadata=media_metadata, - show_metadata=download_queue_item.show_metadata, + episode_metadata=media_metadata_for_download, + show_metadata=safe_show_metadata, gid_metadata=gid_metadata, - playlist_metadata=download_queue_item.playlist_metadata, + playlist_metadata=safe_playlist_metadata, playlist_track=index, ) else: downloader_episode.download( episode_id=media_id, - episode_metadata=media_metadata, - show_metadata=download_queue_item.show_metadata, + episode_metadata=media_metadata_for_download, + show_metadata=safe_show_metadata, gid_metadata=gid_metadata, - playlist_metadata=download_queue_item.playlist_metadata, + playlist_metadata=safe_playlist_metadata, playlist_track=index, ) + #except ValueError: + # error_count += 1 + # continue + except Exception as e: error_count += 1 logger.error( - f'({queue_progress}) Failed to download "{media_metadata["name"]}"', + f'({queue_progress}) Failed to download "{track_name}"', exc_info=not no_exceptions, ) finally: @@ -660,4 +748,5 @@ def main( f"Waiting for {wait_interval} second(s) before continuing" ) time.sleep(wait_interval) + logger.info(f"Done ({error_count} error(s))")