Fix: Subsonic Scrobble: Split the internal id for Podcast Episode (#2203)
authorEric Munson <eric@munsonfam.org>
Sun, 25 May 2025 23:08:05 +0000 (19:08 -0400)
committerGitHub <noreply@github.com>
Sun, 25 May 2025 23:08:05 +0000 (01:08 +0200)
The Subsonic API only recently added an optional endpoint for retrieving
the metadata for an individual podcast episode but most servers do not
yet implement it. The only way to get an individual episode is through
its channel. This means that we have to include both IDs in the MA
provider ID for an episode.

This causes a problem when scrobbling because we are presented with an
ID that is not actually valid for the subsonic server. In the case where
the string EP_CHAN_SEP exists in the provider id, split it and only call
scrobble on the episode portion.

Signed-off-by: Eric B Munson <eric@munsonfam.org>
music_assistant/providers/subsonic_scrobble/__init__.py

index 49f9615a8a5be6c363efd9e411b15d09047a189e..f039d6b50217e0e0ff74ea3b97c8a11130092cbe 100644 (file)
@@ -17,6 +17,7 @@ from music_assistant.helpers.uri import parse_uri
 from music_assistant.mass import MusicAssistant
 from music_assistant.models import ProviderInstanceType
 from music_assistant.models.plugin import PluginProvider
+from music_assistant.providers.opensubsonic.parsers import EP_CHAN_SEP
 from music_assistant.providers.opensubsonic.sonic_provider import OpenSonicProvider
 
 
@@ -99,13 +100,23 @@ class SubsonicScrobbleEventHandler(ScrobblerHelper):
                     # found a subsonic mapping, proceed...
                     prov = self.mass.get_provider(mapping.provider_instance)
                     assert isinstance(prov, OpenSonicProvider)
-                    return prov, mapping.item_id
+                    # Because there is no way to retrieve a single podcast episode in vanilla
+                    # subsonic, we have to carry around the channel id as well. See
+                    # opensubsonic.parsers.parse_episode.
+                    if isinstance(library_item, PodcastEpisode) and EP_CHAN_SEP in mapping.item_id:
+                        _, ret_id = mapping.item_id.split(EP_CHAN_SEP)
+                    else:
+                        ret_id = mapping.item_id
+                    return prov, ret_id
             # no subsonic mapping has been found in library item, ignore...
             return None, item_id
         elif provider_instance_id_or_domain.startswith("opensubsonic"):
             # found a subsonic mapping, proceed...
             prov = self.mass.get_provider(provider_instance_id_or_domain)
             assert isinstance(prov, OpenSonicProvider)
+            if media_type == MediaType.PODCAST_EPISODE and EP_CHAN_SEP in item_id:
+                _, ret_id = item_id.split(EP_CHAN_SEP)
+                return prov, ret_id
             return prov, item_id
         # not an item from subsonic provider, ignore...
         return None, item_id