Add item mappings to YTM (#601)
authorMarvin Schenkel <marvinschenkel@gmail.com>
Fri, 31 Mar 2023 20:26:29 +0000 (22:26 +0200)
committerGitHub <noreply@github.com>
Fri, 31 Mar 2023 20:26:29 +0000 (22:26 +0200)
* Add ItemMappings to YTM.

* Fix nested ItemMapping resolve.

music_assistant/server/controllers/media/tracks.py
music_assistant/server/providers/ytmusic/__init__.py

index 720e5aadb2a0eb9c208be91d0d36425e31b80543..67799ecffbcf94a3e106e6db1175396cc9c238cc 100644 (file)
@@ -109,6 +109,13 @@ class TracksController(MediaControllerBase[Track]):
             item.album = await self.mass.music.albums.get_provider_item(
                 item.album.item_id, item.album.provider
             )
+        if item.album:
+            item.album.artists = [
+                await self.mass.music.artists.get_provider_item(artist.item_id, artist.provider)
+                if isinstance(artist, ItemMapping)
+                else artist
+                for artist in item.album.artists
+            ]
         # grab additional metadata
         if not skip_metadata_lookup:
             await self.mass.metadata.get_track_metadata(item)
index eee388d6c8711dc726e206a6242401f20eced762..8e23c783f9a203b8ea42643364562d9da2b6f59c 100644 (file)
@@ -11,6 +11,8 @@ from urllib.parse import unquote
 import pytube
 import ytmusicapi
 
+from music_assistant.common.helpers.uri import create_uri
+from music_assistant.common.helpers.util import create_sort_name
 from music_assistant.common.models.config_entries import ConfigEntry
 from music_assistant.common.models.enums import ConfigEntryType, ProviderFeature
 from music_assistant.common.models.errors import (
@@ -25,6 +27,7 @@ from music_assistant.common.models.media_items import (
     Artist,
     ContentType,
     ImageType,
+    ItemMapping,
     MediaItemImage,
     MediaType,
     Playlist,
@@ -65,6 +68,7 @@ CONF_COOKIE = "cookie"
 YT_DOMAIN = "https://www.youtube.com"
 YTM_DOMAIN = "https://music.youtube.com"
 YTM_BASE_URL = f"{YTM_DOMAIN}/youtubei/v1/"
+VARIOUS_ARTISTS_YTM_ID = "UCUTXlgdcKU5vfzFqHOWIvkA"
 
 SUPPORTED_FEATURES = (
     ProviderFeature.LIBRARY_ARTISTS,
@@ -512,12 +516,11 @@ class YoutubeMusicProvider(MusicProvider):
             album.metadata.explicit = album_obj["isExplicit"]
         if "artists" in album_obj:
             album.artists = [
-                await self._parse_artist(artist)
+                self._get_artist_item_mapping(artist)
                 for artist in album_obj["artists"]
-                # artist object may be missing an id
-                # in that case its either a performer (like the composer) OR this
-                # is a Various artists compilation album...
-                if (artist.get("id") or artist["name"] == "Various Artists")
+                if artist.get("id")
+                or artist.get("channelId")
+                or artist.get("name") == "Various Artists"
             ]
         if "type" in album_obj:
             if album_obj["type"] == "Single":
@@ -546,7 +549,7 @@ class YoutubeMusicProvider(MusicProvider):
         elif "id" in artist_obj and artist_obj["id"]:
             artist_id = artist_obj["id"]
         elif artist_obj["name"] == "Various Artists":
-            artist_id = "UCUTXlgdcKU5vfzFqHOWIvkA"
+            artist_id = VARIOUS_ARTISTS_YTM_ID
         if not artist_id:
             raise InvalidDataError("Artist does not have a valid ID")
         artist = Artist(item_id=artist_id, name=artist_obj["name"], provider=self.domain)
@@ -601,7 +604,7 @@ class YoutubeMusicProvider(MusicProvider):
         track = Track(item_id=track_obj["videoId"], provider=self.domain, name=track_obj["title"])
         if "artists" in track_obj:
             track.artists = [
-                await self._parse_artist(artist)
+                self._get_artist_item_mapping(artist)
                 for artist in track_obj["artists"]
                 if artist.get("id")
                 or artist.get("channelId")
@@ -614,13 +617,11 @@ class YoutubeMusicProvider(MusicProvider):
             track.metadata.images = await self._parse_thumbnails(track_obj["thumbnails"])
         if (
             track_obj.get("album")
-            and track_obj.get("artists")
             and isinstance(track_obj.get("album"), dict)
             and track_obj["album"].get("id")
         ):
             album = track_obj["album"]
-            album["artists"] = track_obj["artists"]
-            track.album = await self._parse_album(album, album["id"])
+            track.album = self._get_item_mapping(MediaType.ALBUM, album["id"], album["name"])
         if "isExplicit" in track_obj:
             track.metadata.explicit = track_obj["isExplicit"]
         if "duration" in track_obj and str(track_obj["duration"]).isdigit():
@@ -707,6 +708,22 @@ class YoutubeMusicProvider(MusicProvider):
         async with self.mass.http_session.head(url) as response:
             return response.status == 200
 
+    def _get_item_mapping(self, media_type: MediaType, key: str, name: str) -> ItemMapping:
+        return ItemMapping(
+            media_type,
+            key,
+            self.instance_id,
+            name,
+            create_uri(media_type, self.instance_id, key),
+            create_sort_name(self.name),
+        )
+
+    def _get_artist_item_mapping(self, artist_obj: dict) -> ItemMapping:
+        artist_id = artist_obj.get("id") or artist_obj.get("channelId")
+        if not artist_id and artist_obj["name"] == "Various Artists":
+            artist_id = VARIOUS_ARTISTS_YTM_ID
+        return self._get_item_mapping(MediaType.ARTIST, artist_id, artist_obj.get("name"))
+
     @classmethod
     async def _parse_thumbnails(cls, thumbnails_obj: dict) -> list[MediaItemImage]:
         """Parse and sort a list of thumbnails and return the highest quality."""