From: Marcel van der Veldt Date: Sat, 12 Sep 2020 10:43:30 +0000 (+0200) Subject: add album/track versions support on api X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=6c1fac5b85b13e96e64502692107d648418ce098;p=music-assistant-server.git add album/track versions support on api --- diff --git a/music_assistant/models/player.py b/music_assistant/models/player.py index acd945cf..8a90ef91 100755 --- a/music_assistant/models/player.py +++ b/music_assistant/models/player.py @@ -54,13 +54,14 @@ class Player: should_poll: bool = False features: List[PlayerFeature] = field(default_factory=list) config_entries: List[ConfigEntry] = field(default_factory=list) - updated_at: datetime = datetime.utcnow() # managed by playermanager! - active_queue: str = "" # managed by playermanager! - group_parents: List[str] = field(default_factory=list) # managed by playermanager! - cur_queue_item_id: str = None # managed by playermanager! + # below attributes are handled by the player manager. No need to set/override them. + updated_at: datetime = field(default=datetime.utcnow(), init=False) + active_queue: str = field(default="", init=False) + group_parents: List[str] = field(init=False, default_factory=list) + cur_queue_item_id: str = field(default="", init=False) def __setattr__(self, name, value): - """Event when control is updated. Do not override.""" + """Watch for attribute updates. Do not override.""" if name == "updated_at": # updated at is set by the on_update callback # make sure we do not hit an endless loop diff --git a/music_assistant/music_manager.py b/music_assistant/music_manager.py index bfefcfa2..0ee6368b 100755 --- a/music_assistant/music_manager.py +++ b/music_assistant/music_manager.py @@ -86,7 +86,7 @@ class MusicManager: ################ GET MediaItem(s) by id and provider ################# async def async_get_item( - self, item_id: str, provider_id: str, media_type: MediaType, lazy: bool = False + self, item_id: str, provider_id: str, media_type: MediaType, lazy: bool = True ): """Get single music item by id and media type.""" if media_type == MediaType.Artist: @@ -166,7 +166,7 @@ class MusicManager: self, item_id: str, provider_id: str, - lazy: bool = False, + lazy: bool = True, track_details: Track = None, refresh: bool = False, ) -> Track: @@ -240,32 +240,73 @@ class MusicManager: ) -> List[Track]: """Return album tracks for the given provider album id. Generator.""" assert item_id and provider_id - if provider_id == "database": + album = await self.async_get_album(item_id, provider_id) + if album.provider == "database": # album tracks are not stored in db, we always fetch them (cached) from the provider. - db_item = await self.mass.database.async_get_album(item_id) - provider_id = db_item.provider_ids[0].provider - item_id = db_item.provider_ids[0].item_id + provider_id = album.provider_ids[0].provider + item_id = album.provider_ids[0].item_id provider = self.mass.get_provider(provider_id) cache_key = f"{provider_id}.album_tracks.{item_id}" - async for item in async_cached_generator( - self.cache, cache_key, provider.async_get_album_tracks(item_id) - ): - if not item: - continue - assert item.item_id and item.provider - db_id = await self.mass.database.async_get_database_id( - item.provider, item.item_id, MediaType.Track - ) - if db_id: - # return database track instead if we have a match - db_item = await self.mass.database.async_get_track( - db_id, fulldata=False + async with self.mass.database.db_conn() as db_conn: + async for item in async_cached_generator( + self.cache, cache_key, provider.async_get_album_tracks(item_id) + ): + if not item: + continue + db_id = await self.mass.database.async_get_database_id( + item.provider, item.item_id, MediaType.Track, db_conn ) - db_item.disc_number = item.disc_number - db_item.track_number = item.track_number - yield db_item - else: - yield item + if db_id: + # return database track instead if we have a match + track = await self.mass.database.async_get_track( + db_id, fulldata=False, db_conn=db_conn + ) + track.disc_number = item.disc_number + track.track_number = item.track_number + else: + track = item + if not track.album: + track.album = album + yield track + + async def async_get_album_versions( + self, item_id: str, provider_id: str + ) -> List[Album]: + """Return all versions of an album we can find on all providers. Generator.""" + album = await self.async_get_album(item_id, provider_id) + provider_ids = [ + item.id for item in self.mass.get_providers(ProviderType.MUSIC_PROVIDER) + ] + search_query = f"{album.artist.name} - {album.name}" + for provider_id in provider_ids: + provider_result = await self.async_search_provider( + search_query, provider_id, [MediaType.Album], 25 + ) + for item in provider_result.albums: + if compare_strings(item.artist.name, album.artist.name): + yield item + + async def async_get_track_versions( + self, item_id: str, provider_id: str + ) -> List[Track]: + """Return all versions of a track we can find on all providers. Generator.""" + track = await self.async_get_track(item_id, provider_id) + provider_ids = [ + item.id for item in self.mass.get_providers(ProviderType.MUSIC_PROVIDER) + ] + search_query = f"{track.artists[0].name} - {track.name}" + for provider_id in provider_ids: + provider_result = await self.async_search_provider( + search_query, provider_id, [MediaType.Track], 25 + ) + for item in provider_result.tracks: + if not compare_strings(item.name, track.name): + continue + for artist in item.artists: + # artist must match + if compare_strings(artist.name, track.artists[0].name): + yield item + break async def async_get_playlist_tracks( self, item_id: str, provider_id: str @@ -820,7 +861,7 @@ class MusicManager: :param limit: number of items to return in the search (per type). """ result = SearchResult([], [], [], [], []) - # include results from all music providers, filter out duplicates + # include results from all music providers provider_ids = ["database"] + [ item.id for item in self.mass.get_providers(ProviderType.MUSIC_PROVIDER) ] diff --git a/music_assistant/player_manager.py b/music_assistant/player_manager.py index 94b92ed7..1796cd74 100755 --- a/music_assistant/player_manager.py +++ b/music_assistant/player_manager.py @@ -58,7 +58,7 @@ class PlayerManager: async def async_close(self): """Handle stop/shutdown.""" - for player_queue in self._player_queues.values(): + for player_queue in list(self._player_queues.values()): await player_queue.async_close() @run_periodic(1) diff --git a/music_assistant/web.py b/music_assistant/web.py index d7706fec..589df637 100755 --- a/music_assistant/web.py +++ b/music_assistant/web.py @@ -323,7 +323,7 @@ class Web: """Get full artist details.""" item_id = request.match_info.get("item_id") provider = request.rel_url.query.get("provider") - lazy = request.rel_url.query.get("lazy", "false") != "false" + lazy = request.rel_url.query.get("lazy", "true") != "false" if item_id is None or provider is None: return web.Response(text="invalid item or provider", status=501) result = await self.mass.music_manager.async_get_artist( @@ -337,7 +337,7 @@ class Web: """Get full album details.""" item_id = request.match_info.get("item_id") provider = request.rel_url.query.get("provider") - lazy = request.rel_url.query.get("lazy", "false") != "false" + lazy = request.rel_url.query.get("lazy", "true") != "false" if item_id is None or provider is None: return web.Response(text="invalid item or provider", status=501) result = await self.mass.music_manager.async_get_album( @@ -351,11 +351,11 @@ class Web: """Get full track details.""" item_id = request.match_info.get("item_id") provider = request.rel_url.query.get("provider") - lazy = request.rel_url.query.get("lazy", "false") != "false" + lazy = request.rel_url.query.get("lazy", "true") != "false" if item_id is None or provider is None: return web.Response(text="invalid item or provider", status=501) result = await self.mass.music_manager.async_get_track( - item_id, provider, lazy=lazy, refresh=True + item_id, provider, lazy=lazy ) return web.json_response(result, dumps=json_serializer) @@ -467,6 +467,28 @@ class Web: iterator = self.mass.music_manager.async_get_album_tracks(item_id, provider) return await self.__async_stream_json(request, iterator) + @login_required + @routes.get("/api/albums/{item_id}/versions") + async def async_album_versions(self, request): + """Get all versions of an album.""" + item_id = request.match_info.get("item_id") + provider = request.rel_url.query.get("provider") + if item_id is None or provider is None: + return web.Response(text="invalid item_id or provider", status=501) + iterator = self.mass.music_manager.async_get_album_versions(item_id, provider) + return await self.__async_stream_json(request, iterator) + + @login_required + @routes.get("/api/tracks/{item_id}/versions") + async def async_track_versions(self, request): + """Get all versions of an track.""" + item_id = request.match_info.get("item_id") + provider = request.rel_url.query.get("provider") + if item_id is None or provider is None: + return web.Response(text="invalid item_id or provider", status=501) + iterator = self.mass.music_manager.async_get_track_versions(item_id, provider) + return await self.__async_stream_json(request, iterator) + @login_required @routes.get("/api/search") async def async_search(self, request):