From 40a4ff1c8451e8e95f4e8db1a88b99b11b4106a5 Mon Sep 17 00:00:00 2001 From: Jozef Kruszynski <60214390+jozefKruszynski@users.noreply.github.com> Date: Wed, 27 Nov 2024 02:52:08 +0100 Subject: [PATCH] Adds media_browser functionality to the music assistant integration (#131577) * Add test fixtures for all library loading * Add media browser * Add tests for media_browser --- .../music_assistant/media_browser.py | 351 +++++++++++ .../music_assistant/media_player.py | 6 +- tests/components/music_assistant/common.py | 70 ++- .../fixtures/library_album_tracks.json | 364 ++++++++++++ .../fixtures/library_albums.json | 148 +++++ .../fixtures/library_artist_albums.json | 88 +++ .../fixtures/library_artists.json | 60 ++ .../fixtures/library_playlist_tracks.json | 262 +++++++++ .../fixtures/library_playlists.json | 63 ++ .../fixtures/library_radios.json | 66 +++ .../fixtures/library_tracks.json | 556 ++++++++++++++++++ .../music_assistant/test_media_browser.py | 65 ++ 12 files changed, 2096 insertions(+), 3 deletions(-) create mode 100644 homeassistant/components/music_assistant/media_browser.py create mode 100644 tests/components/music_assistant/fixtures/library_album_tracks.json create mode 100644 tests/components/music_assistant/fixtures/library_albums.json create mode 100644 tests/components/music_assistant/fixtures/library_artist_albums.json create mode 100644 tests/components/music_assistant/fixtures/library_artists.json create mode 100644 tests/components/music_assistant/fixtures/library_playlist_tracks.json create mode 100644 tests/components/music_assistant/fixtures/library_playlists.json create mode 100644 tests/components/music_assistant/fixtures/library_radios.json create mode 100644 tests/components/music_assistant/fixtures/library_tracks.json create mode 100644 tests/components/music_assistant/test_media_browser.py diff --git a/homeassistant/components/music_assistant/media_browser.py b/homeassistant/components/music_assistant/media_browser.py new file mode 100644 index 00000000000..e65d6d4a975 --- /dev/null +++ b/homeassistant/components/music_assistant/media_browser.py @@ -0,0 +1,351 @@ +"""Media Source Implementation.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from music_assistant_models.media_items import MediaItemType + +from homeassistant.components import media_source +from homeassistant.components.media_player import ( + BrowseError, + BrowseMedia, + MediaClass, + MediaType, +) +from homeassistant.core import HomeAssistant + +from .const import DEFAULT_NAME, DOMAIN + +if TYPE_CHECKING: + from music_assistant_client import MusicAssistantClient + +MEDIA_TYPE_RADIO = "radio" + +PLAYABLE_MEDIA_TYPES = [ + MediaType.PLAYLIST, + MediaType.ALBUM, + MediaType.ARTIST, + MEDIA_TYPE_RADIO, + MediaType.TRACK, +] + +LIBRARY_ARTISTS = "artists" +LIBRARY_ALBUMS = "albums" +LIBRARY_TRACKS = "tracks" +LIBRARY_PLAYLISTS = "playlists" +LIBRARY_RADIO = "radio" + + +LIBRARY_TITLE_MAP = { + LIBRARY_ARTISTS: "Artists", + LIBRARY_ALBUMS: "Albums", + LIBRARY_TRACKS: "Tracks", + LIBRARY_PLAYLISTS: "Playlists", + LIBRARY_RADIO: "Radio stations", +} + +LIBRARY_MEDIA_CLASS_MAP = { + LIBRARY_ARTISTS: MediaClass.ARTIST, + LIBRARY_ALBUMS: MediaClass.ALBUM, + LIBRARY_TRACKS: MediaClass.TRACK, + LIBRARY_PLAYLISTS: MediaClass.PLAYLIST, + LIBRARY_RADIO: MediaClass.MUSIC, # radio is not accepted by HA +} + +MEDIA_CONTENT_TYPE_FLAC = "audio/flac" +THUMB_SIZE = 200 + + +def media_source_filter(item: BrowseMedia) -> bool: + """Filter media sources.""" + return item.media_content_type.startswith("audio/") + + +async def async_browse_media( + hass: HomeAssistant, + mass: MusicAssistantClient, + media_content_id: str | None, + media_content_type: str | None, +) -> BrowseMedia: + """Browse media.""" + if media_content_id is None: + return await build_main_listing(hass) + + assert media_content_type is not None + + if media_source.is_media_source_id(media_content_id): + return await media_source.async_browse_media( + hass, media_content_id, content_filter=media_source_filter + ) + + if media_content_id == LIBRARY_ARTISTS: + return await build_artists_listing(mass) + if media_content_id == LIBRARY_ALBUMS: + return await build_albums_listing(mass) + if media_content_id == LIBRARY_TRACKS: + return await build_tracks_listing(mass) + if media_content_id == LIBRARY_PLAYLISTS: + return await build_playlists_listing(mass) + if media_content_id == LIBRARY_RADIO: + return await build_radio_listing(mass) + if "artist" in media_content_id: + return await build_artist_items_listing(mass, media_content_id) + if "album" in media_content_id: + return await build_album_items_listing(mass, media_content_id) + if "playlist" in media_content_id: + return await build_playlist_items_listing(mass, media_content_id) + + raise BrowseError(f"Media not found: {media_content_type} / {media_content_id}") + + +async def build_main_listing(hass: HomeAssistant) -> BrowseMedia: + """Build main browse listing.""" + children: list[BrowseMedia] = [] + for library, media_class in LIBRARY_MEDIA_CLASS_MAP.items(): + child_source = BrowseMedia( + media_class=MediaClass.DIRECTORY, + media_content_id=library, + media_content_type=DOMAIN, + title=LIBRARY_TITLE_MAP[library], + children_media_class=media_class, + can_play=False, + can_expand=True, + ) + children.append(child_source) + + try: + item = await media_source.async_browse_media( + hass, None, content_filter=media_source_filter + ) + # If domain is None, it's overview of available sources + if item.domain is None and item.children is not None: + children.extend(item.children) + else: + children.append(item) + except media_source.BrowseError: + pass + + return BrowseMedia( + media_class=MediaClass.DIRECTORY, + media_content_id="", + media_content_type=DOMAIN, + title=DEFAULT_NAME, + can_play=False, + can_expand=True, + children=children, + ) + + +async def build_playlists_listing(mass: MusicAssistantClient) -> BrowseMedia: + """Build Playlists browse listing.""" + media_class = LIBRARY_MEDIA_CLASS_MAP[LIBRARY_PLAYLISTS] + return BrowseMedia( + media_class=MediaClass.DIRECTORY, + media_content_id=LIBRARY_PLAYLISTS, + media_content_type=MediaType.PLAYLIST, + title=LIBRARY_TITLE_MAP[LIBRARY_PLAYLISTS], + can_play=False, + can_expand=True, + children_media_class=media_class, + children=sorted( + [ + build_item(mass, item, can_expand=True) + # we only grab the first page here because the + # HA media browser does not support paging + for item in await mass.music.get_library_playlists(limit=500) + if item.available + ], + key=lambda x: x.title, + ), + ) + + +async def build_playlist_items_listing( + mass: MusicAssistantClient, identifier: str +) -> BrowseMedia: + """Build Playlist items browse listing.""" + playlist = await mass.music.get_item_by_uri(identifier) + + return BrowseMedia( + media_class=MediaClass.PLAYLIST, + media_content_id=playlist.uri, + media_content_type=MediaType.PLAYLIST, + title=playlist.name, + can_play=True, + can_expand=True, + children_media_class=MediaClass.TRACK, + children=[ + build_item(mass, item, can_expand=False) + # we only grab the first page here because the + # HA media browser does not support paging + for item in await mass.music.get_playlist_tracks( + playlist.item_id, playlist.provider + ) + if item.available + ], + ) + + +async def build_artists_listing(mass: MusicAssistantClient) -> BrowseMedia: + """Build Albums browse listing.""" + media_class = LIBRARY_MEDIA_CLASS_MAP[LIBRARY_ARTISTS] + + return BrowseMedia( + media_class=MediaClass.DIRECTORY, + media_content_id=LIBRARY_ARTISTS, + media_content_type=MediaType.ARTIST, + title=LIBRARY_TITLE_MAP[LIBRARY_ARTISTS], + can_play=False, + can_expand=True, + children_media_class=media_class, + children=sorted( + [ + build_item(mass, artist, can_expand=True) + # we only grab the first page here because the + # HA media browser does not support paging + for artist in await mass.music.get_library_artists(limit=500) + if artist.available + ], + key=lambda x: x.title, + ), + ) + + +async def build_artist_items_listing( + mass: MusicAssistantClient, identifier: str +) -> BrowseMedia: + """Build Artist items browse listing.""" + artist = await mass.music.get_item_by_uri(identifier) + albums = await mass.music.get_artist_albums(artist.item_id, artist.provider) + + return BrowseMedia( + media_class=MediaType.ARTIST, + media_content_id=artist.uri, + media_content_type=MediaType.ARTIST, + title=artist.name, + can_play=True, + can_expand=True, + children_media_class=MediaClass.ALBUM, + children=[ + build_item(mass, album, can_expand=True) + for album in albums + if album.available + ], + ) + + +async def build_albums_listing(mass: MusicAssistantClient) -> BrowseMedia: + """Build Albums browse listing.""" + media_class = LIBRARY_MEDIA_CLASS_MAP[LIBRARY_ALBUMS] + + return BrowseMedia( + media_class=MediaClass.DIRECTORY, + media_content_id=LIBRARY_ALBUMS, + media_content_type=MediaType.ALBUM, + title=LIBRARY_TITLE_MAP[LIBRARY_ALBUMS], + can_play=False, + can_expand=True, + children_media_class=media_class, + children=sorted( + [ + build_item(mass, album, can_expand=True) + # we only grab the first page here because the + # HA media browser does not support paging + for album in await mass.music.get_library_albums(limit=500) + if album.available + ], + key=lambda x: x.title, + ), + ) + + +async def build_album_items_listing( + mass: MusicAssistantClient, identifier: str +) -> BrowseMedia: + """Build Album items browse listing.""" + album = await mass.music.get_item_by_uri(identifier) + tracks = await mass.music.get_album_tracks(album.item_id, album.provider) + + return BrowseMedia( + media_class=MediaType.ALBUM, + media_content_id=album.uri, + media_content_type=MediaType.ALBUM, + title=album.name, + can_play=True, + can_expand=True, + children_media_class=MediaClass.TRACK, + children=[ + build_item(mass, track, False) for track in tracks if track.available + ], + ) + + +async def build_tracks_listing(mass: MusicAssistantClient) -> BrowseMedia: + """Build Tracks browse listing.""" + media_class = LIBRARY_MEDIA_CLASS_MAP[LIBRARY_TRACKS] + + return BrowseMedia( + media_class=MediaClass.DIRECTORY, + media_content_id=LIBRARY_TRACKS, + media_content_type=MediaType.TRACK, + title=LIBRARY_TITLE_MAP[LIBRARY_TRACKS], + can_play=False, + can_expand=True, + children_media_class=media_class, + children=sorted( + [ + build_item(mass, track, can_expand=False) + # we only grab the first page here because the + # HA media browser does not support paging + for track in await mass.music.get_library_tracks(limit=500) + if track.available + ], + key=lambda x: x.title, + ), + ) + + +async def build_radio_listing(mass: MusicAssistantClient) -> BrowseMedia: + """Build Radio browse listing.""" + media_class = LIBRARY_MEDIA_CLASS_MAP[LIBRARY_RADIO] + return BrowseMedia( + media_class=MediaClass.DIRECTORY, + media_content_id=LIBRARY_RADIO, + media_content_type=DOMAIN, + title=LIBRARY_TITLE_MAP[LIBRARY_RADIO], + can_play=False, + can_expand=True, + children_media_class=media_class, + children=[ + build_item(mass, track, can_expand=False, media_class=media_class) + # we only grab the first page here because the + # HA media browser does not support paging + for track in await mass.music.get_library_radios(limit=500) + if track.available + ], + ) + + +def build_item( + mass: MusicAssistantClient, + item: MediaItemType, + can_expand: bool = True, + media_class: Any = None, +) -> BrowseMedia: + """Return BrowseMedia for MediaItem.""" + if artists := getattr(item, "artists", None): + title = f"{artists[0].name} - {item.name}" + else: + title = item.name + img_url = mass.get_media_item_image_url(item) + + return BrowseMedia( + media_class=media_class or item.media_type.value, + media_content_id=item.uri, + media_content_type=MediaType.MUSIC, + title=title, + can_play=True, + can_expand=can_expand, + thumbnail=img_url, + ) diff --git a/homeassistant/components/music_assistant/media_player.py b/homeassistant/components/music_assistant/media_player.py index d898322c293..8789bb36d33 100644 --- a/homeassistant/components/music_assistant/media_player.py +++ b/homeassistant/components/music_assistant/media_player.py @@ -43,6 +43,7 @@ from homeassistant.util.dt import utc_from_timestamp from . import MusicAssistantConfigEntry from .const import ATTR_ACTIVE_QUEUE, ATTR_MASS_PLAYER_TYPE, DOMAIN from .entity import MusicAssistantEntity +from .media_browser import async_browse_media if TYPE_CHECKING: from music_assistant_client import MusicAssistantClient @@ -440,10 +441,11 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity): media_content_id: str | None = None, ) -> BrowseMedia: """Implement the websocket media browsing helper.""" - return await media_source.async_browse_media( + return await async_browse_media( self.hass, + self.mass, media_content_id, - content_filter=lambda item: item.media_content_type.startswith("audio/"), + media_content_type, ) def _update_media_image_url( diff --git a/tests/components/music_assistant/common.py b/tests/components/music_assistant/common.py index 307a928f2cc..c8293b5622f 100644 --- a/tests/components/music_assistant/common.py +++ b/tests/components/music_assistant/common.py @@ -3,9 +3,10 @@ from __future__ import annotations from typing import Any -from unittest.mock import MagicMock +from unittest.mock import AsyncMock, MagicMock from music_assistant_models.enums import EventType +from music_assistant_models.media_items import Album, Artist, Playlist, Radio, Track from music_assistant_models.player import Player from music_assistant_models.player_queue import PlayerQueue from syrupy import SnapshotAssertion @@ -42,6 +43,25 @@ async def setup_integration_from_fixtures( data={"url": MOCK_URL}, unique_id=music_assistant_client.server_info.server_id, ) + music = music_assistant_client.music + library_artists = create_library_artists_from_fixture() + music.get_library_artists = AsyncMock(return_value=library_artists) + library_artist_albums = create_library_artist_albums_from_fixture() + music.get_artist_albums = AsyncMock(return_value=library_artist_albums) + library_albums = create_library_albums_from_fixture() + music.get_library_albums = AsyncMock(return_value=library_albums) + library_album_tracks = create_library_album_tracks_from_fixture() + music.get_album_tracks = AsyncMock(return_value=library_album_tracks) + library_tracks = create_library_tracks_from_fixture() + music.get_library_tracks = AsyncMock(return_value=library_tracks) + library_playlists = create_library_playlists_from_fixture() + music.get_library_playlists = AsyncMock(return_value=library_playlists) + library_playlist_tracks = create_library_playlist_tracks_from_fixture() + music.get_playlist_tracks = AsyncMock(return_value=library_playlist_tracks) + library_radios = create_library_radios_from_fixture() + music.get_library_radios = AsyncMock(return_value=library_radios) + music.get_item_by_uri = AsyncMock() + config_entry.add_to_hass(hass) assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -61,6 +81,54 @@ def create_player_queues_from_fixture() -> list[Player]: ] +def create_library_albums_from_fixture() -> list[Album]: + """Create MA Albums from fixture.""" + fixture_data = load_and_parse_fixture("library_albums") + return [Album.from_dict(album_data) for album_data in fixture_data] + + +def create_library_album_tracks_from_fixture() -> list[Track]: + """Create MA Tracks from fixture.""" + fixture_data = load_and_parse_fixture("library_album_tracks") + return [Track.from_dict(track_data) for track_data in fixture_data] + + +def create_library_tracks_from_fixture() -> list[Track]: + """Create MA Tracks from fixture.""" + fixture_data = load_and_parse_fixture("library_tracks") + return [Track.from_dict(track_data) for track_data in fixture_data] + + +def create_library_artists_from_fixture() -> list[Artist]: + """Create MA Artists from fixture.""" + fixture_data = load_and_parse_fixture("library_artists") + return [Artist.from_dict(artist_data) for artist_data in fixture_data] + + +def create_library_artist_albums_from_fixture() -> list[Album]: + """Create MA Albums from fixture.""" + fixture_data = load_and_parse_fixture("library_artist_albums") + return [Album.from_dict(album_data) for album_data in fixture_data] + + +def create_library_playlists_from_fixture() -> list[Playlist]: + """Create MA Playlists from fixture.""" + fixture_data = load_and_parse_fixture("library_playlists") + return [Playlist.from_dict(playlist_data) for playlist_data in fixture_data] + + +def create_library_playlist_tracks_from_fixture() -> list[Track]: + """Create MA Tracks from fixture.""" + fixture_data = load_and_parse_fixture("library_playlist_tracks") + return [Track.from_dict(track_data) for track_data in fixture_data] + + +def create_library_radios_from_fixture() -> list[Radio]: + """Create MA Radios from fixture.""" + fixture_data = load_and_parse_fixture("library_radios") + return [Radio.from_dict(radio_data) for radio_data in fixture_data] + + async def trigger_subscription_callback( hass: HomeAssistant, client: MagicMock, diff --git a/tests/components/music_assistant/fixtures/library_album_tracks.json b/tests/components/music_assistant/fixtures/library_album_tracks.json new file mode 100644 index 00000000000..562ee84fe35 --- /dev/null +++ b/tests/components/music_assistant/fixtures/library_album_tracks.json @@ -0,0 +1,364 @@ +{ + "library_album_tracks": [ + { + "item_id": "247", + "provider": "library", + "name": "Le Mirage", + "version": "", + "sort_name": "mirage, le", + "uri": "library://track/247", + "external_ids": [["isrc", "FR10S1794640"]], + "media_type": "track", + "provider_mappings": [ + { + "item_id": "70953631", + "provider_domain": "tidal", + "provider_instance": "tidal--63Pkq9Aw", + "available": 1, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/track/70953631", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/b7b1897c/57ed/4a31/83d7/9ab3df83183a/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "Dana Murray", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 35, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": true, + "position": null, + "duration": 352, + "artists": [ + { + "item_id": 195, + "provider": "library", + "name": "Dana Jean Phoenix", + "version": "", + "sort_name": "dana jean phoenix", + "uri": "library://artist/195", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + } + ], + "album": { + "item_id": 95, + "provider": "library", + "name": "Synthwave (The 80S Revival)", + "version": "", + "sort_name": "synthwave (the 80s revival)", + "uri": "library://album/95", + "external_ids": [], + "media_type": "album", + "available": true, + "image": { + "type": "thumb", + "path": "https://resources.tidal.com/images/b7b1897c/57ed/4a31/83d7/9ab3df83183a/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + }, + "disc_number": 1, + "track_number": 1 + }, + { + "item_id": "362", + "provider": "library", + "name": "Rabbit in the Headlights", + "version": "", + "sort_name": "rabbit in the headlights", + "uri": "library://track/362", + "external_ids": [["isrc", "GBLFP1645070"]], + "media_type": "track", + "provider_mappings": [ + { + "item_id": "70953636", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": 1, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/track/70953636", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/b7b1897c/57ed/4a31/83d7/9ab3df83183a/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "Michael Oakley", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 34, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": true, + "position": null, + "duration": 253, + "artists": [ + { + "item_id": 90, + "provider": "library", + "name": "Michael Oakley", + "version": "", + "sort_name": "michael oakley", + "uri": "library://artist/90", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + } + ], + "album": { + "item_id": 95, + "provider": "library", + "name": "Synthwave (The 80S Revival)", + "version": "", + "sort_name": "synthwave (the 80s revival)", + "uri": "library://album/95", + "external_ids": [], + "media_type": "album", + "available": true, + "image": { + "type": "thumb", + "path": "https://resources.tidal.com/images/b7b1897c/57ed/4a31/83d7/9ab3df83183a/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + }, + "disc_number": 1, + "track_number": 6 + }, + { + "item_id": "1", + "provider": "library", + "name": "1988 Girls", + "version": "", + "sort_name": "1988 girls", + "uri": "library://track/1", + "external_ids": [["isrc", "DEBL60768604"]], + "media_type": "track", + "provider_mappings": [ + { + "item_id": "70953637", + "provider_domain": "tidal", + "provider_instance": "tidal--56X5qDS7", + "available": 1, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/track/70953637", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/b7b1897c/57ed/4a31/83d7/9ab3df83183a/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "Kiez Beats", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 14, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": true, + "position": null, + "duration": 258, + "artists": [ + { + "item_id": 110, + "provider": "library", + "name": "Futurecop!", + "version": "", + "sort_name": "futurecop!", + "uri": "library://artist/110", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + } + ], + "album": { + "item_id": 95, + "provider": "library", + "name": "Synthwave (The 80S Revival)", + "version": "", + "sort_name": "synthwave (the 80s revival)", + "uri": "library://album/95", + "external_ids": [], + "media_type": "album", + "available": true, + "image": { + "type": "thumb", + "path": "https://resources.tidal.com/images/b7b1897c/57ed/4a31/83d7/9ab3df83183a/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + }, + "disc_number": 1, + "track_number": 7 + }, + { + "item_id": "495", + "provider": "library", + "name": "Timmy Goes to Space", + "version": "", + "sort_name": "timmy goes to space", + "uri": "library://track/495", + "external_ids": [["isrc", "NO2D81710001"]], + "media_type": "track", + "provider_mappings": [ + { + "item_id": "70953643", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": 1, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/track/70953643", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/b7b1897c/57ed/4a31/83d7/9ab3df83183a/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "Jens Kristian Espevik", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 4, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": true, + "position": null, + "duration": 212, + "artists": [ + { + "item_id": 453, + "provider": "library", + "name": "Mr. Maen", + "version": "", + "sort_name": "mr. maen", + "uri": "library://artist/453", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + } + ], + "album": { + "item_id": 95, + "provider": "library", + "name": "Synthwave (The 80S Revival)", + "version": "", + "sort_name": "synthwave (the 80s revival)", + "uri": "library://album/95", + "external_ids": [], + "media_type": "album", + "available": true, + "image": { + "type": "thumb", + "path": "https://resources.tidal.com/images/b7b1897c/57ed/4a31/83d7/9ab3df83183a/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + }, + "disc_number": 1, + "track_number": 13 + } + ] +} diff --git a/tests/components/music_assistant/fixtures/library_albums.json b/tests/components/music_assistant/fixtures/library_albums.json new file mode 100644 index 00000000000..6936a96adc8 --- /dev/null +++ b/tests/components/music_assistant/fixtures/library_albums.json @@ -0,0 +1,148 @@ +{ + "library_albums": [ + { + "item_id": "396", + "provider": "library", + "name": "Synth Punk EP", + "version": "", + "sort_name": "synth punk ep", + "uri": "library://album/396", + "external_ids": [["barcode", "872133626743"]], + "media_type": "album", + "provider_mappings": [ + { + "item_id": "48563817", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": 1, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/album/48563817", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/99c8bc2f/ed43/4fb2/adfb/e7e3157089d2/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "586446 Records DK", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 7, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": false, + "position": null, + "year": 2015, + "artists": [ + { + "item_id": 289, + "provider": "library", + "name": "A Space Love Adventure", + "version": "", + "sort_name": "space love adventure, a", + "uri": "library://artist/289", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + } + ], + "album_type": "ep" + }, + { + "item_id": "95", + "provider": "library", + "name": "Synthwave (The 80S Revival)", + "version": "The 80S Revival", + "sort_name": "synthwave (the 80s revival)", + "uri": "library://album/95", + "external_ids": [["barcode", "3614974086112"]], + "media_type": "album", + "provider_mappings": [ + { + "item_id": "70953630", + "provider_domain": "tidal", + "provider_instance": "tidal--56X5qDS7", + "available": 1, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/album/70953630", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/b7b1897c/57ed/4a31/83d7/9ab3df83183a/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "Kiez Beats", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 43, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": false, + "position": null, + "year": 2017, + "artists": [ + { + "item_id": 96, + "provider": "library", + "name": "Various Artists", + "version": "", + "sort_name": "various artists", + "uri": "library://artist/96", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + } + ], + "album_type": "compilation" + } + ] +} diff --git a/tests/components/music_assistant/fixtures/library_artist_albums.json b/tests/components/music_assistant/fixtures/library_artist_albums.json new file mode 100644 index 00000000000..31885528734 --- /dev/null +++ b/tests/components/music_assistant/fixtures/library_artist_albums.json @@ -0,0 +1,88 @@ +{ + "library_artist_albums": [ + { + "item_id": "115", + "provider": "library", + "name": "A Sea of Stars", + "version": "", + "sort_name": "sea of stars, a", + "uri": "library://album/115", + "external_ids": [["barcode", "859741010126"]], + "media_type": "album", + "provider_mappings": [ + { + "item_id": "157401232", + "provider_domain": "tidal", + "provider_instance": "tidal--56X5qDS7", + "available": 1, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/album/157401232", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/f55c749b/6642/40e3/a291/ff01fd2915cf/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "2021 NRW Records, under exclusive license to NewRetroWave, LLC", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 0, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": false, + "position": null, + "year": 2021, + "artists": [ + { + "item_id": 127, + "provider": "library", + "name": "W O L F C L U B", + "version": "", + "sort_name": "w o l f c l u b", + "uri": "library://artist/127", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + }, + { + "item_id": 128, + "provider": "library", + "name": "Dora Pereli", + "version": "", + "sort_name": "dora pereli", + "uri": "library://artist/128", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + } + ], + "album_type": "single" + } + ] +} diff --git a/tests/components/music_assistant/fixtures/library_artists.json b/tests/components/music_assistant/fixtures/library_artists.json new file mode 100644 index 00000000000..803ce003b6c --- /dev/null +++ b/tests/components/music_assistant/fixtures/library_artists.json @@ -0,0 +1,60 @@ +{ + "library_artists": [ + { + "item_id": "127", + "provider": "library", + "name": "W O L F C L U B", + "version": "", + "sort_name": "w o l f c l u b", + "uri": "library://artist/127", + "external_ids": [], + "media_type": "artist", + "provider_mappings": [ + { + "item_id": "8741977", + "provider_domain": "tidal", + "provider_instance": "tidal--56X5qDS7", + "available": 1, + "audio_format": { + "content_type": "?", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "?", + "bit_rate": 0 + }, + "url": "https://tidal.com/artist/8741977", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": null, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/1e01cdb6/f15d/4d8b/8440/a047976c1cac/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": null, + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": null, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": false, + "position": null + } + ] +} diff --git a/tests/components/music_assistant/fixtures/library_playlist_tracks.json b/tests/components/music_assistant/fixtures/library_playlist_tracks.json new file mode 100644 index 00000000000..1fb1c330957 --- /dev/null +++ b/tests/components/music_assistant/fixtures/library_playlist_tracks.json @@ -0,0 +1,262 @@ +{ + "library_playlist_tracks": [ + { + "item_id": "77616130", + "provider": "tidal--Ah76MuMg", + "name": "Won't Get Fooled Again", + "version": "", + "sort_name": "won't get fooled again", + "uri": "tidal--Ah76MuMg://track/77616130", + "external_ids": [["isrc", "GBUM71405419"]], + "media_type": "track", + "provider_mappings": [ + { + "item_id": "77616130", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": true, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 24, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/track/77616130", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/3496a8ad/ea69/4d7e/bbda/045417ab59e1/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "℗ 1971 Polydor Ltd. (UK)", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 30, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": false, + "position": 0, + "duration": 516, + "artists": [ + { + "item_id": "24915", + "provider": "tidal--Ah76MuMg", + "name": "The Who", + "version": "", + "sort_name": "who, the", + "uri": "tidal--Ah76MuMg://artist/24915", + "external_ids": [], + "media_type": "artist", + "provider_mappings": [ + { + "item_id": "24915", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": true, + "audio_format": { + "content_type": "?", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "?", + "bit_rate": 0 + }, + "url": "https://tidal.com/artist/24915", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": null, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/0f782232/18c8/40b7/bb13/91c6039e40e6/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": null, + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": null, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": false, + "position": null + } + ], + "album": { + "item_id": "77616121", + "provider": "tidal--Ah76MuMg", + "name": "Who's Next", + "version": "", + "sort_name": "who's next", + "uri": "tidal--Ah76MuMg://album/77616121", + "external_ids": [], + "media_type": "album", + "available": true, + "image": null + }, + "disc_number": 1, + "track_number": 9 + }, + { + "item_id": "153795", + "provider": "tidal--Ah76MuMg", + "name": "We're An American Band", + "version": "Remastered 2002", + "sort_name": "we're an american band", + "uri": "tidal--Ah76MuMg://track/153795", + "external_ids": [["isrc", "USCA20200334"]], + "media_type": "track", + "provider_mappings": [ + { + "item_id": "153795", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": true, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/track/153795", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/a6d86e02/84c1/41f7/84f5/41be8571fc40/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "℗ 2002 Capitol Records, LLC", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 48, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": false, + "position": 1, + "duration": 207, + "artists": [ + { + "item_id": "9380", + "provider": "tidal--Ah76MuMg", + "name": "Grand Funk Railroad", + "version": "", + "sort_name": "grand funk railroad", + "uri": "tidal--Ah76MuMg://artist/9380", + "external_ids": [], + "media_type": "artist", + "provider_mappings": [ + { + "item_id": "9380", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": true, + "audio_format": { + "content_type": "?", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "?", + "bit_rate": 0 + }, + "url": "https://tidal.com/artist/9380", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": null, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/6535bf95/a06d/4d23/8262/604fa41d8126/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": null, + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": null, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": false, + "position": null + } + ], + "album": { + "item_id": "153794", + "provider": "tidal--Ah76MuMg", + "name": "We're An American Band (Expanded Edition / Remastered 2002)", + "version": "", + "sort_name": "we're an american band (expanded edition / remastered 2002)", + "uri": "tidal--Ah76MuMg://album/153794", + "external_ids": [], + "media_type": "album", + "available": true, + "image": null + }, + "disc_number": 1, + "track_number": 1 + } + ] +} diff --git a/tests/components/music_assistant/fixtures/library_playlists.json b/tests/components/music_assistant/fixtures/library_playlists.json new file mode 100644 index 00000000000..7f88c5f3e24 --- /dev/null +++ b/tests/components/music_assistant/fixtures/library_playlists.json @@ -0,0 +1,63 @@ +{ + "library_playlists": [ + { + "item_id": "40", + "provider": "library", + "name": "1970s Rock Hits", + "version": "", + "sort_name": "1970s rock hits", + "uri": "library://playlist/40", + "external_ids": [], + "media_type": "playlist", + "provider_mappings": [ + { + "item_id": "30da0578-0ca0-4716-b66e-5f02bcd96702", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": 1, + "audio_format": { + "content_type": "?", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "?", + "bit_rate": 0 + }, + "url": "https://tidal.com/browse/playlist/30da0578-0ca0-4716-b66e-5f02bcd96702", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": null, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/95913801/41c1/4cc9/bf94/a0fba657bba5/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": null, + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": null, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": true, + "position": null, + "owner": "TIDAL", + "is_editable": 0, + "cache_checksum": "2023-10-09 07: 09: 23.446000+00: 00" + } + ] +} diff --git a/tests/components/music_assistant/fixtures/library_radios.json b/tests/components/music_assistant/fixtures/library_radios.json new file mode 100644 index 00000000000..1a6a4666ce4 --- /dev/null +++ b/tests/components/music_assistant/fixtures/library_radios.json @@ -0,0 +1,66 @@ +{ + "library_radios": [ + { + "item_id": "1", + "provider": "library", + "name": "fm4 | ORF | HQ", + "version": "", + "sort_name": "fm4 | orf | hq", + "uri": "library://radio/1", + "external_ids": [], + "media_type": "radio", + "provider_mappings": [ + { + "item_id": "1e13ed4e-daa9-4728-8550-e08d89c1c8e7", + "provider_domain": "radiobrowser", + "provider_instance": "radiobrowser--FRc3pD3t", + "available": 1, + "audio_format": { + "content_type": "?", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "?", + "bit_rate": 0 + }, + "url": null, + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": null, + "images": [ + { + "type": "thumb", + "path": "https://tubestatic.orf.at/mojo/1_3/storyserver//tube/fm4/images/touch-icon-iphone-retina.png", + "provider": "radiobrowser", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": null, + "lyrics": null, + "label": null, + "links": [ + { + "type": "website", + "url": "https://fm4.orf.at/" + } + ], + "performers": null, + "preview": null, + "popularity": 166, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": true, + "position": null, + "duration": 172800 + } + ] +} diff --git a/tests/components/music_assistant/fixtures/library_tracks.json b/tests/components/music_assistant/fixtures/library_tracks.json new file mode 100644 index 00000000000..c4ed83e9342 --- /dev/null +++ b/tests/components/music_assistant/fixtures/library_tracks.json @@ -0,0 +1,556 @@ +{ + "library_tracks": [ + { + "item_id": "456", + "provider": "library", + "name": "Tennessee Whiskey", + "version": "", + "sort_name": "tennessee whiskey", + "uri": "library://track/456", + "external_ids": [["isrc", "USUM71418088"]], + "media_type": "track", + "provider_mappings": [ + { + "item_id": "44832786", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": 1, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/track/44832786", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/4894ff62/9de2/4ed8/a7b9/69e217bbbdda/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "℗ 2015 Mercury Records, a Division of UMG Recordings, Inc.", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 33, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": true, + "position": null, + "duration": 293, + "artists": [ + { + "item_id": 433, + "provider": "library", + "name": "Chris Stapleton", + "version": "", + "sort_name": "chris stapleton", + "uri": "library://artist/433", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + } + ], + "album": { + "item_id": 463, + "provider": "library", + "name": "Traveller", + "version": "", + "sort_name": "traveller", + "uri": "library://album/463", + "external_ids": [], + "media_type": "album", + "available": true, + "image": { + "type": "thumb", + "path": "https://resources.tidal.com/images/4894ff62/9de2/4ed8/a7b9/69e217bbbdda/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + }, + "disc_number": 1, + "track_number": 3 + }, + { + "item_id": "467", + "provider": "library", + "name": "Thelma + Louise", + "version": "", + "sort_name": "thelma + louise", + "uri": "library://track/467", + "external_ids": [["isrc", "GBUM72104380"]], + "media_type": "track", + "provider_mappings": [ + { + "item_id": "194027388", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": 1, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 24, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/track/194027388", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/04fc7c3c/b814/4855/874c/a2e456205b65/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "℗ 2021 Virgin Records Limited", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 20, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": true, + "position": null, + "duration": 137, + "artists": [ + { + "item_id": 81, + "provider": "library", + "name": "Bastille", + "version": "", + "sort_name": "bastille", + "uri": "library://artist/81", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + } + ], + "album": { + "item_id": 471, + "provider": "library", + "name": "Thelma + Louise", + "version": "", + "sort_name": "thelma + louise", + "uri": "library://album/471", + "external_ids": [], + "media_type": "album", + "available": true, + "image": { + "type": "thumb", + "path": "https://resources.tidal.com/images/04fc7c3c/b814/4855/874c/a2e456205b65/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + }, + "disc_number": 1, + "track_number": 1 + }, + { + "item_id": "485", + "provider": "library", + "name": "They Don't Care About Us", + "version": "", + "sort_name": "they don't care about us", + "uri": "library://track/485", + "external_ids": [["isrc", "USSM19500629"]], + "media_type": "track", + "provider_mappings": [ + { + "item_id": "5279069", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": 1, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 24, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/track/5279069", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/a2fa5815/851d/4d2d/b6a7/17a365c838f9/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "(P) 1995 MJJ Productions Inc.", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 27, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": true, + "position": null, + "duration": 284, + "artists": [ + { + "item_id": 30, + "provider": "library", + "name": "Michael Jackson", + "version": "", + "sort_name": "michael jackson", + "uri": "library://artist/30", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + } + ], + "album": { + "item_id": 486, + "provider": "library", + "name": "HIStory - PAST, PRESENT AND FUTURE - BOOK I", + "version": "", + "sort_name": "history - past, present and future - book i", + "uri": "library://album/486", + "external_ids": [], + "media_type": "album", + "available": true, + "image": { + "type": "thumb", + "path": "https://resources.tidal.com/images/a2fa5815/851d/4d2d/b6a7/17a365c838f9/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + }, + "disc_number": 2, + "track_number": 2 + }, + { + "item_id": "486", + "provider": "library", + "name": "They Don't Give A F**** About Us", + "version": "", + "sort_name": "they don't give a f**** about us", + "uri": "library://track/486", + "external_ids": [["isrc", "USIR10211795"]], + "media_type": "track", + "provider_mappings": [ + { + "item_id": "44066854", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": 1, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/track/44066854", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": true, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/6b7b2b58/5dc2/4d0c/8979/7b30bb779d6f/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "℗ 2002 Amaru Entertainment, Inc., Under exclusive license to Interscope Records", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 34, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": true, + "position": null, + "duration": 306, + "artists": [ + { + "item_id": 159, + "provider": "library", + "name": "2Pac", + "version": "", + "sort_name": "2pac", + "uri": "library://artist/159", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + }, + { + "item_id": 451, + "provider": "library", + "name": "The Outlawz", + "version": "", + "sort_name": "outlawz, the", + "uri": "library://artist/451", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + } + ], + "album": { + "item_id": 487, + "provider": "library", + "name": "Better Dayz", + "version": "", + "sort_name": "better dayz", + "uri": "library://album/487", + "external_ids": [], + "media_type": "album", + "available": true, + "image": { + "type": "thumb", + "path": "https://resources.tidal.com/images/6b7b2b58/5dc2/4d0c/8979/7b30bb779d6f/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + }, + "disc_number": 2, + "track_number": 13 + }, + { + "item_id": "487", + "provider": "library", + "name": "Things We Lost In The Fire", + "version": "TORN Remix", + "sort_name": "things we lost in the fire", + "uri": "library://track/487", + "external_ids": [["isrc", "GBUM71304903"]], + "media_type": "track", + "provider_mappings": [ + { + "item_id": "22627902", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": 1, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 16, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/track/22627902", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/de277fd3/cc29/4d63/a60f/13b501c5f3d0/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "℗ 2013 Virgin Records Limited", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 10, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": true, + "position": null, + "duration": 323, + "artists": [ + { + "item_id": 81, + "provider": "library", + "name": "Bastille", + "version": "", + "sort_name": "bastille", + "uri": "library://artist/81", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + } + ], + "album": { + "item_id": 488, + "provider": "library", + "name": "Things We Lost In The Fire", + "version": "", + "sort_name": "things we lost in the fire", + "uri": "library://album/488", + "external_ids": [], + "media_type": "album", + "available": true, + "image": { + "type": "thumb", + "path": "https://resources.tidal.com/images/de277fd3/cc29/4d63/a60f/13b501c5f3d0/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + }, + "disc_number": 1, + "track_number": 3 + }, + { + "item_id": "488", + "provider": "library", + "name": "Those Nights", + "version": "", + "sort_name": "those nights", + "uri": "library://track/488", + "external_ids": [["isrc", "GBUM71803866"]], + "media_type": "track", + "provider_mappings": [ + { + "item_id": "110750762", + "provider_domain": "tidal", + "provider_instance": "tidal--Ah76MuMg", + "available": 1, + "audio_format": { + "content_type": "flac", + "sample_rate": 44100, + "bit_depth": 24, + "channels": 2, + "output_format_str": "flac", + "bit_rate": 0 + }, + "url": "https://tidal.com/track/110750762", + "details": null + } + ], + "metadata": { + "description": null, + "review": null, + "explicit": false, + "images": [ + { + "type": "thumb", + "path": "https://resources.tidal.com/images/713805f3/c08c/4c0f/8199/d63e6badac0d/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + ], + "genres": null, + "mood": null, + "style": null, + "copyright": "℗ 2019 Virgin Records Limited", + "lyrics": null, + "label": null, + "links": null, + "performers": null, + "preview": null, + "popularity": 21, + "release_date": null, + "languages": null, + "last_refresh": null + }, + "favorite": true, + "position": null, + "duration": 270, + "artists": [ + { + "item_id": 81, + "provider": "library", + "name": "Bastille", + "version": "", + "sort_name": "bastille", + "uri": "library://artist/81", + "external_ids": [], + "media_type": "artist", + "available": true, + "image": null + } + ], + "album": { + "item_id": 489, + "provider": "library", + "name": "Doom Days", + "version": "", + "sort_name": "doom days", + "uri": "library://album/489", + "external_ids": [], + "media_type": "album", + "available": true, + "image": { + "type": "thumb", + "path": "https://resources.tidal.com/images/713805f3/c08c/4c0f/8199/d63e6badac0d/750x750.jpg", + "provider": "tidal", + "remotely_accessible": true + } + }, + "disc_number": 1, + "track_number": 10 + } + ] +} diff --git a/tests/components/music_assistant/test_media_browser.py b/tests/components/music_assistant/test_media_browser.py new file mode 100644 index 00000000000..96fd54962d8 --- /dev/null +++ b/tests/components/music_assistant/test_media_browser.py @@ -0,0 +1,65 @@ +"""Test Music Assistant media browser implementation.""" + +from unittest.mock import MagicMock + +import pytest + +from homeassistant.components.media_player import BrowseError, BrowseMedia, MediaType +from homeassistant.components.music_assistant.const import DOMAIN +from homeassistant.components.music_assistant.media_browser import ( + LIBRARY_ALBUMS, + LIBRARY_ARTISTS, + LIBRARY_PLAYLISTS, + LIBRARY_RADIO, + LIBRARY_TRACKS, + async_browse_media, +) +from homeassistant.core import HomeAssistant + +from .common import setup_integration_from_fixtures + + +@pytest.mark.parametrize( + ("media_content_id", "media_content_type", "expected"), + [ + (LIBRARY_PLAYLISTS, MediaType.PLAYLIST, "library://playlist/40"), + (LIBRARY_ARTISTS, MediaType.ARTIST, "library://artist/127"), + (LIBRARY_ALBUMS, MediaType.ALBUM, "library://album/396"), + (LIBRARY_TRACKS, MediaType.TRACK, "library://track/486"), + (LIBRARY_RADIO, DOMAIN, "library://radio/1"), + ("artist", MediaType.ARTIST, "library://album/115"), + ("album", MediaType.ALBUM, "library://track/247"), + ("playlist", DOMAIN, "tidal--Ah76MuMg://track/77616130"), + (None, None, "artists"), + ], +) +async def test_browse_media_root( + hass: HomeAssistant, + music_assistant_client: MagicMock, + media_content_id: str, + media_content_type: str, + expected: str, +) -> None: + """Test the async_browse_media method.""" + await setup_integration_from_fixtures(hass, music_assistant_client) + entity_id = "media_player.test_player_1" + state = hass.states.get(entity_id) + assert state + browse_item: BrowseMedia = await async_browse_media( + hass, music_assistant_client, media_content_id, media_content_type + ) + assert browse_item.children[0].media_content_id == expected + + +async def test_browse_media_not_found( + hass: HomeAssistant, + music_assistant_client: MagicMock, +) -> None: + """Test the async_browse_media method when media is not found.""" + await setup_integration_from_fixtures(hass, music_assistant_client) + entity_id = "media_player.test_player_1" + state = hass.states.get(entity_id) + assert state + + with pytest.raises(BrowseError, match="Media not found: unknown / unknown"): + await async_browse_media(hass, music_assistant_client, "unknown", "unknown")