Spotify Podcast Final caching and fix image quality (#2395)
authorOzGav <gavnosp@hotmail.com>
Mon, 15 Sep 2025 11:51:15 +0000 (21:51 +1000)
committerGitHub <noreply@github.com>
Mon, 15 Sep 2025 11:51:15 +0000 (13:51 +0200)
music_assistant/providers/spotify/parsers.py
music_assistant/providers/spotify/provider.py

index 2fcc040a7410d20d7c921bbcf30b781dce400faf..61e3b4c4de0cee7c2ed22de10073524a59b0eb02 100644 (file)
@@ -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:
index 09680c89a0e02a1e11f581c9e26f56caad76a3d2..c811a46047d7b4f33434eb59de10b49b795d4298 100644 (file)
@@ -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