From 4b909fbddd6c42c3dde7420afb7d037f6a91e7a6 Mon Sep 17 00:00:00 2001 From: OzGav Date: Mon, 15 Sep 2025 21:51:15 +1000 Subject: [PATCH] Spotify Podcast Final caching and fix image quality (#2395) --- music_assistant/providers/spotify/parsers.py | 33 ++++++---- music_assistant/providers/spotify/provider.py | 63 ++++++++++++++++--- 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/music_assistant/providers/spotify/parsers.py b/music_assistant/providers/spotify/parsers.py index 2fcc040a..61e3b4c4 100644 --- a/music_assistant/providers/spotify/parsers.py +++ b/music_assistant/providers/spotify/parsers.py @@ -33,22 +33,31 @@ def parse_images( if not images_list: return UniqueList([]) + # Filter out generic images if requested (for artists) + filtered_images = [] for img in images_list: img_url = img["url"] - # Skip generic placeholder images for artists if requested if exclude_generic and "2a96cbd8b46e442fc41c2b86b821562f" in img_url: continue - return UniqueList( - [ - MediaItemImage( - type=ImageType.THUMB, - path=img_url, - provider=lookup_key, - remotely_accessible=True, - ) - ] - ) - return UniqueList([]) + filtered_images.append(img) + + if not filtered_images: + return UniqueList([]) + + # Spotify orders images from largest to smallest (640x640, 300x300, 64x64) + # Select the largest (highest quality) image - the first one + best_image = filtered_images[0] + + return UniqueList( + [ + MediaItemImage( + type=ImageType.THUMB, + path=best_image["url"], + provider=lookup_key, + remotely_accessible=True, + ) + ] + ) def parse_artist(artist_obj: dict[str, Any], provider: SpotifyProvider) -> Artist: diff --git a/music_assistant/providers/spotify/provider.py b/music_assistant/providers/spotify/provider.py index 09680c89..c811a460 100644 --- a/music_assistant/providers/spotify/provider.py +++ b/music_assistant/providers/spotify/provider.py @@ -343,22 +343,65 @@ class SpotifyProvider(MusicProvider): raise MediaNotFoundError(f"Podcast not found: {prov_podcast_id}") return parse_podcast(podcast_obj, self) + @use_cache(43200) # 12 hours - balances freshness with performance + async def _get_podcast_episodes_data(self, prov_podcast_id: str) -> list[dict[str, Any]]: + """Get raw episode data from Spotify API (cached). + + Args: + prov_podcast_id: Spotify podcast ID + + Returns: + List of episode data dictionaries + """ + episodes_data: list[dict[str, Any]] = [] + + try: + async for item in self._get_all_items( + f"shows/{prov_podcast_id}/episodes", market="from_token" + ): + if item and item.get("id"): + episodes_data.append(item) + except MediaNotFoundError: + self.logger.warning("Podcast %s not found", prov_podcast_id) + return [] + except ResourceTemporarilyUnavailable as err: + self.logger.warning( + "Temporary error fetching episodes for %s: %s", prov_podcast_id, err + ) + raise + + return episodes_data + async def get_podcast_episodes( self, prov_podcast_id: str ) -> AsyncGenerator[PodcastEpisode, None]: """Get all podcast episodes.""" - podcast = await self.get_podcast(prov_podcast_id) - episode_position = 1 + # Get podcast object for context if available - this should be cached from previous calls + podcast: Podcast | None = None - async for item in self._get_all_items( - f"shows/{prov_podcast_id}/episodes", market="from_token" - ): - if not (item and item["id"]): - continue + try: + podcast = await self.mass.music.podcasts.get_provider_item( + prov_podcast_id, self.instance_id + ) + except MediaNotFoundError: + self.logger.debug("Podcast %s not found in Music Assistant library", prov_podcast_id) + + # If we don't have the podcast from MA context, get it via the API + if not podcast: + try: + podcast = await self.get_podcast(prov_podcast_id) # This is cached + except MediaNotFoundError: + self.logger.warning( + "Podcast with ID %s is no longer available on Spotify", prov_podcast_id + ) + + # Get cached episode data - this is where the performance improvement happens + episodes_data = await self._get_podcast_episodes_data(prov_podcast_id) - episode = parse_podcast_episode(item, self, podcast=podcast) - episode.position = episode_position - episode_position += 1 + # Parse and yield episodes with position + for idx, episode_data in enumerate(episodes_data): + episode = parse_podcast_episode(episode_data, self, podcast) + episode.position = idx + 1 yield episode @use_cache(86400) # 24 hours -- 2.34.1