From: Eric Munson Date: Sat, 3 Feb 2024 17:59:26 +0000 (-0500) Subject: Subsonic: Two new features, better error handling, and reworked album/track parsing... X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=1d651db51d4a94f4c57c82cd812211d4bd20f3f9;p=music-assistant-server.git Subsonic: Two new features, better error handling, and reworked album/track parsing (#1055) --- diff --git a/music_assistant/server/providers/opensubsonic/manifest.json b/music_assistant/server/providers/opensubsonic/manifest.json index 0fe2a1df..e30f7ca1 100644 --- a/music_assistant/server/providers/opensubsonic/manifest.json +++ b/music_assistant/server/providers/opensubsonic/manifest.json @@ -4,7 +4,7 @@ "name": "Open Subsonic Media Server Library", "description": "Support for Open Subsonic based streaming providers in Music Assistant.", "codeowners": ["@khers"], - "requirements": ["py-opensonic>=5.0.2"], + "requirements": ["py-opensonic>=5.0.5"], "documentation": "https://github.com/orgs/music-assistant/discussions/1806", "multi_instance": true } diff --git a/music_assistant/server/providers/opensubsonic/sonic_provider.py b/music_assistant/server/providers/opensubsonic/sonic_provider.py index 0f862cd5..8c60f8b6 100644 --- a/music_assistant/server/providers/opensubsonic/sonic_provider.py +++ b/music_assistant/server/providers/opensubsonic/sonic_provider.py @@ -100,6 +100,8 @@ class OpenSonicProvider(MusicProvider): ProviderFeature.BROWSE, ProviderFeature.SEARCH, ProviderFeature.ARTIST_ALBUMS, + ProviderFeature.ARTIST_TOPTRACKS, + ProviderFeature.SIMILAR_TRACKS, ) @property @@ -213,12 +215,16 @@ class OpenSonicProvider(MusicProvider): ) }, ) + if sonic_artist.cover_id: artist.metadata.images = [ MediaItemImage( type=ImageType.THUMB, path=sonic_artist.cover_id, provider=self.instance_id ) ] + else: + artist.metadata.images = [] + if sonic_info: if sonic_info.biography: artist.metadata.description = sonic_info.biography @@ -243,13 +249,25 @@ class OpenSonicProvider(MusicProvider): }, year=sonic_album.year, ) + if sonic_album.cover_id: album.metadata.images = [ MediaItemImage( type=ImageType.THUMB, path=sonic_album.cover_id, provider=self.instance_id ), ] - if sonic_album.artist_id is None: + else: + album.metadata.images = [] + + if sonic_album.artist_id: + album.artists.append( + self._get_item_mapping( + MediaType.ARTIST, + sonic_album.artist_id, + sonic_album.artist if sonic_album.artist else UNKNOWN_ARTIST, + ) + ) + else: album.artists.append( Artist( item_id=UNKNOWN_ARTIST_ID, @@ -264,14 +282,6 @@ class OpenSonicProvider(MusicProvider): }, ) ) - else: - album.artists.append( - self._get_item_mapping( - MediaType.ARTIST, - sonic_album.artist_id, - sonic_album.artist if sonic_album.artist else UNKNOWN_ARTIST, - ) - ) if sonic_info: if sonic_info.small_url: @@ -318,7 +328,26 @@ class OpenSonicProvider(MusicProvider): if not extra_init_kwargs: track.track_number = int(sonic_song.track) if sonic_song.track is not None else 1 - if sonic_song.artist_id is None: + # We need to find an artist for this track but various implementations seem to disagree + # about where the artist with the valid ID needs to be found. We will add any artist with + # an ID and only use UNKNOWN if none are found. + + if sonic_song.artist_id: + track.artists.append( + self._get_item_mapping( + MediaType.ARTIST, + sonic_song.artist_id, + sonic_song.artist if sonic_song.artist else UNKNOWN_ARTIST, + ) + ) + + for entry in sonic_song.artists: + if entry.id == sonic_song.artist_id: + continue + if entry.id is not None and entry.name is not None: + track.artists.append(self._get_item_mapping(MediaType.ARTIST, entry.id, entry.name)) + + if not track.artists: track.artists.append( Artist( item_id=UNKNOWN_ARTIST_ID, @@ -333,20 +362,6 @@ class OpenSonicProvider(MusicProvider): }, ) ) - else: - track.artists.append( - self._get_item_mapping( - MediaType.ARTIST, - sonic_song.artist_id, - sonic_song.artist if sonic_song.artist else UNKNOWN_ARTIST, - ) - ) - - for entry in sonic_song.artists: - if entry.id == sonic_song.artist_id: - continue - if entry.id is not None and entry.name is not None: - track.artists.append(self._get_item_mapping(MediaType.ARTIST, entry.id, entry.name)) return track def _parse_playlist(self, sonic_playlist: SonicPlaylist) -> Playlist: @@ -596,6 +611,19 @@ class OpenSonicProvider(MusicProvider): for index, sonic_song in enumerate(sonic_playlist.songs): yield self._parse_track(sonic_song, {"position": index + 1}) + async def get_artist_toptracks(self, prov_artist_id: str) -> list[Track]: + """Get the top listed tracks for a specified artist.""" + sonic_artist: SonicArtist = await self._run_async(self._conn.getArtist, prov_artist_id) + songs: list[SonicSong] = await self._run_async(self._conn.getTopSongs, sonic_artist.name) + return [self._parse_track(entry) for entry in songs] + + async def get_similar_tracks(self, prov_track_id: str, limit: int = 25) -> list[Track]: + """Get tracks similar to selected track.""" + songs: list[SonicSong] = await self._run_async( + self._conn.getSimilarSongs2, iid=prov_track_id, count=limit + ) + return [self._parse_track(entry) for entry in songs] + async def get_stream_details(self, item_id: str) -> StreamDetails | None: """Get the details needed to process a specified track.""" try: diff --git a/requirements_all.txt b/requirements_all.txt index cf9ebb0f..f10b2d34 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -22,7 +22,7 @@ music-assistant-frontend==2.1.2 orjson==3.9.12 pillow==10.2.0 plexapi==4.15.7 -py-opensonic>=5.0.2 +py-opensonic>=5.0.5 PyChromecast==13.0.8 pycryptodome==3.20.0 python-fullykiosk==0.0.12