Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add item mappings to YTM #601

Merged
merged 5 commits into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions music_assistant/server/controllers/media/tracks.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ async def add(self, item: Track, skip_metadata_lookup: bool = False) -> 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)
Expand Down
37 changes: 27 additions & 10 deletions music_assistant/server/providers/ytmusic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
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 (
Expand All @@ -25,6 +27,7 @@
Artist,
ContentType,
ImageType,
ItemMapping,
MediaItemImage,
MediaType,
Playlist,
Expand Down Expand Up @@ -65,6 +68,7 @@
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,
Expand Down Expand Up @@ -512,12 +516,11 @@ async def _parse_album(self, album_obj: dict, album_id: str = None) -> Album:
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":
Expand Down Expand Up @@ -546,7 +549,7 @@ async def _parse_artist(self, artist_obj: dict) -> Artist:
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)
Expand Down Expand Up @@ -601,7 +604,7 @@ async def _parse_track(self, track_obj: dict) -> Track:
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")
Expand All @@ -614,13 +617,11 @@ async def _parse_track(self, track_obj: dict) -> Track:
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():
Expand Down Expand Up @@ -707,6 +708,22 @@ async def _is_valid_deciphered_url(self, url: str) -> bool:
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."""
Expand Down