From: Marcel van der Veldt Date: Fri, 5 Dec 2025 15:10:20 +0000 (+0100) Subject: Phase out lookup key (#2751) X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=cdcd898b820d25e045589ed078f65df8fb88d374;p=music-assistant-server.git Phase out lookup key (#2751) --- diff --git a/music_assistant/controllers/config.py b/music_assistant/controllers/config.py index 34d8039d..0dd0ac7f 100644 --- a/music_assistant/controllers/config.py +++ b/music_assistant/controllers/config.py @@ -496,7 +496,7 @@ class ConfigController: if raw_conf := self.get(f"{CONF_PLAYERS}/{player_id}"): if player := self.mass.players.get(player_id, False): raw_conf["default_name"] = player.display_name - raw_conf["provider"] = player.provider.lookup_key + raw_conf["provider"] = player.provider.instance_id # pass action and values to get_config_entries if values is None: values = raw_conf.get("values", {}) diff --git a/music_assistant/controllers/media/audiobooks.py b/music_assistant/controllers/media/audiobooks.py index 6ef0bb09..20bf2dfd 100644 --- a/music_assistant/controllers/media/audiobooks.py +++ b/music_assistant/controllers/media/audiobooks.py @@ -277,14 +277,12 @@ class AudiobooksController(MediaControllerBase[Audiobook]): # cleanup provider specific entries for this item # we always prefer the library playlog entry for prov_mapping in media_item.provider_mappings: - if not (provider := self.mass.get_provider(prov_mapping.provider_instance)): - continue await self.mass.music.database.delete( DB_TABLE_PLAYLOG, { "media_type": self.media_type.value, "item_id": prov_mapping.item_id, - "provider": provider.lookup_key, + "provider": prov_mapping.provider_instance, }, ) if media_item.fully_played is None and media_item.resume_position_ms is None: diff --git a/music_assistant/controllers/media/base.py b/music_assistant/controllers/media/base.py index 4adaeee3..9561e606 100644 --- a/music_assistant/controllers/media/base.py +++ b/music_assistant/controllers/media/base.py @@ -162,8 +162,8 @@ class MediaControllerBase[ItemCls: "MediaItemType"](metaclass=ABCMeta): # search by (exact) name match query = f"{self.db_table}.name = :name OR {self.db_table}.sort_name = :sort_name" query_params = {"name": item.name, "sort_name": item.sort_name} - async for db_item in self.iter_library_items( - extra_query=query, extra_query_params=query_params + for db_item in await self._get_library_items_by_query( + extra_query_parts=[query], extra_query_params=query_params ): if compare_media_item(db_item, item, True): return int(db_item.item_id) @@ -263,15 +263,19 @@ class MediaControllerBase[ItemCls: "MediaItemType"](metaclass=ABCMeta): """Iterate all in-database items.""" limit: int = 500 offset: int = 0 + if provider is not None: + provider_filter = provider if isinstance(provider, list) else [provider] + else: + provider_filter = None while True: - next_items = await self.library_items( + next_items = await self._get_library_items_by_query( favorite=favorite, search=search, limit=limit, offset=offset, order_by=order_by, - provider=provider, - extra_query=extra_query, + provider_filter=provider_filter, + extra_query_parts=[extra_query] if extra_query else None, extra_query_params=extra_query_params, ) for item in next_items: @@ -348,7 +352,9 @@ class MediaControllerBase[ItemCls: "MediaItemType"](metaclass=ABCMeta): """Get single library item by id.""" db_id = int(item_id) # ensure integer extra_query = f"WHERE {self.db_table}.item_id = {item_id}" - async for db_item in self.iter_library_items(extra_query=extra_query): + for db_item in await self._get_library_items_by_query( + extra_query_parts=[extra_query], + ): return db_item msg = f"{self.media_type.value} not found in library: {db_id}" raise MediaNotFoundError(msg) diff --git a/music_assistant/controllers/media/playlists.py b/music_assistant/controllers/media/playlists.py index 592a965e..f4fa8e26 100644 --- a/music_assistant/controllers/media/playlists.py +++ b/music_assistant/controllers/media/playlists.py @@ -249,7 +249,7 @@ class PlaylistController(MediaControllerBase[Playlist]): provider_instance_id_or_domain, ) continue - if item_prov.lookup_key == playlist_prov.lookup_key: + if item_prov.instance_id == playlist_prov.instance_id: if item_id not in ids_to_add: ids_to_add.append(item_id) continue @@ -285,7 +285,7 @@ class PlaylistController(MediaControllerBase[Playlist]): continue track_version_uri = create_uri( MediaType.TRACK, - item_prov.lookup_key, + item_prov.instance_id, track_version.item_id, ) if track_version_uri in cur_playlist_track_uris: @@ -305,7 +305,7 @@ class PlaylistController(MediaControllerBase[Playlist]): playlist.name, ) break - if item_prov.lookup_key == playlist_prov.lookup_key: + if item_prov.instance_id == playlist_prov.instance_id: if track_version.item_id not in ids_to_add: ids_to_add.append(track_version.item_id) self.logger.info( diff --git a/music_assistant/controllers/media/podcasts.py b/music_assistant/controllers/media/podcasts.py index 59183eb8..d1326796 100644 --- a/music_assistant/controllers/media/podcasts.py +++ b/music_assistant/controllers/media/podcasts.py @@ -226,7 +226,7 @@ class PodcastsController(MediaControllerBase[Podcast]): DB_TABLE_PLAYLOG, { "item_id": episode.item_id, - "provider": prov.lookup_key, + "provider": prov.instance_id, "media_type": MediaType.PODCAST_EPISODE, }, ) diff --git a/music_assistant/controllers/metadata.py b/music_assistant/controllers/metadata.py index d7ed45a6..26d85518 100644 --- a/music_assistant/controllers/metadata.py +++ b/music_assistant/controllers/metadata.py @@ -55,6 +55,7 @@ from music_assistant.helpers.compare import compare_strings from music_assistant.helpers.images import create_collage, get_image_thumb from music_assistant.helpers.throttle_retry import Throttler from music_assistant.models.core_controller import CoreController +from music_assistant.models.music_provider import MusicProvider if TYPE_CHECKING: from music_assistant_models.config_entries import CoreConfig @@ -524,12 +525,16 @@ class MetaDataController(CoreController): for prov_mapping in sorted( artist.provider_mappings, key=lambda x: x.priority, reverse=True ): - if (prov := self.mass.get_provider(prov_mapping.provider_instance)) is None: + prov = self.mass.get_provider( + prov_mapping.provider_instance, provider_type=MusicProvider + ) + if prov is None: continue - if prov.lookup_key in unique_keys: + # prefer domain for streaming providers as the catalog is the same across instances + prov_key = prov.domain if prov.is_streaming_provider else prov.instance_id + if prov_key in unique_keys: continue - if prov.lookup_key not in local_provs: - unique_keys.add(prov.lookup_key) + unique_keys.add(prov_key) with suppress(MediaNotFoundError): prov_item = await self.mass.music.artists.get_provider_item( prov_mapping.item_id, prov_mapping.provider_instance @@ -577,16 +582,17 @@ class MetaDataController(CoreController): # note that we sort the providers by priority so that we always # prefer local providers over online providers unique_keys: set[str] = set() - local_provs = get_global_cache_value("non_streaming_providers") - if TYPE_CHECKING: - local_provs = cast("set[str]", local_provs) for prov_mapping in sorted(album.provider_mappings, key=lambda x: x.priority, reverse=True): - if (prov := self.mass.get_provider(prov_mapping.provider_instance)) is None: + prov = self.mass.get_provider( + prov_mapping.provider_instance, provider_type=MusicProvider + ) + if prov is None: continue - if prov.lookup_key in unique_keys: + # prefer domain for streaming providers as the catalog is the same across instances + prov_key = prov.domain if prov.is_streaming_provider else prov.instance_id + if prov_key in unique_keys: continue - if prov.lookup_key not in local_provs: - unique_keys.add(prov.lookup_key) + unique_keys.add(prov_key) with suppress(MediaNotFoundError): prov_item = await self.mass.music.albums.get_provider_item( prov_mapping.item_id, prov_mapping.provider_instance @@ -632,15 +638,17 @@ class MetaDataController(CoreController): # note that we sort the providers by priority so that we always # prefer local providers over online providers unique_keys: set[str] = set() - local_provs = get_global_cache_value("non_streaming_providers") - if TYPE_CHECKING: - local_provs = cast("set[str]", local_provs) for prov_mapping in sorted(track.provider_mappings, key=lambda x: x.priority, reverse=True): - if (prov := self.mass.get_provider(prov_mapping.provider_instance)) is None: + prov = self.mass.get_provider( + prov_mapping.provider_instance, provider_type=MusicProvider + ) + if prov is None: continue - if prov.lookup_key in unique_keys: + # prefer domain for streaming providers as the catalog is the same across instances + prov_key = prov.domain if prov.is_streaming_provider else prov.instance_id + if prov_key in unique_keys: continue - unique_keys.add(prov.lookup_key) + unique_keys.add(prov_key) with suppress(MediaNotFoundError): prov_item = await self.mass.music.tracks.get_provider_item( prov_mapping.item_id, prov_mapping.provider_instance @@ -762,18 +770,19 @@ class MetaDataController(CoreController): # note that we sort the providers by priority so that we always # prefer local providers over online providers unique_keys: set[str] = set() - local_provs = get_global_cache_value("non_streaming_providers") - if TYPE_CHECKING: - local_provs = cast("set[str]", local_provs) for prov_mapping in sorted( audiobook.provider_mappings, key=lambda x: x.priority, reverse=True ): - if (prov := self.mass.get_provider(prov_mapping.provider_instance)) is None: + prov = self.mass.get_provider( + prov_mapping.provider_instance, provider_type=MusicProvider + ) + if prov is None: continue - if prov.lookup_key in unique_keys: + # prefer domain for streaming providers as the catalog is the same across instances + prov_key = prov.domain if prov.is_streaming_provider else prov.instance_id + if prov_key in unique_keys: continue - if prov.lookup_key not in local_provs: - unique_keys.add(prov.lookup_key) + unique_keys.add(prov_key) with suppress(MediaNotFoundError): prov_item = await self.mass.music.audiobooks.get_provider_item( prov_mapping.item_id, prov_mapping.provider_instance @@ -810,18 +819,19 @@ class MetaDataController(CoreController): # note that we sort the providers by priority so that we always # prefer local providers over online providers unique_keys: set[str] = set() - local_provs = get_global_cache_value("non_streaming_providers") - if TYPE_CHECKING: - local_provs = cast("set[str]", local_provs) for prov_mapping in sorted( podcast.provider_mappings, key=lambda x: x.priority, reverse=True ): - if (prov := self.mass.get_provider(prov_mapping.provider_instance)) is None: + prov = self.mass.get_provider( + prov_mapping.provider_instance, provider_type=MusicProvider + ) + if prov is None: continue - if prov.lookup_key in unique_keys: + # prefer domain for streaming providers as the catalog is the same across instances + prov_key = prov.domain if prov.is_streaming_provider else prov.instance_id + if prov_key in unique_keys: continue - if prov.lookup_key not in local_provs: - unique_keys.add(prov.lookup_key) + unique_keys.add(prov_key) with suppress(MediaNotFoundError): prov_item = await self.mass.music.podcasts.get_provider_item( prov_mapping.item_id, prov_mapping.provider_instance diff --git a/music_assistant/controllers/music.py b/music_assistant/controllers/music.py index 98f036bb..b691f494 100644 --- a/music_assistant/controllers/music.py +++ b/music_assistant/controllers/music.py @@ -474,7 +474,7 @@ class MusicController(CoreController): if media_types is None: media_types = MediaType.ALL media_types_str = "(" + ",".join(f'"{x}"' for x in media_types) + ")" - available_providers = ("library", *get_global_cache_value("unique_providers", [])) + available_providers = ("library", *self.get_unique_providers()) available_providers_str = "(" + ",".join(f'"{x}"' for x in available_providers) + ")" query = ( f"SELECT * FROM {DB_TABLE_PLAYLOG} " @@ -521,7 +521,7 @@ class MusicController(CoreController): self, limit: int = 10, all_users: bool = False ) -> list[ItemMapping]: """Return a list of the Audiobooks and PodcastEpisodes that are in progress.""" - available_providers = ("library", *get_global_cache_value("unique_providers", [])) + available_providers = ("library", *self.get_unique_providers()) available_providers_str = "(" + ",".join(f'"{x}"' for x in available_providers) + ")" query = ( f"SELECT * FROM {DB_TABLE_PLAYLOG} " @@ -885,10 +885,12 @@ class MusicController(CoreController): if loudness in (None, inf, -inf): # skip invalid values return + # prefer domain for streaming providers as the catalog is the same across instances + prov_key = provider.domain if provider.is_streaming_provider else provider.instance_id values = { "item_id": item_id, "media_type": media_type.value, - "provider": provider.lookup_key, + "provider": prov_key, "loudness": loudness, } if album_loudness not in (None, inf, -inf): @@ -914,10 +916,12 @@ class MusicController(CoreController): return beats_json = await asyncio.to_thread(lambda: json_dumps(analysis.beats.tolist())) downbeats_json = await asyncio.to_thread(lambda: json_dumps(analysis.downbeats.tolist())) + # prefer domain for streaming providers as the catalog is the same across instances + prov_key = provider.domain if provider.is_streaming_provider else provider.instance_id values = { "fragment": analysis.fragment.value, "item_id": item_id, - "provider": provider.lookup_key, + "provider": prov_key, "bpm": analysis.bpm, "beats": beats_json, "downbeats": downbeats_json, @@ -935,11 +939,13 @@ class MusicController(CoreController): """Get Smart Fades BPM analysis for a track from db.""" if not (provider := self.mass.get_provider(provider_instance_id_or_domain)): return None + # prefer domain for streaming providers as the catalog is the same across instances + prov_key = provider.domain if provider.is_streaming_provider else provider.instance_id db_row = await self.database.get_row( DB_TABLE_SMART_FADES_ANALYSIS, { "item_id": item_id, - "provider": provider.lookup_key, + "provider": prov_key, "fragment": fragment.value, }, ) @@ -965,12 +971,14 @@ class MusicController(CoreController): """Get (EBU-R128) Integrated Loudness Measurement for a mediaitem in db.""" if not (provider := self.mass.get_provider(provider_instance_id_or_domain)): return None + # prefer domain for streaming providers as the catalog is the same across instances + prov_key = provider.domain if provider.is_streaming_provider else provider.instance_id db_row = await self.database.get_row( DB_TABLE_LOUDNESS_MEASUREMENTS, { "item_id": item_id, "media_type": media_type.value, - "provider": provider.lookup_key, + "provider": prov_key, }, ) if db_row and db_row["loudness"] != inf and db_row["loudness"] != -inf: @@ -1207,18 +1215,18 @@ class MusicController(CoreController): # Try to get position from providers for prov_mapping in media_item.provider_mappings: - if not (provider := self.mass.get_provider(prov_mapping.provider_instance)): + if not ( + provider := self.mass.get_provider( + prov_mapping.provider_instance, provider_type=MusicProvider + ) + ): continue - # Type guard: ensure this is a MusicProvider with get_resume_position method - if isinstance(provider, MusicProvider): - with suppress(NotImplementedError): - ( - provider_fully_played, - provider_position_ms, - ) = await provider.get_resume_position( - prov_mapping.item_id, media_item.media_type - ) - break # Use first provider that returns data + with suppress(NotImplementedError): + ( + provider_fully_played, + provider_position_ms, + ) = await provider.get_resume_position(prov_mapping.item_id, media_item.media_type) + break # Use first provider that returns data # Get MA's internal position from playlog ma_fully_played = False @@ -1275,18 +1283,18 @@ class MusicController(CoreController): def get_unique_providers(self) -> set[str]: """ - Return all unique MusicProvider instance ids. + Return all unique MusicProvider (instance or domain) ids. - This will return all filebased instances but only one instance - for streaming providers. + This will return instance_ids for non-streaming providers + and domain names for streaming providers to avoid duplicates. """ - instances = set() - domains = set() + result: set[str] = set() for provider in self.providers: - if provider.domain not in domains or not provider.is_streaming_provider: - instances.add(provider.instance_id) - domains.add(provider.domain) - return instances + if provider.is_streaming_provider: + result.add(provider.domain) + else: + result.add(provider.instance_id) + return result async def cleanup_provider(self, provider_instance: str) -> None: """Cleanup provider records from the database.""" diff --git a/music_assistant/controllers/players/player_controller.py b/music_assistant/controllers/players/player_controller.py index 39e38104..83d23c7e 100644 --- a/music_assistant/controllers/players/player_controller.py +++ b/music_assistant/controllers/players/player_controller.py @@ -211,7 +211,7 @@ class PlayerController(CoreController): for player in self._players.values() if (player.available or return_unavailable) and (player.enabled or return_disabled) - and (provider_filter is None or player.provider.lookup_key == provider_filter) + and (provider_filter is None or player.provider.instance_id == provider_filter) and (not user_filter or player.player_id in user_filter) and (return_sync_groups or not isinstance(player, SyncGroupPlayer)) ] @@ -978,7 +978,7 @@ class PlayerController(CoreController): # check if player can be synced/grouped with the target player if not ( child_player_id in parent_player.can_group_with - or child_player.provider.lookup_key in parent_player.can_group_with + or child_player.provider.instance_id in parent_player.can_group_with or "*" in parent_player.can_group_with ): raise UnsupportedFeaturedException( diff --git a/music_assistant/controllers/players/sync_groups.py b/music_assistant/controllers/players/sync_groups.py index 38366b23..a62b0de5 100644 --- a/music_assistant/controllers/players/sync_groups.py +++ b/music_assistant/controllers/players/sync_groups.py @@ -179,7 +179,7 @@ class SyncGroupPlayer(GroupPlayer): if self.is_dynamic and (leader := self.sync_leader): return leader.can_group_with elif self.is_dynamic: - return {self.provider.lookup_key} + return {self.provider.instance_id} else: return set() @@ -565,7 +565,7 @@ class SyncGroupController: player_id = f"{SYNCGROUP_PREFIX}{shortuuid.random(8).lower()}" self.mass.config.create_default_player_config( player_id=player_id, - provider=provider.lookup_key, + provider=provider.instance_id, name=name, enabled=True, values={ @@ -593,7 +593,7 @@ class SyncGroupController: async def on_provider_loaded(self, provider: PlayerProvider) -> None: """Handle logic when a provider is loaded.""" # register existing syncgroup players for this provider - for player_conf in await self.mass.config.get_player_configs(provider.lookup_key): + for player_conf in await self.mass.config.get_player_configs(provider.instance_id): if player_conf.player_id.startswith(SYNCGROUP_PREFIX): await self._register_syncgroup_player(player_conf.player_id, provider) @@ -601,7 +601,7 @@ class SyncGroupController: """Handle logic when a provider is (about to get) unloaded.""" # unregister existing syncgroup players for this provider for player in self.mass.players.all( - provider_filter=provider.lookup_key, return_sync_groups=True + provider_filter=provider.instance_id, return_sync_groups=True ): if player.player_id.startswith(SYNCGROUP_PREFIX): await self.mass.players.unregister(player.player_id, False) diff --git a/music_assistant/helpers/audio.py b/music_assistant/helpers/audio.py index 5e918b47..0674b009 100644 --- a/music_assistant/helpers/audio.py +++ b/music_assistant/helpers/audio.py @@ -239,6 +239,7 @@ async def get_stream_details( This is called just-in-time when a PlayerQueue wants a MediaItem to be played. Do not try to request streamdetails too much in advance as this is expiring data. """ + streamdetails: StreamDetails | None = None time_start = time.time() LOGGER.debug("Getting streamdetails for %s", queue_item.uri) if seek_position and (queue_item.media_type == MediaType.RADIO or not queue_item.duration): @@ -259,36 +260,56 @@ async def get_stream_details( # already got a fresh/unused (or unexpired) streamdetails streamdetails = queue_item.streamdetails else: + # need to (re)create streamdetails # retrieve streamdetails from provider + media_item = queue_item.media_item assert media_item is not None # for type checking - # sort by quality and check item's availability - for prov_media in sorted( - media_item.provider_mappings, key=lambda x: x.quality or 0, reverse=True + preferred_providers: list[str] = [] + if ( + (queue := mass.player_queues.get(queue_item.queue_id)) + and queue.userid + and (playback_user := await mass.webserver.auth.get_user(queue.userid)) + and playback_user.provider_filter ): - if not prov_media.available: - LOGGER.debug(f"Skipping unavailable {prov_media}") - continue - # guard that provider is available - music_prov = mass.get_provider(prov_media.provider_instance) - if TYPE_CHECKING: # avoid circular import - assert isinstance(music_prov, MusicProvider) - if not music_prov: - LOGGER.debug(f"Skipping {prov_media} - provider not available") - continue # provider not available ? - # get streamdetails from provider - try: - BYPASS_THROTTLER.set(True) - streamdetails = await music_prov.get_stream_details( - prov_media.item_id, media_item.media_type - ) - except MusicAssistantError as err: - LOGGER.warning(str(err)) - else: - break - finally: - BYPASS_THROTTLER.set(False) + # handle steering into user preferred providerinstance + preferred_providers = playback_user.provider_filter else: + preferred_providers = [x.provider_instance for x in media_item.provider_mappings] + for allow_other_provider in (False, True): + # sort by quality and check item's availability + for prov_media in sorted( + media_item.provider_mappings, key=lambda x: x.quality or 0, reverse=True + ): + if not prov_media.available: + LOGGER.debug(f"Skipping unavailable {prov_media}") + continue + if ( + not allow_other_provider + and prov_media.provider_instance not in preferred_providers + ): + continue + # guard that provider is available + music_prov = mass.get_provider(prov_media.provider_instance) + if TYPE_CHECKING: # avoid circular import + assert isinstance(music_prov, MusicProvider) + if not music_prov: + LOGGER.debug(f"Skipping {prov_media} - provider not available") + continue # provider not available ? + # get streamdetails from provider + try: + BYPASS_THROTTLER.set(True) + streamdetails = await music_prov.get_stream_details( + prov_media.item_id, media_item.media_type + ) + except MusicAssistantError as err: + LOGGER.warning(str(err)) + else: + break + finally: + BYPASS_THROTTLER.set(False) + + if not streamdetails: msg = f"Unable to retrieve streamdetails for {queue_item.name} ({queue_item.uri})" raise MediaNotFoundError(msg) diff --git a/music_assistant/helpers/podcast_parsers.py b/music_assistant/helpers/podcast_parsers.py index 0ff5a825..886eb601 100644 --- a/music_assistant/helpers/podcast_parsers.py +++ b/music_assistant/helpers/podcast_parsers.py @@ -43,9 +43,8 @@ def parse_podcast( *, feed_url: str, parsed_feed: dict[str, Any], - lookup_key: str, - domain: str, instance_id: str, + domain: str, mass_item_id: str | None = None, ) -> Podcast: """Podcast -> Mass Podcast. @@ -58,7 +57,7 @@ def parse_podcast( item_id=item_id, name=parsed_feed.get("title", "NO_TITLE"), publisher=publisher, - provider=lookup_key, + provider=instance_id, uri=parsed_feed.get("link"), provider_mappings={ ProviderMapping( @@ -91,7 +90,7 @@ def parse_podcast( MediaItemImage( type=ImageType.THUMB, path=podcast_cover, - provider=lookup_key, + provider=instance_id, remotely_accessible=True, ) ] @@ -123,9 +122,8 @@ def parse_podcast_episode( prov_podcast_id: str, episode_cnt: int, podcast_cover: str | None = None, - lookup_key: str, - domain: str, instance_id: str, + domain: str, mass_item_id: str | None = None, ) -> PodcastEpisode | None: """Podcast Episode -> Mass Podcast Episode. @@ -157,13 +155,13 @@ def parse_podcast_episode( episode_id = f"{prov_podcast_id} {guid_or_stream_url}" if mass_item_id is None else mass_item_id mass_episode = PodcastEpisode( item_id=episode_id, - provider=lookup_key, + provider=instance_id, name=episode_title, duration=int(episode_duration), position=episode_cnt, podcast=ItemMapping( item_id=prov_podcast_id, - provider=lookup_key, + provider=instance_id, name=episode_title, media_type=MediaType.PODCAST, ), @@ -200,7 +198,7 @@ def parse_podcast_episode( MediaItemImage( type=ImageType.THUMB, path=episode_cover, - provider=lookup_key, + provider=instance_id, remotely_accessible=True, ) ] diff --git a/music_assistant/mass.py b/music_assistant/mass.py index f8f75484..4d5d252e 100644 --- a/music_assistant/mass.py +++ b/music_assistant/mass.py @@ -8,7 +8,7 @@ import os import pathlib import threading from collections.abc import AsyncGenerator, Awaitable, Callable, Coroutine -from typing import TYPE_CHECKING, Any, Self, TypeGuard, TypeVar, cast +from typing import TYPE_CHECKING, Any, Self, TypeGuard, TypeVar, cast, overload from uuid import uuid4 import aiofiles @@ -86,6 +86,7 @@ BASE_DIR = os.path.dirname(os.path.abspath(__file__)) PROVIDERS_PATH = os.path.join(BASE_DIR, "providers") _R = TypeVar("_R") +_ProviderT = TypeVar("_ProviderT", bound=ProviderInstanceType) def is_music_provider(provider: ProviderInstanceType) -> TypeGuard[MusicProvider]: @@ -304,10 +305,35 @@ class MusicAssistant: """Return all loaded/running Providers (instances).""" return list(self._providers.values()) + @overload def get_provider( - self, provider_instance_or_domain: str, return_unavailable: bool = False - ) -> ProviderInstanceType | None: - """Return provider by instance id or domain.""" + self, + provider_instance_or_domain: str, + return_unavailable: bool = False, + provider_type: None = None, + ) -> ProviderInstanceType | None: ... + + @overload + def get_provider( + self, + provider_instance_or_domain: str, + return_unavailable: bool = False, + *, + provider_type: type[_ProviderT], + ) -> _ProviderT | None: ... + + def get_provider( + self, + provider_instance_or_domain: str, + return_unavailable: bool = False, + provider_type: type[_ProviderT] | None = None, + ) -> ProviderInstanceType | _ProviderT | None: + """Return provider by instance id or domain. + + :param provider_instance_or_domain: Instance ID or domain of the provider. + :param return_unavailable: Also return unavailable providers. + :param provider_type: Optional type hint for the expected provider type (unused at runtime). + """ # lookup by instance_id first if prov := self._providers.get(provider_instance_or_domain): if return_unavailable or prov.available: @@ -899,14 +925,14 @@ class MusicAssistant: *{x.domain for x in self.providers}, *{x.instance_id for x in self.providers}, }, - "unique_providers": {x.lookup_key for x in self.providers}, + "unique_providers": self.music.get_unique_providers(), "streaming_providers": { - x.lookup_key + x.domain for x in self.providers if is_music_provider(x) and x.is_streaming_provider }, "non_streaming_providers": { - x.lookup_key + x.instance_id for x in self.providers if not (is_music_provider(x) and x.is_streaming_provider) }, diff --git a/music_assistant/models/music_provider.py b/music_assistant/models/music_provider.py index adf5d4df..4c241517 100644 --- a/music_assistant/models/music_provider.py +++ b/music_assistant/models/music_provider.py @@ -65,13 +65,6 @@ class MusicProvider(Provider): """ return True - @property - def lookup_key(self) -> str: - """Return domain if (multi-instance) streaming_provider or instance_id otherwise.""" - if self.is_streaming_provider or not self.manifest.multi_instance: - return self.domain - return self.instance_id - async def loaded_in_mass(self) -> None: """Call after the provider has been loaded.""" diff --git a/music_assistant/models/player.py b/music_assistant/models/player.py index 2f42cca3..480d6475 100644 --- a/music_assistant/models/player.py +++ b/music_assistant/models/player.py @@ -726,8 +726,8 @@ class Player(ABC): @property @final def provider_id(self) -> str: - """Return the provider id of the player.""" - return self._provider.lookup_key + """Return the provider (instance) id of the player.""" + return self._provider.instance_id @property @final diff --git a/music_assistant/models/player_provider.py b/music_assistant/models/player_provider.py index 5f193278..135bffef 100644 --- a/music_assistant/models/player_provider.py +++ b/music_assistant/models/player_provider.py @@ -83,4 +83,4 @@ class PlayerProvider(Provider): @property def players(self) -> list[Player]: """Return all players belonging to this provider.""" - return self.mass.players.all(provider_filter=self.lookup_key, return_sync_groups=False) + return self.mass.players.all(provider_filter=self.instance_id, return_sync_groups=False) diff --git a/music_assistant/models/provider.py b/music_assistant/models/provider.py index 10685e28..432e7439 100644 --- a/music_assistant/models/provider.py +++ b/music_assistant/models/provider.py @@ -54,12 +54,6 @@ class Provider: # should not be overridden in normal circumstances return self._supported_features - @property - def lookup_key(self) -> str: - """Return instance_id if multi_instance capable or domain otherwise.""" - # should not be overridden in normal circumstances - return self.instance_id if self.manifest.multi_instance else self.domain - async def handle_async_init(self) -> None: """Handle async initialization of the provider.""" @@ -152,7 +146,6 @@ class Provider: "default_name": self.default_name, "instance_name_postfix": self.instance_name_postfix, "instance_id": self.instance_id, - "lookup_key": self.lookup_key, "supported_features": [x.value for x in self.supported_features], "available": self.available, "is_streaming_provider": getattr(self, "is_streaming_provider", None), diff --git a/music_assistant/providers/_demo_plugin_provider/__init__.py b/music_assistant/providers/_demo_plugin_provider/__init__.py index 26669075..652039d5 100644 --- a/music_assistant/providers/_demo_plugin_provider/__init__.py +++ b/music_assistant/providers/_demo_plugin_provider/__init__.py @@ -153,7 +153,7 @@ class MyDemoPluginprovider(PluginProvider): # the audio_format field should be the native audio format of the stream # that is returned by the get_audio_stream method. return PluginSource( - id=self.lookup_key, + id=self.instance_id, name=self.name, passive=False, can_play_pause=False, diff --git a/music_assistant/providers/airplay/player.py b/music_assistant/providers/airplay/player.py index 410965eb..79fb4322 100644 --- a/music_assistant/providers/airplay/player.py +++ b/music_assistant/providers/airplay/player.py @@ -105,7 +105,7 @@ class AirPlayPlayer(Player): PlayerFeature.VOLUME_SET, } self._attr_volume_level = initial_volume - self._attr_can_group_with = {provider.lookup_key} + self._attr_can_group_with = {provider.instance_id} self._attr_enabled_by_default = not is_broken_airplay_model(manufacturer, model) @cached_property @@ -460,7 +460,7 @@ class AirPlayPlayer(Player): await self.mass.cache.set( key=self.player_id, data=volume_level, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, category=CACHE_CATEGORY_PREV_VOLUME, ) diff --git a/music_assistant/providers/airplay/provider.py b/music_assistant/providers/airplay/provider.py index 4190f753..e0ea6199 100644 --- a/music_assistant/providers/airplay/provider.py +++ b/music_assistant/providers/airplay/provider.py @@ -169,7 +169,7 @@ class AirPlayProvider(PlayerProvider): # Get volume from cache if not ( volume := await self.mass.cache.get( - key=player_id, provider=self.lookup_key, category=CACHE_CATEGORY_PREV_VOLUME + key=player_id, provider=self.instance_id, category=CACHE_CATEGORY_PREV_VOLUME ) ): volume = FALLBACK_VOLUME diff --git a/music_assistant/providers/apple_music/__init__.py b/music_assistant/providers/apple_music/__init__.py index 48ff6127..f3192218 100644 --- a/music_assistant/providers/apple_music/__init__.py +++ b/music_assistant/providers/apple_music/__init__.py @@ -641,7 +641,7 @@ class AppleMusicProvider(MusicProvider): ) from exc return StreamDetails( item_id=item_id, - provider=self.lookup_key, + provider=self.instance_id, path=stream_url, stream_type=StreamType.HTTP, audio_format=AudioFormat(content_type=ContentType.UNKNOWN), @@ -656,7 +656,7 @@ class AppleMusicProvider(MusicProvider): key_id = base64.b64decode(uri.split(",")[1]) return StreamDetails( item_id=item_id, - provider=self.lookup_key, + provider=self.instance_id, audio_format=AudioFormat(content_type=ContentType.MP4, codec_type=ContentType.AAC), stream_type=StreamType.ENCRYPTED_HTTP, decryption_key=await self._get_decryption_key(license_url, key_id, uri, item_id), @@ -698,7 +698,7 @@ class AppleMusicProvider(MusicProvider): # No more details available other than the id, return an ItemMapping return ItemMapping( media_type=MediaType.ARTIST, - provider=self.lookup_key, + provider=self.instance_id, item_id=artist_id, name=artist_id, ) @@ -718,7 +718,7 @@ class AppleMusicProvider(MusicProvider): if artwork := attributes.get("artwork"): artist.metadata.add_image( MediaItemImage( - provider=self.lookup_key, + provider=self.instance_id, type=ImageType.THUMB, path=artwork["url"].format(w=artwork["width"], h=artwork["height"]), remotely_accessible=True, @@ -751,7 +751,7 @@ class AppleMusicProvider(MusicProvider): # No more details available other than the id, return an ItemMapping return ItemMapping( media_type=MediaType.ALBUM, - provider=self.lookup_key, + provider=self.instance_id, item_id=album_id, name=album_id, ) @@ -783,7 +783,7 @@ class AppleMusicProvider(MusicProvider): [ ItemMapping( media_type=MediaType.ARTIST, - provider=self.lookup_key, + provider=self.instance_id, item_id=artist_name, name=artist_name, ) @@ -796,7 +796,7 @@ class AppleMusicProvider(MusicProvider): if artwork := attributes.get("artwork"): album.metadata.add_image( MediaItemImage( - provider=self.lookup_key, + provider=self.instance_id, type=ImageType.THUMB, path=artwork["url"].format(w=artwork["width"], h=artwork["height"]), remotely_accessible=True, @@ -879,7 +879,7 @@ class AppleMusicProvider(MusicProvider): ItemMapping( media_type=MediaType.ARTIST, item_id=artist_name, - provider=self.lookup_key, + provider=self.instance_id, name=artist_name, ) ] @@ -889,7 +889,7 @@ class AppleMusicProvider(MusicProvider): if artwork := attributes.get("artwork"): track.metadata.add_image( MediaItemImage( - provider=self.lookup_key, + provider=self.instance_id, type=ImageType.THUMB, path=artwork["url"].format(w=artwork["width"], h=artwork["height"]), remotely_accessible=True, @@ -913,7 +913,7 @@ class AppleMusicProvider(MusicProvider): is_editable = attributes.get("canEdit", False) playlist = Playlist( item_id=playlist_id, - provider=self.instance_id if is_editable else self.lookup_key, + provider=self.instance_id, name=attributes.get("name", UNKNOWN_PLAYLIST_NAME), owner=attributes.get("curatorName", "me"), provider_mappings={ @@ -932,7 +932,7 @@ class AppleMusicProvider(MusicProvider): url = url.format(w=artwork["width"], h=artwork["height"]) playlist.metadata.add_image( MediaItemImage( - provider=self.lookup_key, + provider=self.instance_id, type=ImageType.THUMB, path=url, remotely_accessible=True, diff --git a/music_assistant/providers/ard_audiothek/__init__.py b/music_assistant/providers/ard_audiothek/__init__.py index 8d706445..2c4ca655 100644 --- a/music_assistant/providers/ard_audiothek/__init__.py +++ b/music_assistant/providers/ard_audiothek/__init__.py @@ -381,7 +381,6 @@ class ARDAudiothek(MusicProvider): podcasts += [ _parse_podcast( self.domain, - self.lookup_key, self.instance_id, element, element["coreId"], @@ -476,7 +475,6 @@ class ARDAudiothek(MusicProvider): return _parse_podcast( self.domain, - self.lookup_key, self.instance_id, result, prov_podcast_id, @@ -514,7 +512,6 @@ class ARDAudiothek(MusicProvider): progress = self._get_progress(episode_id) yield _parse_podcast_episode( self.domain, - self.lookup_key, self.instance_id, episode, episode_id, @@ -535,7 +532,6 @@ class ARDAudiothek(MusicProvider): progress = self._get_progress(prov_episode_id) return _parse_podcast_episode( self.domain, - self.lookup_key, self.instance_id, result, result["showId"], @@ -667,7 +663,6 @@ class ARDAudiothek(MusicProvider): podcast = _parse_podcast( self.domain, - self.lookup_key, self.instance_id, pod, pod["coreId"], @@ -700,7 +695,6 @@ def _parse_social_media( def _parse_podcast( domain: str, - lookup_key: str, instance_id: str, podcast_query: dict[str, Any], podcast_id: str, @@ -709,7 +703,7 @@ def _parse_podcast( name=podcast_query["title"], item_id=podcast_id, publisher=podcast_query["publicationService"]["title"], - provider=lookup_key, + provider=instance_id, provider_mappings={ ProviderMapping( item_id=podcast_id, @@ -767,7 +761,6 @@ def _parse_radio( def _parse_podcast_episode( domain: str, - lookup_key: str, instance_id: str, episode: dict[str, Any], podcast_id: str, @@ -779,10 +772,10 @@ def _parse_podcast_episode( name=episode["title"], duration=episode["duration"], item_id=episode["coreId"], - provider=lookup_key, + provider=instance_id, podcast=ItemMapping( item_id=podcast_id, - provider=lookup_key, + provider=instance_id, name=podcast_title, media_type=MediaType.PODCAST, ), diff --git a/music_assistant/providers/audiobookshelf/__init__.py b/music_assistant/providers/audiobookshelf/__init__.py index 12aaf71b..cf5d502f 100644 --- a/music_assistant/providers/audiobookshelf/__init__.py +++ b/music_assistant/providers/audiobookshelf/__init__.py @@ -393,9 +393,8 @@ for more details. assert isinstance(podcast_minified, LibraryItemMinifiedPodcast) mass_podcast = parse_podcast( abs_podcast=podcast_minified, - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, token=self._client.token, base_url=str(self.config.get_value(CONF_URL)).rstrip("/"), ) @@ -424,9 +423,8 @@ for more details. abs_podcast = await self._get_abs_expanded_podcast(prov_podcast_id=prov_podcast_id) return parse_podcast( abs_podcast=abs_podcast, - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, token=self._client.token, base_url=str(self.config.get_value(CONF_URL)).rstrip("/"), ) @@ -455,9 +453,8 @@ for more details. episode=abs_episode, prov_podcast_id=prov_podcast_id, fallback_episode_cnt=episode_cnt, - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, token=self._client.token, base_url=str(self.config.get_value(CONF_URL)).rstrip("/"), media_progress=progress, @@ -484,9 +481,8 @@ for more details. episode=abs_episode, prov_podcast_id=prov_podcast_id, fallback_episode_cnt=episode_cnt, - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, token=self._client.token, base_url=str(self.config.get_value(CONF_URL)).rstrip("/"), media_progress=progress, @@ -515,9 +511,8 @@ for more details. continue mass_audiobook = parse_audiobook( abs_audiobook=book_expanded, - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, token=self._client.token, base_url=str(self.config.get_value(CONF_URL)).rstrip("/"), ) @@ -545,9 +540,8 @@ for more details. abs_audiobook = await self._get_abs_expanded_audiobook(prov_audiobook_id=prov_audiobook_id) return parse_audiobook( abs_audiobook=abs_audiobook, - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, token=self._client.token, base_url=str(self.config.get_value(CONF_URL)).rstrip("/"), media_progress=progress, @@ -591,7 +585,7 @@ for more details. file_parts.append(MultiPartPath(path=stream_url, duration=track.duration)) return StreamDetails( - provider=self.lookup_key, + provider=self.instance_id, item_id=abs_audiobook.id_, audio_format=AudioFormat(content_type=content_type), media_type=MediaType.AUDIOBOOK, @@ -624,7 +618,7 @@ for more details. base_url = str(self.config.get_value(CONF_URL)) stream_url = f"{base_url}{abs_episode.audio_track.content_url}?token={self._client.token}" return StreamDetails( - provider=self.lookup_key, + provider=self.instance_id, item_id=podcast_id, audio_format=AudioFormat( content_type=content_type, @@ -727,7 +721,7 @@ for more details. icon=ABS_SHELF_ID_ICONS.get(shelf_id), # translation_key=shelf.id_, items=UniqueList(recommendation_items), - provider=self.lookup_key, + provider=self.instance_id, ) ) @@ -770,7 +764,7 @@ for more details. icon="mdi-bookshelf", # translation_key=shelf.id_, items=UniqueList(browse_items), - provider=self.lookup_key, + provider=self.instance_id, ) ) @@ -826,9 +820,8 @@ for more details. item = parse_podcast_episode( episode=entity.recent_episode, prov_podcast_id=podcast_id, - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, token=self._client.token, base_url=str(self.config.get_value(CONF_URL)).rstrip("/"), ) @@ -851,7 +844,7 @@ for more details. BrowseFolder( item_id=entity.id_, name=entity.name, - provider=self.lookup_key, + provider=self.instance_id, path=path, ) ) @@ -880,7 +873,7 @@ for more details. BrowseFolder( item_id=entity.id_, name=entity.name, - provider=self.lookup_key, + provider=self.instance_id, path=path, ) ) @@ -1056,7 +1049,7 @@ for more details. return BrowseFolder( item_id=lib_id, name=lib_name, - provider=self.lookup_key, + provider=self.instance_id, path=f"{self.instance_id}://{path}", ) @@ -1103,7 +1096,7 @@ for more details. BrowseFolder( item_id=item_name.lower(), name=item_name, - provider=self.lookup_key, + provider=self.instance_id, path=path, ) ) @@ -1118,7 +1111,7 @@ for more details. BrowseFolder( item_id=author.id_, name=author.name, - provider=self.lookup_key, + provider=self.instance_id, path=path, ) ) @@ -1134,7 +1127,7 @@ for more details. BrowseFolder( item_id=narrator.id_, name=narrator.name, - provider=self.lookup_key, + provider=self.instance_id, path=path, ) ) @@ -1152,7 +1145,7 @@ for more details. BrowseFolder( item_id=abs_series.id_, name=abs_series.name, - provider=self.lookup_key, + provider=self.instance_id, path=path, ) ) @@ -1172,7 +1165,7 @@ for more details. BrowseFolder( item_id=abs_collection.id_, name=abs_collection.name, - provider=self.lookup_key, + provider=self.instance_id, path=path, ) ) @@ -1213,7 +1206,7 @@ for more details. BrowseFolder( item_id=series.id_, name=f"{series.name} ({AbsBrowseItemsBook.SERIES})", - provider=self.lookup_key, + provider=self.instance_id, path=path, ) ) @@ -1297,9 +1290,8 @@ for more details. await self.mass.music.audiobooks.add_item_to_library( parse_audiobook( abs_audiobook=abs_item, - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, token=self._client.token, base_url=str(self.config.get_value(CONF_URL)).rstrip("/"), ), @@ -1314,9 +1306,8 @@ for more details. ) mass_podcast = parse_podcast( abs_podcast=abs_item, - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, token=self._client.token, base_url=str(self.config.get_value(CONF_URL)).rstrip("/"), ) @@ -1474,7 +1465,7 @@ for more details. if discarded_item := await self.mass.music.get_library_item_by_prov_id( media_type=MediaType.AUDIOBOOK, item_id=discarded_progress_id, - provider_instance_id_or_domain=self.lookup_key, + provider_instance_id_or_domain=self.instance_id, ): self.progress_guard.add_progress(discarded_progress_id) await self.mass.music.mark_item_unplayed(discarded_item) diff --git a/music_assistant/providers/audiobookshelf/parsers.py b/music_assistant/providers/audiobookshelf/parsers.py index 6c9137d8..d146fb47 100644 --- a/music_assistant/providers/audiobookshelf/parsers.py +++ b/music_assistant/providers/audiobookshelf/parsers.py @@ -42,9 +42,8 @@ def parse_podcast( abs_podcast: AbsLibraryItemExpandedPodcast | AbsLibraryItemMinifiedPodcast | AbsLibraryItemPodcast, - lookup_key: str, - domain: str, instance_id: str, + domain: str, token: str | None, base_url: str, ) -> MassPodcast: @@ -57,7 +56,7 @@ def parse_podcast( item_id=abs_podcast.id_, name=title, publisher=abs_podcast.media.metadata.author, - provider=lookup_key, + provider=instance_id, provider_mappings={ ProviderMapping( item_id=abs_podcast.id_, @@ -70,7 +69,7 @@ def parse_podcast( if token is not None: image_url = f"{base_url}/api/items/{abs_podcast.id_}/cover?token={token}" mass_podcast.metadata.images = UniqueList( - [MediaItemImage(type=ImageType.THUMB, path=image_url, provider=lookup_key)] + [MediaItemImage(type=ImageType.THUMB, path=image_url, provider=instance_id)] ) mass_podcast.metadata.explicit = abs_podcast.media.metadata.explicit if abs_podcast.media.metadata.language is not None: @@ -98,9 +97,8 @@ def parse_podcast_episode( episode: AbsPodcastEpisode | AbsPodcastEpisodeExpanded, prov_podcast_id: str, fallback_episode_cnt: int | None = None, - lookup_key: str, - domain: str, instance_id: str, + domain: str, token: str | None, base_url: str, media_progress: AbsMediaProgress | None = None, @@ -153,13 +151,13 @@ def parse_podcast_episode( position = fallback_episode_cnt mass_episode = MassPodcastEpisode( item_id=episode_id, - provider=lookup_key, + provider=instance_id, name=episode.title, duration=duration, position=position, podcast=ItemMapping( item_id=prov_podcast_id, - provider=lookup_key, + provider=instance_id, name=episode.title, media_type=MediaType.PODCAST, ), @@ -173,7 +171,7 @@ def parse_podcast_episode( url_api = f"/api/items/{prov_podcast_id}/cover?token={token}" url_cover = f"{base_url}{url_api}" mass_episode.metadata.images = UniqueList( - [MediaItemImage(type=ImageType.THUMB, path=url_cover, provider=lookup_key)] + [MediaItemImage(type=ImageType.THUMB, path=url_cover, provider=instance_id)] ) if media_progress is not None and media_progress.current_time is not None: @@ -186,9 +184,8 @@ def parse_podcast_episode( def parse_audiobook( *, abs_audiobook: AbsLibraryItemExpandedBook | AbsLibraryItemMinifiedBook, - lookup_key: str, - domain: str, instance_id: str, + domain: str, token: str | None, base_url: str, media_progress: AbsMediaProgress | None = None, @@ -203,7 +200,7 @@ def parse_audiobook( title += f" | {subtitle}" mass_audiobook = MassAudiobook( item_id=abs_audiobook.id_, - provider=lookup_key, + provider=instance_id, name=title, duration=int(abs_audiobook.media.duration), provider_mappings={ @@ -241,7 +238,7 @@ def parse_audiobook( api_url = f"/api/items/{abs_audiobook.id_}/cover?token={token}" cover_url = f"{base_url}{api_url}" mass_audiobook.metadata.images = UniqueList( - [MediaItemImage(type=ImageType.THUMB, path=cover_url, provider=lookup_key)] + [MediaItemImage(type=ImageType.THUMB, path=cover_url, provider=instance_id)] ) # expanded version diff --git a/music_assistant/providers/bluesound/player.py b/music_assistant/providers/bluesound/player.py index 50dba51f..abad06f0 100644 --- a/music_assistant/providers/bluesound/player.py +++ b/music_assistant/providers/bluesound/player.py @@ -74,7 +74,7 @@ class BluesoundPlayer(Player): self._attr_source_list = [] self._attr_needs_poll = True self._attr_poll_interval = IDLE_POLL_INTERVAL - self._attr_can_group_with = {provider.lookup_key} + self._attr_can_group_with = {provider.instance_id} async def setup(self) -> None: """Set up the player.""" diff --git a/music_assistant/providers/builtin/__init__.py b/music_assistant/providers/builtin/__init__.py index c138478f..8069e1b6 100644 --- a/music_assistant/providers/builtin/__init__.py +++ b/music_assistant/providers/builtin/__init__.py @@ -308,7 +308,7 @@ class BuiltinProvider(MusicProvider): self.logger.warning("Radio station %s not found: %s", item, err) yield Radio( item_id=item["item_id"], - provider=self.lookup_key, + provider=self.instance_id, name=item["name"], provider_mappings={ ProviderMapping( diff --git a/music_assistant/providers/deezer/__init__.py b/music_assistant/providers/deezer/__init__.py index e072b750..a8cd37f4 100644 --- a/music_assistant/providers/deezer/__init__.py +++ b/music_assistant/providers/deezer/__init__.py @@ -425,7 +425,7 @@ class DeezerProvider(MusicProvider): return [ RecommendationFolder( item_id="recommended_tracks", - provider=self.lookup_key, + provider=self.instance_id, name="Recommended tracks", translation_key="recommended_tracks", items=UniqueList( @@ -476,7 +476,7 @@ class DeezerProvider(MusicProvider): url = url_details["sources"][0]["url"] return StreamDetails( item_id=item_id, - provider=self.lookup_key, + provider=self.instance_id, audio_format=AudioFormat( content_type=ContentType.try_parse(url_details["format"].split("_")[0]) ), @@ -557,7 +557,7 @@ class DeezerProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=track.album.cover_big, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ) @@ -572,7 +572,7 @@ class DeezerProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=album.cover_big, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ] @@ -587,7 +587,7 @@ class DeezerProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=artist.picture_big, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ] @@ -599,7 +599,7 @@ class DeezerProvider(MusicProvider): """Parse the deezer-python artist to a Music Assistant artist.""" return Artist( item_id=str(artist.id), - provider=self.lookup_key, + provider=self.instance_id, name=artist.name, media_type=MediaType.ARTIST, provider_mappings={ @@ -618,14 +618,14 @@ class DeezerProvider(MusicProvider): return Album( album_type=self.get_album_type(album), item_id=str(album.id), - provider=self.lookup_key, + provider=self.instance_id, name=album.title, artists=UniqueList( [ ItemMapping( media_type=MediaType.ARTIST, item_id=str(album.artist.id), - provider=self.lookup_key, + provider=self.instance_id, name=album.artist.name, ) ] @@ -648,7 +648,7 @@ class DeezerProvider(MusicProvider): is_editable = creator.id == self.user.id return Playlist( item_id=str(playlist.id), - provider=self.instance_id if is_editable else self.lookup_key, + provider=self.instance_id, name=playlist.title, media_type=MediaType.PLAYLIST, provider_mappings={ @@ -665,7 +665,7 @@ class DeezerProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=playlist.picture_big, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ] @@ -687,7 +687,7 @@ class DeezerProvider(MusicProvider): artist = ItemMapping( media_type=MediaType.ARTIST, item_id=str(getattr(track.artist, "id", f"deezer-{track.artist.name}")), - provider=self.lookup_key, + provider=self.instance_id, name=track.artist.name, ) else: @@ -696,7 +696,7 @@ class DeezerProvider(MusicProvider): album = ItemMapping( media_type=MediaType.ALBUM, item_id=str(track.album.id), - provider=self.lookup_key, + provider=self.instance_id, name=track.album.title, ) else: @@ -704,7 +704,7 @@ class DeezerProvider(MusicProvider): item = Track( item_id=str(track.id), - provider=self.lookup_key, + provider=self.instance_id, name=track.title, sort_name=self.get_short_title(track), duration=track.duration, diff --git a/music_assistant/providers/gpodder/__init__.py b/music_assistant/providers/gpodder/__init__.py index 13cb26fa..3aafc94c 100644 --- a/music_assistant/providers/gpodder/__init__.py +++ b/music_assistant/providers/gpodder/__init__.py @@ -402,9 +402,8 @@ class GPodder(MusicProvider): yield parse_podcast( feed_url=feed_url, parsed_feed=parsed_podcast, - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, ) self.timestamp_subscriptions = subscriptions.timestamp @@ -420,9 +419,8 @@ class GPodder(MusicProvider): return parse_podcast( feed_url=prov_podcast_id, parsed_feed=parsed_podcast, - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, ) async def get_podcast_episodes( @@ -449,7 +447,6 @@ class GPodder(MusicProvider): episode_cnt=cnt, podcast_cover=podcast_cover, domain=self.domain, - lookup_key=self.lookup_key, instance_id=self.instance_id, ) if mass_episode is None: @@ -578,7 +575,7 @@ class GPodder(MusicProvider): if stream_url is None: raise MediaNotFoundError return StreamDetails( - provider=self.lookup_key, + provider=self.instance_id, item_id=item_id, audio_format=AudioFormat( content_type=ContentType.try_parse(stream_url), diff --git a/music_assistant/providers/ibroadcast/__init__.py b/music_assistant/providers/ibroadcast/__init__.py index 580711b5..86296f8a 100644 --- a/music_assistant/providers/ibroadcast/__init__.py +++ b/music_assistant/providers/ibroadcast/__init__.py @@ -200,7 +200,7 @@ class IBroadcastProvider(MusicProvider): return ItemMapping( media_type=media_type, item_id=key, - provider=self.lookup_key, + provider=self.instance_id, name=name, ) @@ -241,7 +241,7 @@ class IBroadcastProvider(MusicProvider): url = await self._client.get_full_stream_url(int(item_id), "music-assistant") return StreamDetails( - provider=self.lookup_key, + provider=self.instance_id, item_id=item_id, audio_format=AudioFormat( content_type=ContentType.UNKNOWN, @@ -270,7 +270,7 @@ class IBroadcastProvider(MusicProvider): artist = Artist( item_id=artist_id, name=artist_obj["name"], - provider=self.lookup_key, + provider=self.instance_id, provider_mappings={ ProviderMapping( item_id=artist_id, @@ -287,7 +287,7 @@ class IBroadcastProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=await self._client.get_artist_artwork_url(artist_id), - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ] @@ -300,7 +300,7 @@ class IBroadcastProvider(MusicProvider): name, version = parse_title_and_version(album_obj["name"]) album = Album( item_id=album_id, - provider=self.lookup_key, + provider=self.instance_id, name=name, year=album_obj["year"], version=version, @@ -318,7 +318,7 @@ class IBroadcastProvider(MusicProvider): artist = Artist( item_id=VARIOUS_ARTISTS_MBID, name=VARIOUS_ARTISTS_NAME, - provider=self.lookup_key, + provider=self.instance_id, provider_mappings={ ProviderMapping( item_id=VARIOUS_ARTISTS_MBID, @@ -353,7 +353,7 @@ class IBroadcastProvider(MusicProvider): return MediaItemImage( type=ImageType.THUMB, path=url, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) @@ -361,7 +361,7 @@ class IBroadcastProvider(MusicProvider): """Parse an iBroadcast track object to a Track model object.""" track = Track( item_id=track_obj["track_id"], - provider=self.lookup_key, + provider=self.instance_id, name=track_obj["title"], provider_mappings={ ProviderMapping( @@ -442,7 +442,7 @@ class IBroadcastProvider(MusicProvider): playlist_id = str(playlist_obj["playlist_id"]) playlist = Playlist( item_id=playlist_id, - provider=self.lookup_key, + provider=self.instance_id, name=playlist_obj["name"], provider_mappings={ ProviderMapping( diff --git a/music_assistant/providers/itunes_podcasts/__init__.py b/music_assistant/providers/itunes_podcasts/__init__.py index d6c123be..ddb97665 100644 --- a/music_assistant/providers/itunes_podcasts/__init__.py +++ b/music_assistant/providers/itunes_podcasts/__init__.py @@ -188,7 +188,7 @@ class ITunesPodcastsProvider(MusicProvider): name=result.track_name, item_id=result.feed_url, publisher=result.artist_name, - provider=self.lookup_key, + provider=self.instance_id, provider_mappings={ ProviderMapping( item_id=result.feed_url, @@ -207,7 +207,7 @@ class ITunesPodcastsProvider(MusicProvider): if artwork_url is not None: image_list.append( MediaItemImage( - type=ImageType.THUMB, path=artwork_url, provider=self.lookup_key + type=ImageType.THUMB, path=artwork_url, provider=self.instance_id ) ) podcast.metadata.images = UniqueList(image_list) @@ -221,9 +221,8 @@ class ITunesPodcastsProvider(MusicProvider): return parse_podcast( feed_url=prov_podcast_id, parsed_feed=parsed, - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, ) async def get_podcast_episodes( @@ -240,7 +239,6 @@ class ITunesPodcastsProvider(MusicProvider): episode_cnt=cnt, podcast_cover=podcast_cover, domain=self.domain, - lookup_key=self.lookup_key, instance_id=self.instance_id, ): yield mass_episode @@ -269,7 +267,7 @@ class ITunesPodcastsProvider(MusicProvider): icon="mdi-trending-up", # translation_key=shelf.id_, items=UniqueList(podcast_list), - provider=self.lookup_key, + provider=self.instance_id, ) ] @@ -297,7 +295,7 @@ class ITunesPodcastsProvider(MusicProvider): if stream_url is None: raise MediaNotFoundError return StreamDetails( - provider=self.lookup_key, + provider=self.instance_id, item_id=item_id, audio_format=AudioFormat( content_type=ContentType.try_parse(stream_url), diff --git a/music_assistant/providers/jellyfin/__init__.py b/music_assistant/providers/jellyfin/__init__.py index fb84d13d..f417412b 100644 --- a/music_assistant/providers/jellyfin/__init__.py +++ b/music_assistant/providers/jellyfin/__init__.py @@ -364,7 +364,7 @@ class JellyfinProvider(MusicProvider): artist = Artist( item_id=UNKNOWN_ARTIST_MAPPING.item_id, name=UNKNOWN_ARTIST_MAPPING.name, - provider=self.lookup_key, + provider=self.instance_id, provider_mappings={ ProviderMapping( item_id=UNKNOWN_ARTIST_MAPPING.item_id, @@ -450,7 +450,7 @@ class JellyfinProvider(MusicProvider): ) return StreamDetails( item_id=jellyfin_track[ITEM_KEY_ID], - provider=self.lookup_key, + provider=self.instance_id, audio_format=audio_format(jellyfin_track), stream_type=StreamType.HTTP, duration=int( diff --git a/music_assistant/providers/musiccast/player.py b/music_assistant/providers/musiccast/player.py index d0270837..d5037477 100644 --- a/music_assistant/providers/musiccast/player.py +++ b/music_assistant/providers/musiccast/player.py @@ -116,7 +116,7 @@ class MusicCastPlayer(Player): self._attr_name = self.zone_device.zone_data.name # group - self._attr_can_group_with = {self.provider.lookup_key} + self._attr_can_group_with = {self.provider.instance_id} self._attr_available = True diff --git a/music_assistant/providers/musiccast/provider.py b/music_assistant/providers/musiccast/provider.py index 632ae75c..a16440cc 100644 --- a/music_assistant/providers/musiccast/provider.py +++ b/music_assistant/providers/musiccast/provider.py @@ -96,7 +96,7 @@ class MusicCastProvider(PlayerProvider): async def unload(self, is_removed: bool = False) -> None: """Call on unload.""" - for mc_player in self.mass.players.all(provider_filter=self.lookup_key): + for mc_player in self.mass.players.all(provider_filter=self.instance_id): assert isinstance(mc_player, MusicCastPlayer) # for type checking mc_player.physical_device.remove() diff --git a/music_assistant/providers/nicovideo/converters/album.py b/music_assistant/providers/nicovideo/converters/album.py index c5762227..0a7bb553 100644 --- a/music_assistant/providers/nicovideo/converters/album.py +++ b/music_assistant/providers/nicovideo/converters/album.py @@ -75,7 +75,7 @@ class NicovideoAlbumConverter(NicovideoConverterBase): # Create album with common structure album = Album( item_id=item_id, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, name=name, metadata=MediaItemMetadata( description=description, @@ -95,7 +95,7 @@ class NicovideoAlbumConverter(NicovideoConverterBase): if owner_id: owner_artist = Artist( item_id=str(owner_id), - provider=self.provider.lookup_key, + provider=self.provider.instance_id, name=owner_name if owner_name else "", provider_mappings=self.helper.create_provider_mapping( item_id=str(owner_id), @@ -113,7 +113,7 @@ class NicovideoAlbumConverter(NicovideoConverterBase): MediaItemImage( type=ImageType.THUMB, path=thumbnail_url, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, remotely_accessible=True, ) ] diff --git a/music_assistant/providers/nicovideo/converters/artist.py b/music_assistant/providers/nicovideo/converters/artist.py index d86060eb..b4ab552d 100644 --- a/music_assistant/providers/nicovideo/converters/artist.py +++ b/music_assistant/providers/nicovideo/converters/artist.py @@ -44,7 +44,7 @@ class NicovideoArtistConverter(NicovideoConverterBase): artist = Artist( item_id=item_id, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, name=name, metadata=MediaItemMetadata( description=owner_or_user.description @@ -63,7 +63,7 @@ class NicovideoArtistConverter(NicovideoConverterBase): MediaItemImage( type=ImageType.THUMB, path=icon_url, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, remotely_accessible=True, ) ) diff --git a/music_assistant/providers/nicovideo/converters/playlist.py b/music_assistant/providers/nicovideo/converters/playlist.py index 99dedd7d..33170ea2 100644 --- a/music_assistant/providers/nicovideo/converters/playlist.py +++ b/music_assistant/providers/nicovideo/converters/playlist.py @@ -30,7 +30,7 @@ class NicovideoPlaylistConverter(NicovideoConverterBase): """Convert a nicovideo UserMylistItem into a Playlist.""" playlist = Playlist( item_id=str(mylist.id_), - provider=self.provider.lookup_key, + provider=self.provider.instance_id, name=(mylist.title if isinstance(mylist, EssentialMylist) else mylist.name), owner=mylist.owner.id_ or "", is_editable=True, # Own mylists are editable by default @@ -53,7 +53,7 @@ class NicovideoPlaylistConverter(NicovideoConverterBase): MediaItemImage( type=ImageType.THUMB, path=mylist.owner.icon_url, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, remotely_accessible=True, ) ) diff --git a/music_assistant/providers/nicovideo/converters/track.py b/music_assistant/providers/nicovideo/converters/track.py index 4dc4b1e6..c67acdb1 100644 --- a/music_assistant/providers/nicovideo/converters/track.py +++ b/music_assistant/providers/nicovideo/converters/track.py @@ -59,7 +59,7 @@ class NicovideoTrackConverter(NicovideoConverterBase): # Create track with available information return Track( item_id=content.id_, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, name=content.title, duration=content.video.duration, artists=artists_list, @@ -103,7 +103,7 @@ class NicovideoTrackConverter(NicovideoConverterBase): # Create base track with enhanced metadata return Track( item_id=video.id_, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, name=video.title, duration=video.duration, artists=artists_list, @@ -173,7 +173,7 @@ class NicovideoTrackConverter(NicovideoConverterBase): # Create base track with enhanced metadata track = Track( item_id=video.id_, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, name=video.title, duration=video.duration, artists=artists_list, @@ -293,7 +293,7 @@ class NicovideoTrackConverter(NicovideoConverterBase): MediaItemImage( type=ImageType.THUMB, path=url, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, remotely_accessible=True, ) ) @@ -354,7 +354,7 @@ class NicovideoTrackConverter(NicovideoConverterBase): MediaItemImage( type=ImageType.THUMB, path=thumbnail_url, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, remotely_accessible=True, ) ] @@ -380,7 +380,7 @@ class NicovideoTrackConverter(NicovideoConverterBase): MediaItemImage( type=ImageType.THUMB, path=thumbnail.nhd_url, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, remotely_accessible=True, ) ) @@ -391,7 +391,7 @@ class NicovideoTrackConverter(NicovideoConverterBase): MediaItemImage( type=ImageType.THUMB, path=thumbnail.large_url, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, remotely_accessible=True, ) ) @@ -403,7 +403,7 @@ class NicovideoTrackConverter(NicovideoConverterBase): MediaItemImage( type=ImageType.THUMB, path=thumbnail.middle_url, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, remotely_accessible=True, ) ) diff --git a/music_assistant/providers/nicovideo/provider_mixins/explorer.py b/music_assistant/providers/nicovideo/provider_mixins/explorer.py index ff2ff25c..8e00550a 100644 --- a/music_assistant/providers/nicovideo/provider_mixins/explorer.py +++ b/music_assistant/providers/nicovideo/provider_mixins/explorer.py @@ -67,7 +67,7 @@ class NicovideoMusicProviderExplorerMixin(NicovideoMusicProviderMixinBase): RecommendationFolder( item_id="nicovideo_recommendations", name="nicovideo recommendations", - provider=self.lookup_key, + provider=self.instance_id, icon="mdi-star-circle-outline", items=UniqueList(main_recommendation_tracks), ) @@ -80,7 +80,7 @@ class NicovideoMusicProviderExplorerMixin(NicovideoMusicProviderMixinBase): RecommendationFolder( item_id="nicovideo_history", name="Recently watched (nicovideo history)", - provider=self.lookup_key, + provider=self.instance_id, icon="mdi-history", items=UniqueList(history_tracks), ) @@ -95,7 +95,7 @@ class NicovideoMusicProviderExplorerMixin(NicovideoMusicProviderMixinBase): RecommendationFolder( item_id="nicovideo_following_activities", name="New Tracks from Followed Users", - provider=self.lookup_key, + provider=self.instance_id, icon="mdi-account-plus-outline", items=UniqueList(following_activities_tracks), ) @@ -108,7 +108,7 @@ class NicovideoMusicProviderExplorerMixin(NicovideoMusicProviderMixinBase): RecommendationFolder( item_id="nicovideo_like_history", name="Recently liked (Like history)", - provider=self.lookup_key, + provider=self.instance_id, icon="mdi-heart-outline", items=UniqueList(like_history_tracks), ) diff --git a/music_assistant/providers/nugs/__init__.py b/music_assistant/providers/nugs/__init__.py index 6969985e..380a8d34 100644 --- a/music_assistant/providers/nugs/__init__.py +++ b/music_assistant/providers/nugs/__init__.py @@ -201,7 +201,7 @@ class NugsProvider(MusicProvider): stream_url = await self._get_stream_url(item_id) return StreamDetails( item_id=item_id, - provider=self.lookup_key, + provider=self.instance_id, audio_format=AudioFormat( content_type=ContentType.UNKNOWN, ), @@ -219,17 +219,17 @@ class NugsProvider(MusicProvider): popular_folder = RecommendationFolder( name="Most Popular", item_id="nugs_popular_shows", - provider=self.lookup_key, + provider=self.instance_id, ) recommended_folder = RecommendationFolder( name="Recommended Shows", item_id="nugs_recommended_shows", - provider=self.lookup_key, + provider=self.instance_id, ) recent_folder = RecommendationFolder( name="Recent Shows", item_id="nugs_recent_shows", - provider=self.lookup_key, + provider=self.instance_id, ) popular_data = await self._get_data("catalog", popular, limit=20) for item in popular_data["items"]: @@ -255,7 +255,7 @@ class NugsProvider(MusicProvider): artist_name = artist_obj.get("artistName") or artist_obj.get("name") artist = Artist( item_id=str(artist_id), - provider=self.lookup_key, + provider=self.instance_id, name=str(artist_name), provider_mappings={ ProviderMapping( @@ -271,7 +271,7 @@ class NugsProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=artist_obj["avatarImage"]["url"], - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ) @@ -283,7 +283,7 @@ class NugsProvider(MusicProvider): title = album_obj.get("title") or album_obj.get("containerInfo") album = Album( item_id=str(item_id), - provider=self.lookup_key, + provider=self.instance_id, name=str(title), # version=album_obj["type"], provider_mappings={ @@ -312,7 +312,7 @@ class NugsProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=path, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ) @@ -335,7 +335,7 @@ class NugsProvider(MusicProvider): """Parse nugs playlist object to generic layout.""" return Playlist( item_id=playlist_obj["id"], - provider=self.lookup_key, + provider=self.instance_id, name=playlist_obj["name"], provider_mappings={ ProviderMapping( @@ -350,7 +350,7 @@ class NugsProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=playlist_obj["imageUrl"], - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ] @@ -374,7 +374,7 @@ class NugsProvider(MusicProvider): track = Track( item_id=str(track_id), - provider=self.lookup_key, + provider=self.instance_id, name=str(track_name), provider_mappings={ ProviderMapping( @@ -411,7 +411,7 @@ class NugsProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=image_url, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ) @@ -458,7 +458,7 @@ class NugsProvider(MusicProvider): return ItemMapping( media_type=media_type, item_id=key, - provider=self.lookup_key, + provider=self.instance_id, name=name, ) diff --git a/music_assistant/providers/plex/__init__.py b/music_assistant/providers/plex/__init__.py index dbbe6591..68b877d5 100644 --- a/music_assistant/providers/plex/__init__.py +++ b/music_assistant/providers/plex/__init__.py @@ -544,14 +544,14 @@ class PlexProvider(MusicProvider): return ItemMapping( media_type=media_type, item_id=key, - provider=self.lookup_key, + provider=self.instance_id, name=mapped_name, version=mapped_version, ) async def _get_or_create_artist_by_name(self, artist_name: str) -> Artist | ItemMapping: if library_items := await self.mass.music.artists._get_library_items_by_query( - search=artist_name, provider_filter=[self.lookup_key] + search=artist_name, provider_filter=[self.instance_id] ): return ItemMapping.from_item(library_items[0]) @@ -559,7 +559,7 @@ class PlexProvider(MusicProvider): return Artist( item_id=artist_id, name=artist_name or UNKNOWN_ARTIST, - provider=self.lookup_key, + provider=self.instance_id, provider_mappings={ ProviderMapping( item_id=str(artist_id), @@ -651,7 +651,7 @@ class PlexProvider(MusicProvider): album_id = plex_album.key album = Album( item_id=album_id, - provider=self.lookup_key, + provider=self.instance_id, name=plex_album.title or "[Unknown]", provider_mappings={ ProviderMapping( @@ -676,7 +676,7 @@ class PlexProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=thumb, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=False, ) ] @@ -702,7 +702,7 @@ class PlexProvider(MusicProvider): artist = Artist( item_id=artist_id, name=plex_artist.title or UNKNOWN_ARTIST, - provider=self.lookup_key, + provider=self.instance_id, provider_mappings={ ProviderMapping( item_id=str(artist_id), @@ -720,7 +720,7 @@ class PlexProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=thumb, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=False, ) ] @@ -731,7 +731,7 @@ class PlexProvider(MusicProvider): """Parse a Plex Playlist response to a Playlist object.""" playlist = Playlist( item_id=plex_playlist.key, - provider=self.lookup_key, + provider=self.instance_id, name=plex_playlist.title or "[Unknown]", provider_mappings={ ProviderMapping( @@ -750,7 +750,7 @@ class PlexProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=thumb, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=False, ) ] @@ -766,7 +766,7 @@ class PlexProvider(MusicProvider): # Collections are imported as playlists with the configured prefix playlist = Playlist( item_id=f"collection:{plex_collection.key}", - provider=self.lookup_key, + provider=self.instance_id, name=f"{collection_prefix}{plex_collection.title}", provider_mappings={ ProviderMapping( @@ -783,7 +783,7 @@ class PlexProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=thumb, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=False, ) ] @@ -805,7 +805,7 @@ class PlexProvider(MusicProvider): content = None track = Track( item_id=plex_track.key, - provider=self.lookup_key, + provider=self.instance_id, name=plex_track.title or "[Unknown]", provider_mappings={ ProviderMapping( @@ -855,7 +855,7 @@ class PlexProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=thumb, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=False, ) ] @@ -1174,7 +1174,7 @@ class PlexProvider(MusicProvider): folder = RecommendationFolder( name=hub.title, item_id=f"{self.instance_id}_{hub.hubIdentifier}", - provider=self.lookup_key, + provider=self.instance_id, icon="mdi-music", ) @@ -1266,7 +1266,7 @@ class PlexProvider(MusicProvider): stream_details = StreamDetails( item_id=plex_track.key, - provider=self.lookup_key, + provider=self.instance_id, audio_format=AudioFormat( content_type=content_type, channels=media.audioChannels, diff --git a/music_assistant/providers/podcast_index/helpers.py b/music_assistant/providers/podcast_index/helpers.py index 858fa8d1..4c254d64 100644 --- a/music_assistant/providers/podcast_index/helpers.py +++ b/music_assistant/providers/podcast_index/helpers.py @@ -81,7 +81,7 @@ async def make_api_request( def parse_podcast_from_feed( - feed_data: dict[str, Any], lookup_key: str, domain: str, instance_id: str + feed_data: dict[str, Any], instance_id: str, domain: str ) -> Podcast | None: """Parse podcast from API feed data.""" feed_url = feed_data.get("url") @@ -94,7 +94,7 @@ def parse_podcast_from_feed( item_id=str(podcast_id), name=feed_data.get("title", "Unknown Podcast"), publisher=feed_data.get("author") or feed_data.get("ownerName", "Unknown"), - provider=lookup_key, + provider=instance_id, provider_mappings={ ProviderMapping( item_id=str(podcast_id), @@ -121,7 +121,7 @@ def parse_podcast_from_feed( MediaItemImage( type=ImageType.THUMB, path=image_url, - provider=lookup_key, + provider=instance_id, remotely_accessible=True, ) ) @@ -143,9 +143,8 @@ def parse_episode_from_data( episode_data: dict[str, Any], podcast_id: str, episode_idx: int, - lookup_key: str, - domain: str, instance_id: str, + domain: str, podcast_name: str | None = None, ) -> PodcastEpisode | None: """Parse episode from API episode data.""" @@ -170,13 +169,13 @@ def parse_episode_from_data( episode = PodcastEpisode( item_id=episode_id, - provider=lookup_key, + provider=instance_id, name=episode_data.get("title", "Unknown Episode"), duration=duration, position=position, podcast=ItemMapping( item_id=podcast_id, - provider=lookup_key, + provider=instance_id, name=podcast_name, media_type=MediaType.PODCAST, ), @@ -210,7 +209,7 @@ def parse_episode_from_data( MediaItemImage( type=ImageType.THUMB, path=image_url, - provider=lookup_key, + provider=instance_id, remotely_accessible=True, ) ) diff --git a/music_assistant/providers/podcast_index/provider.py b/music_assistant/providers/podcast_index/provider.py index 120bf00a..413b3ef4 100644 --- a/music_assistant/providers/podcast_index/provider.py +++ b/music_assistant/providers/podcast_index/provider.py @@ -86,9 +86,7 @@ class PodcastIndexProvider(MusicProvider): podcasts = [] for feed_data in response.get("feeds", []): - podcast = parse_podcast_from_feed( - feed_data, self.lookup_key, self.domain, self.instance_id - ) + podcast = parse_podcast_from_feed(feed_data, self.instance_id, self.domain) if podcast: podcasts.append(podcast) @@ -218,9 +216,7 @@ class PodcastIndexProvider(MusicProvider): # Try by ID first response = await self._api_request("podcasts/byfeedid", params={"id": prov_podcast_id}) if response.get("feed"): - podcast = parse_podcast_from_feed( - response["feed"], self.lookup_key, self.domain, self.instance_id - ) + podcast = parse_podcast_from_feed(response["feed"], self.instance_id, self.domain) if podcast: return podcast except (ProviderUnavailableError, InvalidDataError): @@ -272,9 +268,8 @@ class PodcastIndexProvider(MusicProvider): episode_data, prov_podcast_id, idx, - self.lookup_key, - self.domain, self.instance_id, + self.domain, podcast_name, ) if episode: @@ -303,7 +298,7 @@ class PodcastIndexProvider(MusicProvider): if episode_data: episode = parse_episode_from_data( - episode_data, podcast_id, 0, self.lookup_key, self.domain, self.instance_id + episode_data, podcast_id, 0, self.instance_id, self.domain ) if episode: return episode @@ -341,7 +336,7 @@ class PodcastIndexProvider(MusicProvider): stream_url = episode_data.get("enclosureUrl") if stream_url: return StreamDetails( - provider=self.lookup_key, + provider=self.instance_id, item_id=item_id, audio_format=AudioFormat( content_type=ContentType.try_parse( @@ -381,9 +376,7 @@ class PodcastIndexProvider(MusicProvider): response = await self._api_request(endpoint, params) podcasts = [] for feed_data in response.get("feeds", []): - podcast = parse_podcast_from_feed( - feed_data, self.lookup_key, self.domain, self.instance_id - ) + podcast = parse_podcast_from_feed(feed_data, self.instance_id, self.domain) if podcast: podcasts.append(podcast) return podcasts @@ -444,9 +437,8 @@ class PodcastIndexProvider(MusicProvider): episode_data, podcast_id, idx, - self.lookup_key, - self.domain, self.instance_id, + self.domain, podcast_name, ) if episode: @@ -504,9 +496,7 @@ class PodcastIndexProvider(MusicProvider): podcasts = [] for feed_data in search_response.get("feeds", []): - podcast = parse_podcast_from_feed( - feed_data, self.lookup_key, self.domain, self.instance_id - ) + podcast = parse_podcast_from_feed(feed_data, self.instance_id, self.domain) if podcast: podcasts.append(podcast) diff --git a/music_assistant/providers/podcastfeed/__init__.py b/music_assistant/providers/podcastfeed/__init__.py index 5e82534b..039590d7 100644 --- a/music_assistant/providers/podcastfeed/__init__.py +++ b/music_assistant/providers/podcastfeed/__init__.py @@ -177,7 +177,7 @@ class PodcastMusicprovider(MusicProvider): if item_id == episode["guid"]: stream_url = episode["enclosures"][0]["url"] return StreamDetails( - provider=self.lookup_key, + provider=self.instance_id, item_id=item_id, audio_format=AudioFormat( content_type=ContentType.try_parse(stream_url), @@ -200,9 +200,8 @@ class PodcastMusicprovider(MusicProvider): return parse_podcast( feed_url=self.feed_url, parsed_feed=self.parsed_podcast, - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, mass_item_id=self.podcast_id, ) @@ -214,9 +213,8 @@ class PodcastMusicprovider(MusicProvider): prov_podcast_id=self.podcast_id, episode_cnt=fallback_position, podcast_cover=self.parsed_podcast.get("cover_url"), - lookup_key=self.lookup_key, - domain=self.domain, instance_id=self.instance_id, + domain=self.domain, mass_item_id=episode_obj["guid"], ) # Override remotely_accessible as these providers can have unreliable image URLs diff --git a/music_assistant/providers/qobuz/__init__.py b/music_assistant/providers/qobuz/__init__.py index 80a97d5e..c701f82c 100644 --- a/music_assistant/providers/qobuz/__init__.py +++ b/music_assistant/providers/qobuz/__init__.py @@ -467,7 +467,7 @@ class QobuzProvider(MusicProvider): self.mass.create_task(self._report_playback_started(streamdata)) return StreamDetails( item_id=str(item_id), - provider=self.lookup_key, + provider=self.instance_id, audio_format=AudioFormat( content_type=content_type, sample_rate=int(streamdata["sampling_rate"] * 1000), @@ -551,7 +551,7 @@ class QobuzProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=img, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ) @@ -614,7 +614,7 @@ class QobuzProvider(MusicProvider): if img := self.__get_image(album_obj): album.metadata.add_image( MediaItemImage( - provider=self.lookup_key, + provider=self.instance_id, type=ImageType.THUMB, path=img, remotely_accessible=True, @@ -710,7 +710,7 @@ class QobuzProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=img, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ) @@ -728,7 +728,7 @@ class QobuzProvider(MusicProvider): ) playlist = Playlist( item_id=str(playlist_obj["id"]), - provider=self.instance_id if is_editable else self.lookup_key, + provider=self.instance_id, name=playlist_obj["name"], owner=playlist_obj["owner"]["name"], provider_mappings={ @@ -746,7 +746,7 @@ class QobuzProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=img, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ) diff --git a/music_assistant/providers/radiobrowser/__init__.py b/music_assistant/providers/radiobrowser/__init__.py index 44d9bd30..da22a34c 100644 --- a/music_assistant/providers/radiobrowser/__init__.py +++ b/music_assistant/providers/radiobrowser/__init__.py @@ -333,7 +333,7 @@ class RadioBrowserProvider(MusicProvider): folder.image = MediaItemImage( type=ImageType.THUMB, path=country.favicon, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) items.append(folder) @@ -471,7 +471,7 @@ class RadioBrowserProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=radio_obj.favicon, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ] diff --git a/music_assistant/providers/radioparadise/parsers.py b/music_assistant/providers/radioparadise/parsers.py index 8f04b7d9..062b7ba8 100644 --- a/music_assistant/providers/radioparadise/parsers.py +++ b/music_assistant/providers/radioparadise/parsers.py @@ -14,14 +14,12 @@ from .constants import RADIO_PARADISE_CHANNELS, STATION_ICONS_BASE_URL from .helpers import enhance_title_with_upcoming # noqa: F401 -def parse_radio( - channel_id: str, provider_lookup_key: str, provider_domain: str, instance_id: str -) -> Radio: +def parse_radio(channel_id: str, instance_id: str, provider_domain: str) -> Radio: """Create a Radio object from cached channel information.""" channel_info = RADIO_PARADISE_CHANNELS.get(channel_id, {}) radio = Radio( - provider=provider_lookup_key, + provider=instance_id, item_id=channel_id, name=channel_info.get("name", "Unknown Radio"), provider_mappings={ @@ -40,7 +38,7 @@ def parse_radio( icon_url = f"{STATION_ICONS_BASE_URL}/{station_icon}" radio.metadata.add_image( MediaItemImage( - provider=provider_lookup_key, + provider=instance_id, type=ImageType.THUMB, path=icon_url, remotely_accessible=True, diff --git a/music_assistant/providers/radioparadise/provider.py b/music_assistant/providers/radioparadise/provider.py index d4ffb09f..6b06587b 100644 --- a/music_assistant/providers/radioparadise/provider.py +++ b/music_assistant/providers/radioparadise/provider.py @@ -65,7 +65,7 @@ class RadioParadiseProvider(MusicProvider): stream_details = StreamDetails( item_id=item_id, - provider=self.lookup_key, + provider=self.instance_id, audio_format=AudioFormat( content_type=content_type, channels=2, @@ -112,7 +112,7 @@ class RadioParadiseProvider(MusicProvider): def _parse_radio(self, channel_id: str) -> Radio: """Create a Radio object from cached channel information.""" - return parsers.parse_radio(channel_id, self.lookup_key, self.domain, self.instance_id) + return parsers.parse_radio(channel_id, self.instance_id, self.domain) async def _get_channel_metadata(self, channel_id: str) -> dict[str, Any] | None: """Get current track and upcoming tracks from Radio Paradise's block API. diff --git a/music_assistant/providers/sendspin/player.py b/music_assistant/providers/sendspin/player.py index 0d100a04..eb0e3bb7 100644 --- a/music_assistant/providers/sendspin/player.py +++ b/music_assistant/providers/sendspin/player.py @@ -205,7 +205,7 @@ class SendspinPlayer(Player): PlayerFeature.VOLUME_SET, PlayerFeature.VOLUME_MUTE, } - self._attr_can_group_with = {provider.lookup_key} + self._attr_can_group_with = {provider.instance_id} self._attr_power_control = PLAYER_CONTROL_NONE self._attr_device_info = DeviceInfo() if player_client := sendspin_client.player: diff --git a/music_assistant/providers/siriusxm/__init__.py b/music_assistant/providers/siriusxm/__init__.py index 028c537d..e53c0b8e 100644 --- a/music_assistant/providers/siriusxm/__init__.py +++ b/music_assistant/providers/siriusxm/__init__.py @@ -233,7 +233,7 @@ class SiriusXMProvider(MusicProvider): # See `_channel_updated` for where this is handled. self._current_stream_details = StreamDetails( item_id=item_id, - provider=self.lookup_key, + provider=self.instance_id, audio_format=AudioFormat( content_type=ContentType.AAC, ), @@ -292,7 +292,7 @@ class SiriusXMProvider(MusicProvider): def _parse_radio(self, channel: XMChannel) -> Radio: radio = Radio( - provider=self.lookup_key, + provider=self.instance_id, item_id=channel.id, name=channel.name, provider_mappings={ @@ -314,7 +314,7 @@ class SiriusXMProvider(MusicProvider): if icon is not None: images.append( MediaItemImage( - provider=self.lookup_key, + provider=self.instance_id, type=ImageType.THUMB, path=icon, remotely_accessible=True, @@ -322,7 +322,7 @@ class SiriusXMProvider(MusicProvider): ) images.append( MediaItemImage( - provider=self.lookup_key, + provider=self.instance_id, type=ImageType.LOGO, path=icon, remotely_accessible=True, @@ -332,7 +332,7 @@ class SiriusXMProvider(MusicProvider): if banner is not None: images.append( MediaItemImage( - provider=self.lookup_key, + provider=self.instance_id, type=ImageType.BANNER, path=banner, remotely_accessible=True, @@ -340,7 +340,7 @@ class SiriusXMProvider(MusicProvider): ) images.append( MediaItemImage( - provider=self.lookup_key, + provider=self.instance_id, type=ImageType.LANDSCAPE, path=banner, remotely_accessible=True, diff --git a/music_assistant/providers/snapcast/player.py b/music_assistant/providers/snapcast/player.py index 71533846..dd7e2974 100644 --- a/music_assistant/providers/snapcast/player.py +++ b/music_assistant/providers/snapcast/player.py @@ -83,7 +83,7 @@ class SnapCastPlayer(Player): PlayerFeature.VOLUME_MUTE, PlayerFeature.PLAY_ANNOUNCEMENT, } - self._attr_can_group_with = {self.provider.lookup_key} + self._attr_can_group_with = {self.provider.instance_id} async def volume_set(self, volume_level: int) -> None: """Send VOLUME_SET command to given player.""" diff --git a/music_assistant/providers/sonos/player.py b/music_assistant/providers/sonos/player.py index b57629d4..8f6f7efe 100644 --- a/music_assistant/providers/sonos/player.py +++ b/music_assistant/providers/sonos/player.py @@ -154,7 +154,7 @@ class SonosPlayer(Player): ) self._attr_device_info.model = self.discovery_info["device"]["modelDisplayName"] self._attr_device_info.manufacturer = self._provider.manifest.name - self._attr_can_group_with = {self._provider.lookup_key} + self._attr_can_group_with = {self._provider.instance_id} if SonosCapability.LINE_IN in self.discovery_info["device"]["capabilities"]: self._attr_source_list.append(PLAYER_SOURCE_MAP[SOURCE_LINE_IN]) @@ -572,7 +572,7 @@ class SonosPlayer(Player): if x.player_id != airplay_player.player_id ) else: - self._attr_can_group_with = {self._provider.lookup_key} + self._attr_can_group_with = {self._provider.instance_id} else: # player is group child (synced to another player) group_parent: SonosPlayer = self.mass.players.get( diff --git a/music_assistant/providers/sonos_s1/player.py b/music_assistant/providers/sonos_s1/player.py index 12c11661..1d4137ae 100644 --- a/music_assistant/providers/sonos_s1/player.py +++ b/music_assistant/providers/sonos_s1/player.py @@ -85,7 +85,7 @@ class SonosPlayer(Player): self._attr_needs_poll = True self._attr_poll_interval = 5 self._attr_available = True - self._attr_can_group_with = {provider.lookup_key} + self._attr_can_group_with = {provider.instance_id} # Subscriptions and events self._subscriptions: list[SubscriptionBase] = [] @@ -708,7 +708,7 @@ class SonosPlayer(Player): except TimeoutError: self.logger.warning("Timeout waiting for target groups %s", groups) - if players := self.mass.players.all(provider_filter=_provider.lookup_key): + if players := self.mass.players.all(provider_filter=_provider.instance_id): any_speaker = cast("SonosPlayer", players[0]) any_speaker.soco.zone_group_state.clear_cache() diff --git a/music_assistant/providers/sonos_s1/provider.py b/music_assistant/providers/sonos_s1/provider.py index 91d81c2a..37011b59 100644 --- a/music_assistant/providers/sonos_s1/provider.py +++ b/music_assistant/providers/sonos_s1/provider.py @@ -57,7 +57,7 @@ class SonosPlayerProvider(PlayerProvider): while self._discovery_running: await asyncio.sleep(0.5) # Clean up subscriptions and connections - for sonos_player in self.mass.players.all(provider_filter=self.lookup_key): + for sonos_player in self.mass.players.all(provider_filter=self.instance_id): sonos_player = cast("SonosPlayer", sonos_player) await sonos_player.offline() # Stop the async event listener diff --git a/music_assistant/providers/soundcloud/__init__.py b/music_assistant/providers/soundcloud/__init__.py index 41323299..955d1755 100644 --- a/music_assistant/providers/soundcloud/__init__.py +++ b/music_assistant/providers/soundcloud/__init__.py @@ -233,7 +233,7 @@ class SoundcloudMusicProvider(MusicProvider): folder = RecommendationFolder( name=collection["title"], item_id=f"{self.instance_id}_{collection['id']}", - provider=self.lookup_key, + provider=self.instance_id, icon="mdi-playlist-music", ) for playlist in collection.get("items").get("collection", []): @@ -252,7 +252,7 @@ class SoundcloudMusicProvider(MusicProvider): folder = RecommendationFolder( name="SoundCloud Feed", item_id=f"{self.instance_id}_sc_subscribed_feed", - provider=self.lookup_key, + provider=self.instance_id, icon="mdi-rss", ) for item in feed["collection"]: @@ -372,7 +372,7 @@ class SoundcloudMusicProvider(MusicProvider): """Return the content details for the given track when it will be streamed.""" url: str = await self._soundcloud.get_stream_url(track_id=item_id, presets=["mp3"]) return StreamDetails( - provider=self.lookup_key, + provider=self.instance_id, item_id=item_id, # let ffmpeg work out the details itself as # soundcloud uses a mix of different content types and streaming methods @@ -419,7 +419,7 @@ class SoundcloudMusicProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=img_url, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ] @@ -453,7 +453,7 @@ class SoundcloudMusicProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=self._transform_artwork_url(playlist_obj["artwork_url"]), - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ] @@ -499,7 +499,7 @@ class SoundcloudMusicProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=self._transform_artwork_url(track_obj["artwork_url"]), - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ] diff --git a/music_assistant/providers/spotify/parsers.py b/music_assistant/providers/spotify/parsers.py index 81720fc8..626813d9 100644 --- a/music_assistant/providers/spotify/parsers.py +++ b/music_assistant/providers/spotify/parsers.py @@ -28,7 +28,7 @@ if TYPE_CHECKING: def parse_images( - images_list: list[dict[str, Any]], lookup_key: str, exclude_generic: bool = False + images_list: list[dict[str, Any]], instance_id: str, exclude_generic: bool = False ) -> UniqueList[MediaItemImage]: """Parse images list into MediaItemImage objects.""" if not images_list: @@ -54,7 +54,7 @@ def parse_images( MediaItemImage( type=ImageType.THUMB, path=best_image["url"], - provider=lookup_key, + provider=instance_id, remotely_accessible=True, ) ] @@ -65,7 +65,7 @@ def parse_artist(artist_obj: dict[str, Any], provider: SpotifyProvider) -> Artis """Parse spotify artist object to generic layout.""" artist = Artist( item_id=artist_obj["id"], - provider=provider.lookup_key, + provider=provider.instance_id, name=artist_obj["name"] or artist_obj["id"], provider_mappings={ ProviderMapping( @@ -81,7 +81,7 @@ def parse_artist(artist_obj: dict[str, Any], provider: SpotifyProvider) -> Artis # Use unified image parsing with generic exclusion artist.metadata.images = parse_images( - artist_obj.get("images", []), provider.lookup_key, exclude_generic=True + artist_obj.get("images", []), provider.instance_id, exclude_generic=True ) return artist @@ -91,7 +91,7 @@ def parse_album(album_obj: dict[str, Any], provider: SpotifyProvider) -> Album: name, version = parse_title_and_version(album_obj["name"]) album = Album( item_id=album_obj["id"], - provider=provider.lookup_key, + provider=provider.instance_id, name=name, version=version, provider_mappings={ @@ -125,7 +125,7 @@ def parse_album(album_obj: dict[str, Any], provider: SpotifyProvider) -> Album: if "genres" in album_obj: album.metadata.genres = set(album_obj["genres"]) - album.metadata.images = parse_images(album_obj.get("images", []), provider.lookup_key) + album.metadata.images = parse_images(album_obj.get("images", []), provider.instance_id) if "label" in album_obj: album.metadata.label = album_obj["label"] @@ -147,7 +147,7 @@ def parse_track( name, version = parse_title_and_version(track_obj["name"]) track = Track( item_id=track_obj["id"], - provider=provider.lookup_key, + provider=provider.instance_id, name=name, version=version, duration=track_obj["duration_ms"] / 1000, @@ -182,7 +182,7 @@ def parse_track( if "album" in track_obj: track.album = parse_album(track_obj["album"], provider) track.metadata.images = parse_images( - track_obj["album"].get("images", []), provider.lookup_key + track_obj["album"].get("images", []), provider.instance_id ) if track_obj.get("copyright"): track.metadata.copyright = track_obj["copyright"] @@ -206,7 +206,7 @@ def parse_playlist(playlist_obj: dict[str, Any], provider: SpotifyProvider) -> P playlist = Playlist( item_id=playlist_obj["id"], - provider=provider.instance_id if is_editable else provider.lookup_key, + provider=provider.instance_id, name=playlist_obj["name"], owner=owner_name, provider_mappings={ @@ -220,7 +220,7 @@ def parse_playlist(playlist_obj: dict[str, Any], provider: SpotifyProvider) -> P is_editable=is_editable, ) - playlist.metadata.images = parse_images(playlist_obj.get("images", []), provider.lookup_key) + playlist.metadata.images = parse_images(playlist_obj.get("images", []), provider.instance_id) return playlist @@ -228,7 +228,7 @@ def parse_podcast(podcast_obj: dict[str, Any], provider: SpotifyProvider) -> Pod """Parse spotify podcast (show) object to generic layout.""" podcast = Podcast( item_id=podcast_obj["id"], - provider=provider.lookup_key, + provider=provider.instance_id, name=podcast_obj["name"], provider_mappings={ ProviderMapping( @@ -246,7 +246,7 @@ def parse_podcast(podcast_obj: dict[str, Any], provider: SpotifyProvider) -> Pod if podcast_obj.get("description"): podcast.metadata.description = podcast_obj["description"] - podcast.metadata.images = parse_images(podcast_obj.get("images", []), provider.lookup_key) + podcast.metadata.images = parse_images(podcast_obj.get("images", []), provider.instance_id) if "explicit" in podcast_obj: podcast.metadata.explicit = podcast_obj["explicit"] @@ -266,7 +266,7 @@ def parse_podcast_episode( if podcast is None and "show" in episode_obj: podcast = Podcast( item_id=episode_obj["show"]["id"], - provider=provider.lookup_key, + provider=provider.instance_id, name=episode_obj["show"]["name"], provider_mappings={ ProviderMapping( @@ -281,14 +281,14 @@ def parse_podcast_episode( # Create a minimal podcast reference if none available podcast = Podcast( item_id="unknown", - provider=provider.lookup_key, + provider=provider.instance_id, name="Unknown Podcast", provider_mappings=set(), ) episode = PodcastEpisode( item_id=episode_obj["id"], - provider=provider.lookup_key, + provider=provider.instance_id, name=episode_obj["name"], duration=episode_obj["duration_ms"] // 1000 if episode_obj.get("duration_ms") else 0, podcast=podcast, @@ -322,7 +322,7 @@ def parse_podcast_episode( episode.metadata.release_date = datetime.fromisoformat(date_str) - episode.metadata.images = parse_images(episode_obj.get("images", []), provider.lookup_key) + episode.metadata.images = parse_images(episode_obj.get("images", []), provider.instance_id) # Use podcast artwork if episode has none if not episode.metadata.images and isinstance(podcast, Podcast) and podcast.metadata.images: @@ -385,7 +385,7 @@ def parse_audiobook(audiobook_obj: dict[str, Any], provider: SpotifyProvider) -> if audiobook_obj.get("publisher"): audiobook.publisher = audiobook_obj["publisher"] - audiobook.metadata.images = parse_images(audiobook_obj.get("images", []), provider.lookup_key) + audiobook.metadata.images = parse_images(audiobook_obj.get("images", []), provider.instance_id) if audiobook_obj.get("explicit"): audiobook.metadata.explicit = audiobook_obj["explicit"] diff --git a/music_assistant/providers/spotify/provider.py b/music_assistant/providers/spotify/provider.py index 5b6f887d..631f4935 100644 --- a/music_assistant/providers/spotify/provider.py +++ b/music_assistant/providers/spotify/provider.py @@ -695,7 +695,7 @@ class SpotifyProvider(MusicProvider): # For all other media types (tracks, podcast episodes) return StreamDetails( item_id=item_id, - provider=self.lookup_key, + provider=self.instance_id, media_type=media_type, audio_format=AudioFormat(content_type=ContentType.OGG, bit_rate=320), stream_type=StreamType.CUSTOM, @@ -845,7 +845,7 @@ class SpotifyProvider(MusicProvider): liked_songs = Playlist( item_id=self._get_liked_songs_playlist_id(), - provider=self.lookup_key, + provider=self.instance_id, name=f"Liked Songs {self._sp_user['display_name']}", # TODO to be translated owner=self._sp_user["display_name"], provider_mappings={ @@ -864,7 +864,7 @@ class SpotifyProvider(MusicProvider): image = MediaItemImage( type=ImageType.THUMB, path="https://misc.scdn.co/liked-songs/liked-songs-64.png", - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) if liked_songs.metadata.images is None: diff --git a/music_assistant/providers/squeezelite/player.py b/music_assistant/providers/squeezelite/player.py index c1a5f6f3..1419bfdd 100644 --- a/music_assistant/providers/squeezelite/player.py +++ b/music_assistant/providers/squeezelite/player.py @@ -93,7 +93,7 @@ class SqueezelitePlayer(Player): PlayerFeature.GAPLESS_PLAYBACK, PlayerFeature.GAPLESS_DIFFERENT_SAMPLERATE, } - self._attr_can_group_with = {provider.lookup_key} + self._attr_can_group_with = {provider.instance_id} self.multi_client_stream: MultiClientStream | None = None self._sync_playpoints: deque[SyncPlayPoint] = deque(maxlen=MIN_REQ_PLAYPOINTS) self._do_not_resync_before: float = 0.0 diff --git a/music_assistant/providers/test/__init__.py b/music_assistant/providers/test/__init__.py index b006ad04..8703589d 100644 --- a/music_assistant/providers/test/__init__.py +++ b/music_assistant/providers/test/__init__.py @@ -149,7 +149,7 @@ class TestProvider(MusicProvider): artist_idx, album_idx, track_idx = prov_track_id.split("_", 3) return Track( item_id=prov_track_id, - provider=self.lookup_key, + provider=self.instance_id, name=f"Test Track {artist_idx} - {album_idx} - {track_idx}", duration=60, artists=UniqueList([await self.get_artist(artist_idx)]), @@ -170,7 +170,7 @@ class TestProvider(MusicProvider): """Get full artist details by id.""" return Artist( item_id=prov_artist_id, - provider=self.lookup_key, + provider=self.instance_id, name=f"Test Artist {prov_artist_id}", metadata=MediaItemMetadata(images=UniqueList([DEFAULT_THUMB, DEFAULT_FANART])), provider_mappings={ @@ -187,7 +187,7 @@ class TestProvider(MusicProvider): artist_idx, album_idx = prov_album_id.split("_", 2) return Album( item_id=prov_album_id, - provider=self.lookup_key, + provider=self.instance_id, name=f"Test Album {album_idx}", artists=UniqueList([await self.get_artist(artist_idx)]), provider_mappings={ @@ -204,7 +204,7 @@ class TestProvider(MusicProvider): """Get full podcast details by id.""" return Podcast( item_id=prov_podcast_id, - provider=self.lookup_key, + provider=self.instance_id, name=f"Test Podcast {prov_podcast_id}", metadata=MediaItemMetadata(images=UniqueList([DEFAULT_THUMB])), provider_mappings={ @@ -221,7 +221,7 @@ class TestProvider(MusicProvider): """Get full audiobook details by id.""" return Audiobook( item_id=prov_audiobook_id, - provider=self.lookup_key, + provider=self.instance_id, name=f"Test Audiobook {prov_audiobook_id}", metadata=MediaItemMetadata( images=UniqueList([DEFAULT_THUMB]), @@ -305,12 +305,12 @@ class TestProvider(MusicProvider): podcast_id, episode_idx = prov_episode_id.split("_", 2) return PodcastEpisode( item_id=prov_episode_id, - provider=self.lookup_key, + provider=self.instance_id, name=f"Test PodcastEpisode {podcast_id}-{episode_idx}", duration=60, podcast=ItemMapping( item_id=podcast_id, - provider=self.lookup_key, + provider=self.instance_id, name=f"Test Podcast {podcast_id}", media_type=MediaType.PODCAST, image=DEFAULT_THUMB, @@ -332,7 +332,7 @@ class TestProvider(MusicProvider): async def get_stream_details(self, item_id: str, media_type: MediaType) -> StreamDetails: """Get streamdetails for a track/radio.""" return StreamDetails( - provider=self.lookup_key, + provider=self.instance_id, item_id=item_id, audio_format=AudioFormat( content_type=ContentType.OGG, diff --git a/music_assistant/providers/theaudiodb/__init__.py b/music_assistant/providers/theaudiodb/__init__.py index 384c5f07..4fb37165 100644 --- a/music_assistant/providers/theaudiodb/__init__.py +++ b/music_assistant/providers/theaudiodb/__init__.py @@ -256,7 +256,7 @@ class AudioDbMetadataProvider(MetadataProvider): MediaItemImage( type=img_type, path=img, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ) @@ -304,7 +304,7 @@ class AudioDbMetadataProvider(MetadataProvider): MediaItemImage( type=img_type, path=img, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ) @@ -358,7 +358,7 @@ class AudioDbMetadataProvider(MetadataProvider): MediaItemImage( type=img_type, path=img, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ) diff --git a/music_assistant/providers/tidal/parsers.py b/music_assistant/providers/tidal/parsers.py index 0a6cf519..142fb6c4 100644 --- a/music_assistant/providers/tidal/parsers.py +++ b/music_assistant/providers/tidal/parsers.py @@ -37,7 +37,7 @@ def parse_artist(provider: TidalProvider, artist_obj: dict[str, Any]) -> Artist: artist_id = str(artist_obj["id"]) artist = Artist( item_id=artist_id, - provider=provider.lookup_key, + provider=provider.instance_id, name=artist_obj["name"], provider_mappings={ ProviderMapping( @@ -59,7 +59,7 @@ def parse_artist(provider: TidalProvider, artist_obj: dict[str, Any]) -> Artist: MediaItemImage( type=ImageType.THUMB, path=image_url, - provider=provider.lookup_key, + provider=provider.instance_id, remotely_accessible=True, ) ] @@ -76,7 +76,7 @@ def parse_album(provider: TidalProvider, album_obj: dict[str, Any]) -> Album: album = Album( item_id=album_id, - provider=provider.lookup_key, + provider=provider.instance_id, name=name, version=version, provider_mappings={ @@ -147,7 +147,7 @@ def parse_album(provider: TidalProvider, album_obj: dict[str, Any]) -> Album: MediaItemImage( type=ImageType.THUMB, path=image_url, - provider=provider.lookup_key, + provider=provider.instance_id, remotely_accessible=True, ) ] @@ -169,7 +169,7 @@ def parse_track( hi_res_lossless = any(tag in tags for tag in ["HIRES_LOSSLESS", "HI_RES_LOSSLESS"]) track = Track( item_id=track_id, - provider=provider.lookup_key, + provider=provider.instance_id, name=track_obj.get("title", "Unknown"), version=version, duration=track_obj.get("duration", 0), @@ -220,7 +220,7 @@ def parse_track( MediaItemImage( type=ImageType.THUMB, path=image_url, - provider=provider.lookup_key, + provider=provider.instance_id, remotely_accessible=True, ) ] @@ -263,7 +263,7 @@ def parse_playlist( playlist = Playlist( item_id=playlist_id, - provider=provider.instance_id if is_editable else provider.lookup_key, + provider=provider.instance_id, name=playlist_obj.get("title", "Unknown"), owner=owner_name, provider_mappings={ @@ -295,7 +295,7 @@ def parse_playlist( MediaItemImage( type=ImageType.THUMB, path=image_url, - provider=provider.lookup_key, + provider=provider.instance_id, remotely_accessible=True, ) ] @@ -308,7 +308,7 @@ def parse_playlist( MediaItemImage( type=ImageType.THUMB, path=image_url, - provider=provider.lookup_key, + provider=provider.instance_id, remotely_accessible=True, ) ] diff --git a/music_assistant/providers/tidal/provider.py b/music_assistant/providers/tidal/provider.py index b4afbc91..3185d8e6 100644 --- a/music_assistant/providers/tidal/provider.py +++ b/music_assistant/providers/tidal/provider.py @@ -192,7 +192,7 @@ class TidalProvider(MusicProvider): return ItemMapping( media_type=media_type, item_id=key, - provider=self.lookup_key, + provider=self.instance_id, name=name, ) diff --git a/music_assistant/providers/tidal/recommendations.py b/music_assistant/providers/tidal/recommendations.py index 844c72d5..64f270d8 100644 --- a/music_assistant/providers/tidal/recommendations.py +++ b/music_assistant/providers/tidal/recommendations.py @@ -110,7 +110,7 @@ class TidalRecommendationManager: RecommendationFolder( item_id=item_id, name=folder_name, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, items=UniqueList[MediaItemType | ItemMapping | BrowseFolder](unique_items), subtitle=f"From {page_name} • {len(unique_items)} items", translation_key=item_id, diff --git a/music_assistant/providers/tidal/streaming.py b/music_assistant/providers/tidal/streaming.py index 194bf95f..3830f64a 100644 --- a/music_assistant/providers/tidal/streaming.py +++ b/music_assistant/providers/tidal/streaming.py @@ -74,7 +74,7 @@ class TidalStreamingManager: return StreamDetails( item_id=track.item_id, - provider=self.provider.lookup_key, + provider=self.provider.instance_id, audio_format=AudioFormat( content_type=content_type, sample_rate=stream_data.get("sampleRate", 44100), diff --git a/music_assistant/providers/tunein/__init__.py b/music_assistant/providers/tunein/__init__.py index 8c747060..671a9bdd 100644 --- a/music_assistant/providers/tunein/__init__.py +++ b/music_assistant/providers/tunein/__init__.py @@ -182,7 +182,7 @@ class TuneInProvider(MusicProvider): preferred_stream = stream_info[0] radio = Radio( item_id=details["preset_id"], - provider=self.lookup_key, + provider=self.instance_id, name=name, provider_mappings={ ProviderMapping( @@ -202,7 +202,7 @@ class TuneInProvider(MusicProvider): # custom url (no stream object present) radio = Radio( item_id=details["URL"], - provider=self.lookup_key, + provider=self.instance_id, name=name, provider_mappings={ ProviderMapping( @@ -230,7 +230,7 @@ class TuneInProvider(MusicProvider): MediaItemImage( type=ImageType.THUMB, path=img, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ] @@ -267,7 +267,7 @@ class TuneInProvider(MusicProvider): if item_id.startswith("http"): # custom url return StreamDetails( - provider=self.lookup_key, + provider=self.instance_id, item_id=item_id, audio_format=AudioFormat( content_type=ContentType.UNKNOWN, @@ -286,7 +286,7 @@ class TuneInProvider(MusicProvider): # and the first one is the best quality preferred_stream = stream_info[0] return StreamDetails( - provider=self.lookup_key, + provider=self.instance_id, item_id=item_id, # set contenttype to unknown so ffmpeg can auto detect it audio_format=AudioFormat(content_type=ContentType.UNKNOWN), diff --git a/music_assistant/providers/universal_group/player.py b/music_assistant/providers/universal_group/player.py index b94a0479..ed5a9f0e 100644 --- a/music_assistant/providers/universal_group/player.py +++ b/music_assistant/providers/universal_group/player.py @@ -74,9 +74,9 @@ class UniversalGroupPlayer(GroupPlayer): ) # allow grouping with all providers, except the ugp provider itself self._attr_can_group_with = { - x.lookup_key + x.instance_id for x in self.mass.players.providers - if x.lookup_key != self.provider.lookup_key + if x.instance_id != self.provider.instance_id } self._set_attributes() diff --git a/music_assistant/providers/universal_group/provider.py b/music_assistant/providers/universal_group/provider.py index 9d22310c..b742a0a4 100644 --- a/music_assistant/providers/universal_group/provider.py +++ b/music_assistant/providers/universal_group/provider.py @@ -30,7 +30,7 @@ class UniversalGroupProvider(PlayerProvider): player_id = f"{UGP_PREFIX}{shortuuid.random(8).lower()}" self.mass.config.create_default_player_config( player_id=player_id, - provider=self.lookup_key, + provider=self.instance_id, name=name, enabled=True, values={ @@ -53,7 +53,7 @@ class UniversalGroupProvider(PlayerProvider): async def discover_players(self) -> None: """Discover players.""" - for player_conf in await self.mass.config.get_player_configs(self.lookup_key): + for player_conf in await self.mass.config.get_player_configs(self.instance_id): if player_conf.player_id.startswith(UGP_PREFIX): await self._register_player(player_conf.player_id) diff --git a/music_assistant/providers/ytmusic/__init__.py b/music_assistant/providers/ytmusic/__init__.py index 771eeb1d..ab68bc17 100644 --- a/music_assistant/providers/ytmusic/__init__.py +++ b/music_assistant/providers/ytmusic/__init__.py @@ -621,7 +621,7 @@ class YoutubeMusicProvider(MusicProvider): stream_format = await self._get_stream_format(item_id=item_id) self.logger.debug("Found stream_format: %s for song %s", stream_format["format"], item_id) stream_details = StreamDetails( - provider=self.lookup_key, + provider=self.instance_id, item_id=item_id, audio_format=AudioFormat( content_type=ContentType.try_parse(stream_format["audio_ext"]), @@ -649,7 +649,7 @@ class YoutubeMusicProvider(MusicProvider): folder = RecommendationFolder( name=section["title"], item_id=f"{self.instance_id}_{section['title']}", - provider=self.lookup_key, + provider=self.instance_id, icon=determine_recommendation_icon(section["title"]), ) for recommended_item in section.get("contents", []): @@ -746,7 +746,7 @@ class YoutubeMusicProvider(MusicProvider): album = Album( item_id=album_id, name=name, - provider=self.lookup_key, + provider=self.instance_id, provider_mappings={ ProviderMapping( item_id=str(album_id), @@ -808,7 +808,7 @@ class YoutubeMusicProvider(MusicProvider): artist = Artist( item_id=artist_id, name=artist_obj["name"], - provider=self.lookup_key, + provider=self.instance_id, provider_mappings={ ProviderMapping( item_id=str(artist_id), @@ -837,7 +837,7 @@ class YoutubeMusicProvider(MusicProvider): playlist_name = f"{playlist_name} ({self.name})" playlist = Playlist( item_id=playlist_id, - provider=self.instance_id if is_editable else self.lookup_key, + provider=self.instance_id, name=playlist_name, provider_mappings={ ProviderMapping( @@ -874,7 +874,7 @@ class YoutubeMusicProvider(MusicProvider): track_id = str(track_obj["videoId"]) track = Track( item_id=track_id, - provider=self.lookup_key, + provider=self.instance_id, name=track_obj["title"], provider_mappings={ ProviderMapping( @@ -930,7 +930,7 @@ class YoutubeMusicProvider(MusicProvider): podcast = Podcast( item_id=podcast_obj["podcastId"], name=podcast_obj["title"], - provider=self.lookup_key, + provider=self.instance_id, provider_mappings={ ProviderMapping( item_id=podcast_obj["podcastId"], @@ -956,7 +956,7 @@ class YoutubeMusicProvider(MusicProvider): item_id = f"{podcast.item_id}{PODCAST_EPISODE_SPLITTER}{episode_id}" episode = PodcastEpisode( item_id=item_id, - provider=self.lookup_key, + provider=self.instance_id, name=episode_obj.get("title"), podcast=podcast, provider_mappings={ @@ -1024,7 +1024,7 @@ class YoutubeMusicProvider(MusicProvider): return ItemMapping( media_type=media_type, item_id=key, - provider=self.lookup_key, + provider=self.instance_id, name=name, ) @@ -1079,7 +1079,7 @@ class YoutubeMusicProvider(MusicProvider): MediaItemImage( type=image_type, path=url, - provider=self.lookup_key, + provider=self.instance_id, remotely_accessible=True, ) ) diff --git a/tests/providers/nicovideo/__snapshots__/test_converters.ambr b/tests/providers/nicovideo/__snapshots__/test_converters.ambr index b5630955..27e0fa24 100644 --- a/tests/providers/nicovideo/__snapshots__/test_converters.ambr +++ b/tests/providers/nicovideo/__snapshots__/test_converters.ambr @@ -34,7 +34,7 @@ }), 'name': '', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -57,7 +57,7 @@ ]), 'sort_name': '', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -96,7 +96,7 @@ }), 'name': 'テストシリーズ68461151-527007', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -119,7 +119,7 @@ ]), 'sort_name': 'テストシリース68461151-527007', 'translation_key': None, - 'uri': 'nicovideo://album/527007', + 'uri': 'nicovideo_test://album/527007', 'version': '', 'year': None, }) @@ -160,7 +160,7 @@ }), 'name': 'ゲスト', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -183,7 +183,7 @@ ]), 'sort_name': 'ケスト', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -222,7 +222,7 @@ }), 'name': 'テストシリーズ68461151-527007', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -245,7 +245,7 @@ ]), 'sort_name': 'テストシリース68461151-527007', 'translation_key': None, - 'uri': 'nicovideo://album/527007', + 'uri': 'nicovideo_test://album/527007', 'version': '', 'year': None, }), @@ -270,7 +270,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -296,7 +296,7 @@ }), 'name': 'ゲスト', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -319,7 +319,7 @@ ]), 'sort_name': 'ケスト', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -342,13 +342,13 @@ 'images': list([ dict({ 'path': 'https://img.cdn.nimg.jp/s/nicovideo/thumbnails/45285955/45285955.27227006.original/r640x360l?key=7098d92a9c12d0b14101a4c3c8297e04c6e7a59eb59ffe9e71c88fa0181b0cb5', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), dict({ 'path': 'https://nicovideo.cdn.nimg.jp/thumbnails/45285955/45285955.27227006.L', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -374,7 +374,7 @@ }), 'name': 'APIテスト用', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -398,7 +398,7 @@ 'sort_name': 'apiテスト用', 'track_number': 0, 'translation_key': None, - 'uri': 'nicovideo://track/sm45285955', + 'uri': 'nicovideo_test://track/sm45285955', 'version': '', }), ]), @@ -439,7 +439,7 @@ }), 'name': '', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -462,7 +462,7 @@ ]), 'sort_name': '', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -501,7 +501,7 @@ }), 'name': 'テストシリーズ68461151-527007', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -524,7 +524,7 @@ ]), 'sort_name': 'テストシリース68461151-527007', 'translation_key': None, - 'uri': 'nicovideo://album/527007', + 'uri': 'nicovideo_test://album/527007', 'version': '', 'year': None, }) @@ -547,7 +547,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -573,7 +573,7 @@ }), 'name': '中の', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -596,7 +596,7 @@ ]), 'sort_name': '中の', 'translation_key': None, - 'uri': 'nicovideo://artist/4', + 'uri': 'nicovideo_test://artist/4', 'version': '', }) # --- @@ -618,7 +618,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -644,7 +644,7 @@ }), 'name': 'ゲスト', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -667,7 +667,7 @@ ]), 'sort_name': 'ケスト', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }) # --- @@ -692,7 +692,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -718,7 +718,7 @@ }), 'name': 'ゲスト', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -741,7 +741,7 @@ ]), 'sort_name': 'ケスト', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -764,13 +764,13 @@ 'images': list([ dict({ 'path': 'https://img.cdn.nimg.jp/s/nicovideo/thumbnails/45285955/45285955.27227006.original/r640x360l?key=7098d92a9c12d0b14101a4c3c8297e04c6e7a59eb59ffe9e71c88fa0181b0cb5', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), dict({ 'path': 'https://nicovideo.cdn.nimg.jp/thumbnails/45285955/45285955.27227006.L', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -796,7 +796,7 @@ }), 'name': 'APIテスト用', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -820,7 +820,7 @@ 'sort_name': 'apiテスト用', 'track_number': 0, 'translation_key': None, - 'uri': 'nicovideo://track/sm45285955', + 'uri': 'nicovideo_test://track/sm45285955', 'version': '', }) # --- @@ -845,7 +845,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -871,7 +871,7 @@ }), 'name': 'ゲスト', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -894,7 +894,7 @@ ]), 'sort_name': 'ケスト', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -917,13 +917,13 @@ 'images': list([ dict({ 'path': 'https://img.cdn.nimg.jp/s/nicovideo/thumbnails/45285955/45285955.27227006.original/r640x360l?key=7098d92a9c12d0b14101a4c3c8297e04c6e7a59eb59ffe9e71c88fa0181b0cb5', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), dict({ 'path': 'https://nicovideo.cdn.nimg.jp/thumbnails/45285955/45285955.27227006.L', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -949,7 +949,7 @@ }), 'name': 'APIテスト用', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -973,7 +973,7 @@ 'sort_name': 'apiテスト用', 'track_number': 0, 'translation_key': None, - 'uri': 'nicovideo://track/sm45285955', + 'uri': 'nicovideo_test://track/sm45285955', 'version': '', }) # --- @@ -996,7 +996,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -1023,7 +1023,7 @@ 'name': 'テストマイリスト68461151-78597499', 'owner': '68461151', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -1046,7 +1046,7 @@ ]), 'sort_name': 'テストマイリスト68461151-78597499', 'translation_key': None, - 'uri': 'nicovideo://playlist/78597499', + 'uri': 'nicovideo_test://playlist/78597499', 'version': '', }) # --- @@ -1069,7 +1069,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -1096,7 +1096,7 @@ 'name': 'テストマイリスト68461151-78597499', 'owner': '68461151', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -1119,7 +1119,7 @@ ]), 'sort_name': 'テストマイリスト68461151-78597499', 'translation_key': None, - 'uri': 'nicovideo://playlist/78597499', + 'uri': 'nicovideo_test://playlist/78597499', 'version': '', }) # --- @@ -1143,7 +1143,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -1170,7 +1170,7 @@ 'name': 'テストマイリスト68461151-78597499', 'owner': '68461151', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -1193,7 +1193,7 @@ ]), 'sort_name': 'テストマイリスト68461151-78597499', 'translation_key': None, - 'uri': 'nicovideo://playlist/78597499', + 'uri': 'nicovideo_test://playlist/78597499', 'version': '', }), 'tracks': list([ @@ -1217,7 +1217,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -1243,7 +1243,7 @@ }), 'name': 'ゲスト', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -1266,7 +1266,7 @@ ]), 'sort_name': 'ケスト', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -1289,13 +1289,13 @@ 'images': list([ dict({ 'path': 'https://img.cdn.nimg.jp/s/nicovideo/thumbnails/45285955/45285955.27227006.original/r640x360l?key=7098d92a9c12d0b14101a4c3c8297e04c6e7a59eb59ffe9e71c88fa0181b0cb5', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), dict({ 'path': 'https://nicovideo.cdn.nimg.jp/thumbnails/45285955/45285955.27227006.L', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -1321,7 +1321,7 @@ }), 'name': 'APIテスト用', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -1345,7 +1345,7 @@ 'sort_name': 'apiテスト用', 'track_number': 0, 'translation_key': None, - 'uri': 'nicovideo://track/sm45285955', + 'uri': 'nicovideo_test://track/sm45285955', 'version': '', }), ]), @@ -1370,7 +1370,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -1397,7 +1397,7 @@ 'name': 'テストマイリスト68461151-78597499', 'owner': '68461151', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -1420,7 +1420,7 @@ ]), 'sort_name': 'テストマイリスト68461151-78597499', 'translation_key': None, - 'uri': 'nicovideo://playlist/78597499', + 'uri': 'nicovideo_test://playlist/78597499', 'version': '', }) # --- @@ -1459,7 +1459,7 @@ }), 'name': 'ゲスト', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -1482,7 +1482,7 @@ ]), 'sort_name': 'ケスト', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -1521,7 +1521,7 @@ }), 'name': 'テストシリーズ68461151-527007', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -1544,7 +1544,7 @@ ]), 'sort_name': 'テストシリース68461151-527007', 'translation_key': None, - 'uri': 'nicovideo://album/527007', + 'uri': 'nicovideo_test://album/527007', 'version': '', 'year': None, }) @@ -1570,7 +1570,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -1596,7 +1596,7 @@ }), 'name': 'ゲスト', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -1619,7 +1619,7 @@ ]), 'sort_name': 'ケスト', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -1642,13 +1642,13 @@ 'images': list([ dict({ 'path': 'https://img.cdn.nimg.jp/s/nicovideo/thumbnails/45285955/45285955.27227006.original/r640x360l?key=7098d92a9c12d0b14101a4c3c8297e04c6e7a59eb59ffe9e71c88fa0181b0cb5', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), dict({ 'path': 'https://nicovideo.cdn.nimg.jp/thumbnails/45285955/45285955.27227006.L', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -1674,7 +1674,7 @@ }), 'name': 'APIテスト用', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -1698,7 +1698,7 @@ 'sort_name': 'apiテスト用', 'track_number': 0, 'translation_key': None, - 'uri': 'nicovideo://track/sm45285955', + 'uri': 'nicovideo_test://track/sm45285955', 'version': '', }) # --- @@ -1723,7 +1723,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -1749,7 +1749,7 @@ }), 'name': 'ゲスト', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -1772,7 +1772,7 @@ ]), 'sort_name': 'ケスト', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -1795,13 +1795,13 @@ 'images': list([ dict({ 'path': 'https://img.cdn.nimg.jp/s/nicovideo/thumbnails/45285955/45285955.27227006.original/r640x360l?key=7098d92a9c12d0b14101a4c3c8297e04c6e7a59eb59ffe9e71c88fa0181b0cb5', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), dict({ 'path': 'https://nicovideo.cdn.nimg.jp/thumbnails/45285955/45285955.27227006.L', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -1827,7 +1827,7 @@ }), 'name': 'APIテスト用', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -1851,7 +1851,7 @@ 'sort_name': 'apiテスト用', 'track_number': 0, 'translation_key': None, - 'uri': 'nicovideo://track/sm45285955', + 'uri': 'nicovideo_test://track/sm45285955', 'version': '', }) # --- @@ -1914,7 +1914,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -1940,7 +1940,7 @@ }), 'name': 'ゲスト', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -1963,7 +1963,7 @@ ]), 'sort_name': 'ケスト', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -1986,13 +1986,13 @@ 'images': list([ dict({ 'path': 'https://img.cdn.nimg.jp/s/nicovideo/thumbnails/45285955/45285955.27227006.original/r640x360l?key=7098d92a9c12d0b14101a4c3c8297e04c6e7a59eb59ffe9e71c88fa0181b0cb5', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), dict({ 'path': 'https://nicovideo.cdn.nimg.jp/thumbnails/45285955/45285955.27227006.L', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -2018,7 +2018,7 @@ }), 'name': 'APIテスト用', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -2042,7 +2042,7 @@ 'sort_name': 'apiテスト用', 'track_number': 0, 'translation_key': None, - 'uri': 'nicovideo://track/sm45285955', + 'uri': 'nicovideo_test://track/sm45285955', 'version': '', }) # --- @@ -2067,7 +2067,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -2093,7 +2093,7 @@ }), 'name': 'ゲスト', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -2116,7 +2116,7 @@ ]), 'sort_name': 'ケスト', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -2139,13 +2139,13 @@ 'images': list([ dict({ 'path': 'https://img.cdn.nimg.jp/s/nicovideo/thumbnails/45285955/45285955.27227006.original/r640x360l?key=7098d92a9c12d0b14101a4c3c8297e04c6e7a59eb59ffe9e71c88fa0181b0cb5', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), dict({ 'path': 'https://nicovideo.cdn.nimg.jp/thumbnails/45285955/45285955.27227006.L', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -2171,7 +2171,7 @@ }), 'name': 'APIテスト用', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -2195,7 +2195,7 @@ 'sort_name': 'apiテスト用', 'track_number': 0, 'translation_key': None, - 'uri': 'nicovideo://track/sm45285955', + 'uri': 'nicovideo_test://track/sm45285955', 'version': '', }) # --- @@ -2221,7 +2221,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -2247,7 +2247,7 @@ }), 'name': 'ゲスト', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -2270,7 +2270,7 @@ ]), 'sort_name': 'ケスト', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -2309,7 +2309,7 @@ }), 'name': 'テストシリーズ68461151-527007', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -2332,7 +2332,7 @@ ]), 'sort_name': 'テストシリース68461151-527007', 'translation_key': None, - 'uri': 'nicovideo://album/527007', + 'uri': 'nicovideo_test://album/527007', 'version': '', 'year': None, }), @@ -2354,7 +2354,7 @@ 'images': list([ dict({ 'path': 'https://secure-dcdn.cdn.nimg.jp/nicoaccount/usericon/defaults/blank.jpg', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -2380,7 +2380,7 @@ }), 'name': 'ゲスト', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -2403,7 +2403,7 @@ ]), 'sort_name': 'ケスト', 'translation_key': None, - 'uri': 'nicovideo://artist/68461151', + 'uri': 'nicovideo_test://artist/68461151', 'version': '', }), ]), @@ -2430,19 +2430,19 @@ 'images': list([ dict({ 'path': 'https://nicovideo.cdn.nimg.jp/thumbnails/45285955/45285955.27227006', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), dict({ 'path': 'https://nicovideo.cdn.nimg.jp/thumbnails/45285955/45285955.27227006.L', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), dict({ 'path': 'https://nicovideo.cdn.nimg.jp/thumbnails/45285955/45285955.27227006.M', - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'remotely_accessible': True, 'type': 'thumb', }), @@ -2468,7 +2468,7 @@ }), 'name': 'APIテスト用', 'position': None, - 'provider': 'nicovideo', + 'provider': 'nicovideo_test', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -2492,7 +2492,7 @@ 'sort_name': 'apiテスト用', 'track_number': 0, 'translation_key': None, - 'uri': 'nicovideo://track/sm45285955', + 'uri': 'nicovideo_test://track/sm45285955', 'version': '', }) # --- diff --git a/tests/providers/nicovideo/helpers.py b/tests/providers/nicovideo/helpers.py index a27b7e70..5f8620e4 100644 --- a/tests/providers/nicovideo/helpers.py +++ b/tests/providers/nicovideo/helpers.py @@ -19,7 +19,6 @@ def create_converter_manager() -> NicovideoConverterManager: """Create a NicovideoConverterManager for testing.""" # Create mock provider mock_provider = Mock() - mock_provider.lookup_key = "nicovideo" mock_provider.instance_id = "nicovideo_test" mock_provider.domain = "nicovideo" diff --git a/tests/providers/tidal/__snapshots__/test_parsers.ambr b/tests/providers/tidal/__snapshots__/test_parsers.ambr index be38d7f0..6a5b1079 100644 --- a/tests/providers/tidal/__snapshots__/test_parsers.ambr +++ b/tests/providers/tidal/__snapshots__/test_parsers.ambr @@ -20,7 +20,7 @@ 'images': list([ dict({ 'path': 'https://resources.tidal.com/images/1234/5678/90ab/cdef/750x750.jpg', - 'provider': 'tidal', + 'provider': 'tidal_instance', 'remotely_accessible': True, 'type': 'thumb', }), @@ -41,7 +41,7 @@ }), 'name': 'Test Artist', 'position': None, - 'provider': 'tidal', + 'provider': 'tidal_instance', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -64,7 +64,7 @@ ]), 'sort_name': 'test artist', 'translation_key': None, - 'uri': 'tidal://artist/12345', + 'uri': 'tidal_instance://artist/12345', 'version': '', }), ]), @@ -88,7 +88,7 @@ 'images': list([ dict({ 'path': 'https://resources.tidal.com/images/abcd/ef01/2345/6789/750x750.jpg', - 'provider': 'tidal', + 'provider': 'tidal_instance', 'remotely_accessible': True, 'type': 'thumb', }), @@ -109,7 +109,7 @@ }), 'name': 'Test Album', 'position': None, - 'provider': 'tidal', + 'provider': 'tidal_instance', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -132,7 +132,7 @@ ]), 'sort_name': 'test album', 'translation_key': None, - 'uri': 'tidal://album/67890', + 'uri': 'tidal_instance://album/67890', 'version': 'Deluxe Edition', 'year': 2023, }) @@ -155,7 +155,7 @@ 'images': list([ dict({ 'path': 'https://resources.tidal.com/images/1234/5678/90ab/cdef/750x750.jpg', - 'provider': 'tidal', + 'provider': 'tidal_instance', 'remotely_accessible': True, 'type': 'thumb', }), @@ -176,7 +176,7 @@ }), 'name': 'Test Artist', 'position': None, - 'provider': 'tidal', + 'provider': 'tidal_instance', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -199,7 +199,7 @@ ]), 'sort_name': 'test artist', 'translation_key': None, - 'uri': 'tidal://artist/12345', + 'uri': 'tidal_instance://artist/12345', 'version': '', }) # --- @@ -222,7 +222,7 @@ 'images': list([ dict({ 'path': 'http://example.com/mix-medium.jpg', - 'provider': 'tidal', + 'provider': 'tidal_instance', 'remotely_accessible': True, 'type': 'thumb', }), @@ -244,7 +244,7 @@ 'name': 'My Daily Discovery', 'owner': 'Created by Tidal', 'position': None, - 'provider': 'tidal', + 'provider': 'tidal_instance', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -267,7 +267,7 @@ ]), 'sort_name': 'my daily discovery', 'translation_key': None, - 'uri': 'tidal://playlist/mix_mix_123', + 'uri': 'tidal_instance://playlist/mix_mix_123', 'version': '', }) # --- @@ -290,7 +290,7 @@ 'images': list([ dict({ 'path': 'https://resources.tidal.com/images/playlist/square/image/id/750x750.jpg', - 'provider': 'tidal', + 'provider': 'tidal_instance', 'remotely_accessible': True, 'type': 'thumb', }), @@ -312,7 +312,7 @@ 'name': 'Test Playlist', 'owner': 'Tidal', 'position': None, - 'provider': 'tidal', + 'provider': 'tidal_instance', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -335,7 +335,7 @@ ]), 'sort_name': 'test playlist', 'translation_key': None, - 'uri': 'tidal://playlist/aabbcc-1122-3344-5566', + 'uri': 'tidal_instance://playlist/aabbcc-1122-3344-5566', 'version': '', }) # --- @@ -350,10 +350,10 @@ 'item_id': '67890', 'media_type': 'album', 'name': 'Test Album', - 'provider': 'tidal', + 'provider': 'tidal_instance', 'sort_name': 'test album', 'translation_key': None, - 'uri': 'tidal://album/67890', + 'uri': 'tidal_instance://album/67890', 'version': '', }), 'artists': list([ @@ -374,7 +374,7 @@ 'images': list([ dict({ 'path': 'https://resources.tidal.com/images/1234/5678/90ab/cdef/750x750.jpg', - 'provider': 'tidal', + 'provider': 'tidal_instance', 'remotely_accessible': True, 'type': 'thumb', }), @@ -395,7 +395,7 @@ }), 'name': 'Test Artist', 'position': None, - 'provider': 'tidal', + 'provider': 'tidal_instance', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -418,7 +418,7 @@ ]), 'sort_name': 'test artist', 'translation_key': None, - 'uri': 'tidal://artist/12345', + 'uri': 'tidal_instance://artist/12345', 'version': '', }), ]), @@ -445,7 +445,7 @@ 'images': list([ dict({ 'path': 'https://resources.tidal.com/images/abcd/ef01/2345/6789/750x750.jpg', - 'provider': 'tidal', + 'provider': 'tidal_instance', 'remotely_accessible': True, 'type': 'thumb', }), @@ -466,7 +466,7 @@ }), 'name': 'Test Track', 'position': None, - 'provider': 'tidal', + 'provider': 'tidal_instance', 'provider_mappings': list([ dict({ 'audio_format': dict({ @@ -490,7 +490,7 @@ 'sort_name': 'test track', 'track_number': 1, 'translation_key': None, - 'uri': 'tidal://track/112233', + 'uri': 'tidal_instance://track/112233', 'version': 'Remastered', }) # --- diff --git a/tests/providers/tidal/test_library.py b/tests/providers/tidal/test_library.py index 0d28dfc1..3efb038a 100644 --- a/tests/providers/tidal/test_library.py +++ b/tests/providers/tidal/test_library.py @@ -15,7 +15,6 @@ from music_assistant.providers.tidal.library import TidalLibraryManager def provider_mock() -> Mock: """Return a mock provider.""" provider = Mock() - provider.lookup_key = "tidal" provider.domain = "tidal" provider.instance_id = "tidal_instance" provider.auth.user_id = "12345" @@ -37,7 +36,7 @@ def provider_mock() -> Mock: return ItemMapping( media_type=media_type, item_id=key, - provider=provider.lookup_key, + provider=provider.instance_id, name=name, ) diff --git a/tests/providers/tidal/test_media.py b/tests/providers/tidal/test_media.py index 05549c31..f90ef1e7 100644 --- a/tests/providers/tidal/test_media.py +++ b/tests/providers/tidal/test_media.py @@ -14,7 +14,6 @@ from music_assistant.providers.tidal.media import TidalMediaManager def provider_mock() -> Mock: """Return a mock provider.""" provider = Mock() - provider.lookup_key = "tidal" provider.domain = "tidal" provider.instance_id = "tidal_instance" provider.auth.user_id = "12345" @@ -36,7 +35,7 @@ def provider_mock() -> Mock: return ItemMapping( media_type=media_type, item_id=key, - provider=provider.lookup_key, + provider=provider.instance_id, name=name, ) diff --git a/tests/providers/tidal/test_media_extended.py b/tests/providers/tidal/test_media_extended.py index 6ba419de..43b25e0b 100644 --- a/tests/providers/tidal/test_media_extended.py +++ b/tests/providers/tidal/test_media_extended.py @@ -14,7 +14,6 @@ from music_assistant.providers.tidal.media import TidalMediaManager def provider_mock() -> Mock: """Return a mock provider.""" provider = Mock() - provider.lookup_key = "tidal" provider.domain = "tidal" provider.instance_id = "tidal_instance" provider.auth.user_id = "12345" @@ -27,7 +26,7 @@ def provider_mock() -> Mock: return ItemMapping( media_type=media_type, item_id=key, - provider=provider.lookup_key, + provider=provider.instance_id, name=name, ) diff --git a/tests/providers/tidal/test_page_parser.py b/tests/providers/tidal/test_page_parser.py index 7ab0c9f8..80b45172 100644 --- a/tests/providers/tidal/test_page_parser.py +++ b/tests/providers/tidal/test_page_parser.py @@ -17,7 +17,6 @@ PAGE_FIXTURES = list(FIXTURES_DIR.glob("pages/*.json")) def provider_mock() -> Mock: """Return a mock provider.""" provider = Mock() - provider.lookup_key = "tidal" provider.domain = "tidal" provider.instance_id = "tidal_instance" provider.auth.user_id = "12345" diff --git a/tests/providers/tidal/test_page_parser_extended.py b/tests/providers/tidal/test_page_parser_extended.py index ce622e7f..135cb632 100644 --- a/tests/providers/tidal/test_page_parser_extended.py +++ b/tests/providers/tidal/test_page_parser_extended.py @@ -14,7 +14,6 @@ from music_assistant.providers.tidal.tidal_page_parser import TidalPageParser def provider_mock() -> Mock: """Return a mock provider.""" provider = Mock() - provider.lookup_key = "tidal" provider.domain = "tidal" provider.instance_id = "tidal_instance" provider.auth.user_id = "12345" @@ -27,7 +26,7 @@ def provider_mock() -> Mock: return ItemMapping( media_type=media_type, item_id=key, - provider=provider.lookup_key, + provider=provider.instance_id, name=name, ) diff --git a/tests/providers/tidal/test_parsers.py b/tests/providers/tidal/test_parsers.py index 31971360..11999af9 100644 --- a/tests/providers/tidal/test_parsers.py +++ b/tests/providers/tidal/test_parsers.py @@ -27,7 +27,6 @@ PLAYLIST_FIXTURES = list(FIXTURES_DIR.glob("playlists/*.json")) def provider_mock() -> Mock: """Return a mock provider.""" provider = Mock() - provider.lookup_key = "tidal" provider.domain = "tidal" provider.instance_id = "tidal_instance" provider.auth.user_id = "12345" @@ -38,7 +37,7 @@ def provider_mock() -> Mock: return ItemMapping( media_type=media_type, item_id=key, - provider=provider.lookup_key, + provider=provider.instance_id, name=name, ) diff --git a/tests/providers/tidal/test_provider.py b/tests/providers/tidal/test_provider.py index a3d6a9f5..9d148734 100644 --- a/tests/providers/tidal/test_provider.py +++ b/tests/providers/tidal/test_provider.py @@ -242,7 +242,7 @@ async def test_get_item_mapping(provider: TidalProvider) -> None: assert mapping.media_type == MediaType.ARTIST assert mapping.item_id == "123" - assert mapping.provider == provider.lookup_key + assert mapping.provider == provider.instance_id assert mapping.name == "Test Artist" diff --git a/tests/providers/tidal/test_streaming.py b/tests/providers/tidal/test_streaming.py index 72f2c11d..d10618ba 100644 --- a/tests/providers/tidal/test_streaming.py +++ b/tests/providers/tidal/test_streaming.py @@ -14,7 +14,7 @@ from music_assistant.providers.tidal.streaming import TidalStreamingManager def provider_mock() -> Mock: """Return a mock provider.""" provider = Mock() - provider.lookup_key = "tidal" + provider.domain = "tidal" provider.instance_id = "tidal_instance" provider.config.get_value.return_value = "HIGH" provider.api = AsyncMock() @@ -73,7 +73,7 @@ async def test_get_stream_details_lossless( stream_details = await streaming_manager.get_stream_details("123") assert stream_details.item_id == "123" - assert stream_details.provider == "tidal" + assert stream_details.provider == "tidal_instance" assert stream_details.audio_format.content_type == ContentType.FLAC assert stream_details.audio_format.sample_rate == 44100 assert stream_details.audio_format.bit_depth == 16