ContentType,
ExternalID,
ImageType,
+ MediaType,
ProviderFeature,
StreamType,
)
ItemMapping,
MediaItemImage,
MediaItemType,
- MediaType,
Playlist,
ProviderMapping,
SearchResults,
Track,
+ UniqueList,
)
from music_assistant_models.streamdetails import StreamDetails
from pywidevine import PSSH, Cdm, Device, DeviceTypes
from music_assistant.helpers.json import json_loads
from music_assistant.helpers.playlists import fetch_playlist
from music_assistant.helpers.throttle_retry import ThrottlerManager, throttle_with_retries
+from music_assistant.helpers.util import infer_album_type
from music_assistant.models.music_provider import MusicProvider
if TYPE_CHECKING:
self._decrypt_private_key = await _file.read()
async def search(
- self, search_query: str, media_types=list[MediaType] | None, limit: int = 5
+ self, search_query: str, media_types: list[MediaType] | None, limit: int = 5
) -> SearchResults:
"""Perform search on musicprovider.
enable_cache=True,
)
- def _parse_artist(self, artist_obj):
+ def _parse_artist(self, artist_obj: dict[str, Any]) -> Artist:
"""Parse artist object to generic layout."""
relationships = artist_obj.get("relationships", {})
if (
},
)
if artwork := attributes.get("artwork"):
- artist.metadata.images = [
+ artist.metadata.add_image(
MediaItemImage(
+ provider=self.lookup_key,
type=ImageType.THUMB,
path=artwork["url"].format(w=artwork["width"], h=artwork["height"]),
- provider=self.lookup_key,
remotely_accessible=True,
)
- ]
+ )
if genres := attributes.get("genreNames"):
artist.metadata.genres = set(genres)
if notes := attributes.get("editorialNotes"):
},
)
if artists := relationships.get("artists"):
- album.artists = [self._parse_artist(artist) for artist in artists["data"]]
+ album.artists = UniqueList([self._parse_artist(artist) for artist in artists["data"]])
elif artist_name := attributes.get("artistName"):
- album.artists = [
- ItemMapping(
- media_type=MediaType.ARTIST,
- provider=self.lookup_key,
- item_id=artist_name,
- name=artist_name,
- )
- ]
+ album.artists = UniqueList(
+ [
+ ItemMapping(
+ media_type=MediaType.ARTIST,
+ provider=self.lookup_key,
+ item_id=artist_name,
+ name=artist_name,
+ )
+ ]
+ )
if release_date := attributes.get("releaseDate"):
album.year = int(release_date.split("-")[0])
if genres := attributes.get("genreNames"):
album.metadata.genres = set(genres)
if artwork := attributes.get("artwork"):
- album.metadata.images = [
+ album.metadata.add_image(
MediaItemImage(
+ provider=self.lookup_key,
type=ImageType.THUMB,
path=artwork["url"].format(w=artwork["width"], h=artwork["height"]),
- provider=self.lookup_key,
remotely_accessible=True,
)
- ]
+ )
if album_copyright := attributes.get("copyright"):
album.metadata.copyright = album_copyright
if record_label := attributes.get("recordLabel"):
elif attributes.get("isCompilation"):
album_type = AlbumType.COMPILATION
album.album_type = album_type
+
+ # Try inference - override if it finds something more specific
+ # Apple Music doesn't seem to have version field
+ inferred_type = infer_album_type(album.name, "")
+ if inferred_type in (AlbumType.SOUNDTRACK, AlbumType.LIVE):
+ album.album_type = inferred_type
+
return album
def _parse_track(
if "data" in albums and len(albums["data"]) > 0:
track.album = self._parse_album(albums["data"][0])
if artwork := attributes.get("artwork"):
- track.metadata.images = [
+ track.metadata.add_image(
MediaItemImage(
+ provider=self.lookup_key,
type=ImageType.THUMB,
path=artwork["url"].format(w=artwork["width"], h=artwork["height"]),
- provider=self.lookup_key,
remotely_accessible=True,
)
- ]
+ )
if genres := attributes.get("genreNames"):
track.metadata.genres = set(genres)
if composers := attributes.get("composerName"):
track.external_ids.add((ExternalID.ISRC, isrc))
return track
- def _parse_playlist(self, playlist_obj) -> Playlist:
+ def _parse_playlist(self, playlist_obj: dict[str, Any]) -> Playlist:
"""Parse Apple Music playlist object to generic layout."""
attributes = playlist_obj["attributes"]
playlist_id = attributes["playParams"].get("globalId") or playlist_obj["id"]
url = artwork["url"]
if artwork["width"] and artwork["height"]:
url = url.format(w=artwork["width"], h=artwork["height"])
- playlist.metadata.images = [
+ playlist.metadata.add_image(
MediaItemImage(
+ provider=self.lookup_key,
type=ImageType.THUMB,
path=url,
- provider=self.lookup_key,
remotely_accessible=True,
)
- ]
+ )
if description := attributes.get("description"):
playlist.metadata.description = description.get("standard")
if checksum := attributes.get("lastModifiedDate"):
from ibroadcastaio import IBroadcastClient
from music_assistant_models.config_entries import ConfigEntry, ConfigValueType
from music_assistant_models.enums import (
- AlbumType,
ConfigEntryType,
ContentType,
ImageType,
VARIOUS_ARTISTS_MBID,
VARIOUS_ARTISTS_NAME,
)
-from music_assistant.helpers.util import parse_title_and_version
+from music_assistant.helpers.util import infer_album_type, parse_title_and_version
from music_assistant.models.music_provider import MusicProvider
SUPPORTED_FEATURES = {
if "rating" in album_obj and album_obj["rating"] == 5:
album.favorite = True
- # iBroadcast doesn't seem to know album type
- album.album_type = AlbumType.UNKNOWN
+ # iBroadcast doesn't seem to know album type - try inference
+ album.album_type = infer_album_type(name, version)
+
# There is only an artwork in the tracks, lets get the first track one
artwork_url = await self._client.get_album_artwork_url(album_id)
if artwork_url:
from music_assistant.constants import CONF_PASSWORD, CONF_USERNAME
from music_assistant.helpers.json import json_loads
+from music_assistant.helpers.util import infer_album_type
from music_assistant.models.music_provider import MusicProvider
if TYPE_CHECKING:
if year:
album.year = int(year)
+ # No album type info in this provider so try and infer it
+ album.album_type = infer_album_type(album.name, "")
+
return album
def _parse_playlist(self, playlist_obj: dict[str, Any]) -> Playlist:
import hashlib
import time
from contextlib import suppress
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, Any
from aiohttp import client_exceptions
from music_assistant_models.config_entries import ConfigEntry, ConfigValueType
from music_assistant.helpers.app_vars import app_var
from music_assistant.helpers.json import json_loads
from music_assistant.helpers.throttle_retry import ThrottlerManager, throttle_with_retries
-from music_assistant.helpers.util import lock, parse_title_and_version, try_parse_int
+from music_assistant.helpers.util import (
+ infer_album_type,
+ lock,
+ parse_title_and_version,
+ try_parse_int,
+)
from music_assistant.models.music_provider import MusicProvider
if TYPE_CHECKING:
raise LoginFailed(msg)
async def search(
- self, search_query: str, media_types=list[MediaType], limit: int = 5
+ self, search_query: str, media_types: list[MediaType], limit: int = 5
) -> SearchResults:
"""Perform search on musicprovider.
artist.metadata.description = artist_obj["biography"].get("content")
return artist
- async def _parse_album(self, album_obj: dict, artist_obj: dict | None = None):
+ async def parse_album(
+ self, album_obj: dict[str, Any], artist_obj: dict[str, Any] | None = None
+ ) -> Album:
"""Parse qobuz album object to generic layout."""
if not artist_obj and "artist" not in album_obj:
# artist missing in album info, return full abum instead
or album_obj.get("release_type", "") == "album"
):
album.album_type = AlbumType.ALBUM
+
+ # Try inference - override if it finds something more specific
+ inferred_type = infer_album_type(name, version)
+ if inferred_type in (AlbumType.SOUNDTRACK, AlbumType.LIVE):
+ album.album_type = inferred_type
+
if "genre" in album_obj:
album.metadata.genres = {album_obj["genre"]["name"]}
if img := self.__get_image(album_obj):
- album.metadata.images = [
+ album.metadata.add_image(
MediaItemImage(
+ provider=self.lookup_key,
type=ImageType.THUMB,
path=img,
- provider=self.lookup_key,
remotely_accessible=True,
)
- ]
+ )
if "label" in album_obj:
album.metadata.label = album_obj["label"]["name"]
if released_at := album_obj.get("released_at"):
from music_assistant.constants import CACHE_CATEGORY_DEFAULT, CACHE_CATEGORY_RECOMMENDATIONS
from music_assistant.helpers.throttle_retry import ThrottlerManager, throttle_with_retries
+from music_assistant.helpers.util import infer_album_type
from music_assistant.models.music_provider import MusicProvider
from .auth_manager import ManualAuthenticationHelper, TidalAuthManager
elif album_type == "SINGLE":
album.album_type = AlbumType.SINGLE
+ # Try inference - override if it finds something more specific
+ inferred_type = infer_album_type(name, version)
+ if inferred_type in (AlbumType.SOUNDTRACK, AlbumType.LIVE):
+ album.album_type = inferred_type
+
# Safely parse year
if release_date := album_obj.get("releaseDate", ""):
try:
RecommendationFolder,
SearchResults,
Track,
+ UniqueList,
)
from music_assistant_models.streamdetails import StreamDetails
from ytmusicapi.constants import SUPPORTED_LANGUAGES
from music_assistant.constants import CONF_USERNAME, VERBOSE_LOG_LEVEL
from music_assistant.controllers.cache import use_cache
-from music_assistant.helpers.util import install_package
+from music_assistant.helpers.util import infer_album_type, install_package
from music_assistant.models.music_provider import MusicProvider
from .helpers import (
def _parse_album(self, album_obj: dict, album_id: str | None = None) -> Album:
"""Parse a YT Album response to an Album model object."""
album_id = album_id or album_obj.get("id") or album_obj.get("browseId")
+
+ if not album_id:
+ raise InvalidDataError("Album ID is required but not found")
+
if "title" in album_obj:
name = album_obj["title"]
elif "name" in album_obj:
if album_obj.get("year") and album_obj["year"].isdigit():
album.year = album_obj["year"]
if "thumbnails" in album_obj:
- album.metadata.images = self._parse_thumbnails(album_obj["thumbnails"])
+ album.metadata.images = UniqueList(self._parse_thumbnails(album_obj["thumbnails"]))
if description := album_obj.get("description"):
album.metadata.description = unquote(description)
if "isExplicit" in album_obj:
album.metadata.explicit = album_obj["isExplicit"]
if "artists" in album_obj:
- album.artists = [
- self._get_artist_item_mapping(artist)
- for artist in album_obj["artists"]
- if artist.get("id")
- or artist.get("channelId")
- or artist.get("name") == "Various Artists"
- ]
+ album.artists = UniqueList(
+ [
+ self._get_artist_item_mapping(artist)
+ for artist in album_obj["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":
album_type = AlbumType.SINGLE
else:
album_type = AlbumType.UNKNOWN
album.album_type = album_type
+
+ # Try inference - override if it finds something more specific
+ inferred_type = infer_album_type(name, "") # YouTube doesn't seem to have version field
+ if inferred_type in (AlbumType.SOUNDTRACK, AlbumType.LIVE):
+ album.album_type = inferred_type
+
return album
def _parse_artist(self, artist_obj: dict) -> Artist: