From: Marcel van der Veldt Date: Tue, 3 Sep 2024 19:29:48 +0000 (+0200) Subject: Some small bugfixes and tweaks (#1642) X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=b0a978cd094259a8e757474e866b030f3c56961c;p=music-assistant-server.git Some small bugfixes and tweaks (#1642) * Require extra duration match for 'soft' external id matches * Fix race condition in spotify stream when token expired * Add a bit of extra logging * fix playlist matching * limit radiobrowser results * update airplay mute state on volume changes --- diff --git a/music_assistant/server/helpers/audio.py b/music_assistant/server/helpers/audio.py index 13b4417c..da4c3421 100644 --- a/music_assistant/server/helpers/audio.py +++ b/music_assistant/server/helpers/audio.py @@ -254,7 +254,7 @@ async def crossfade_pcm_parts( _returncode, crossfaded_audio, _stderr = await communicate(args, fade_in_part) if crossfaded_audio: LOGGER.log( - 5, + VERBOSE_LOG_LEVEL, "crossfaded 2 pcm chunks. fade_in_part: %s - " "fade_out_part: %s - fade_length: %s seconds", len(fade_in_part), @@ -310,12 +310,12 @@ async def strip_silence( # return stripped audio bytes_stripped = len(audio_data) - len(stripped_data) - if LOGGER.isEnabledFor(5): + if LOGGER.isEnabledFor(VERBOSE_LOG_LEVEL): pcm_sample_size = int(sample_rate * (bit_depth / 8) * 2) seconds_stripped = round(bytes_stripped / pcm_sample_size, 2) location = "end" if reverse else "begin" LOGGER.log( - 5, + VERBOSE_LOG_LEVEL, "stripped %s seconds of silence from %s of pcm audio. bytes stripped: %s", seconds_stripped, location, @@ -336,6 +336,8 @@ async def get_stream_details( Do not try to request streamdetails in advance as this is expiring data. param media_item: The QueueItem for which to request the streamdetails for. """ + 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): LOGGER.warning("seeking is not possible on duration-less streams!") seek_position = 0 @@ -406,7 +408,8 @@ async def get_stream_details( streamdetails.target_loudness = None else: streamdetails.target_loudness = player_settings.get_value(CONF_VOLUME_NORMALIZATION_TARGET) - + process_time = int((time.time() - time_start) * 1000) + LOGGER.debug("retrieved streamdetails for %s in %s milliseconds", queue_item.uri, process_time) return streamdetails diff --git a/music_assistant/server/helpers/compare.py b/music_assistant/server/helpers/compare.py index 93b12027..808b319f 100644 --- a/music_assistant/server/helpers/compare.py +++ b/music_assistant/server/helpers/compare.py @@ -128,22 +128,32 @@ def compare_track( # return early on exact item_id match if compare_item_ids(base_item, compare_item): return True - # return early on (un)matched external id + # return early on (un)matched primary/unique external id for ext_id in ( ExternalID.MB_RECORDING, - ExternalID.DISCOGS, + ExternalID.MB_TRACK, ExternalID.ACOUSTID, + ): + external_id_match = compare_external_ids( + base_item.external_ids, compare_item.external_ids, ext_id + ) + if external_id_match is not None: + return external_id_match + # check secondary external id matches + for ext_id in ( + ExternalID.DISCOGS, ExternalID.TADB, - # make sure to check musicbrainz before isrc - # https://github.com/music-assistant/hass-music-assistant/issues/2316 ExternalID.ISRC, ExternalID.ASIN, ): external_id_match = compare_external_ids( base_item.external_ids, compare_item.external_ids, ext_id ) - if external_id_match is not None: - return external_id_match + if external_id_match is True: + # we got a 'soft-match' on a secondary external id (like ISRC) + # but we do a double check on duration + if abs(base_item.duration - compare_item.duration) <= 2: + return True # compare name if not compare_strings(base_item.name, compare_item.name, strict=True): @@ -227,18 +237,15 @@ def compare_playlist( """Compare two Playlist items and return True if they match.""" if base_item is None or compare_item is None: return False - # return early on exact item_id match - if compare_item_ids(base_item, compare_item): - return True - # compare owner (if not ItemMapping) + # require (exact) name match + if not compare_strings(base_item.name, compare_item.name, strict=strict): + return False + # require exact owner match (if not ItemMapping) if isinstance(base_item, Playlist) and isinstance(compare_item, Playlist): if not compare_strings(base_item.owner, compare_item.owner): return False - # compare version - if not compare_version(base_item.version, compare_item.version): - return False - # finally comparing on (exact) name match - return compare_strings(base_item.name, compare_item.name, strict=strict) + # a playlist is always unique - so do a strict compare on item id(s) + return compare_item_ids(base_item, compare_item) def compare_radio( diff --git a/music_assistant/server/providers/airplay/__init__.py b/music_assistant/server/providers/airplay/__init__.py index cc96b275..08e92935 100644 --- a/music_assistant/server/providers/airplay/__init__.py +++ b/music_assistant/server/providers/airplay/__init__.py @@ -749,6 +749,7 @@ class AirplayProvider(PlayerProvider): await airplay_player.raop_stream.send_cli_command(f"VOLUME={volume_level}\n") mass_player = self.mass.players.get(player_id) mass_player.volume_level = volume_level + mass_player.volume_muted = volume_level == 0 self.mass.players.update(player_id) # store last state in cache await self.mass.cache.set(player_id, volume_level, base_key=CACHE_KEY_PREV_VOLUME) diff --git a/music_assistant/server/providers/radiobrowser/__init__.py b/music_assistant/server/providers/radiobrowser/__init__.py index dde24e19..5675f4c6 100644 --- a/music_assistant/server/providers/radiobrowser/__init__.py +++ b/music_assistant/server/providers/radiobrowser/__init__.py @@ -245,7 +245,7 @@ class RadioBrowserProvider(MusicProvider): async def get_country_folders(self, base_path: str) -> list[BrowseFolder]: """Get a list of country names as BrowseFolder.""" items: list[BrowseFolder] = [] - for country in await self.radios.countries(order=Order.NAME, hide_broken=True): + for country in await self.radios.countries(order=Order.NAME, hide_broken=True, limit=1000): folder = BrowseFolder( item_id=country.code.lower(), provider=self.domain, @@ -270,7 +270,7 @@ class RadioBrowserProvider(MusicProvider): """Get radio stations by popularity.""" stations = await self.radios.stations( hide_broken=True, - limit=5000, + limit=1000, order=Order.CLICK_COUNT, reverse=True, ) @@ -287,7 +287,8 @@ class RadioBrowserProvider(MusicProvider): filter_by=FilterBy.TAG_EXACT, filter_term=tag, hide_broken=True, - order=Order.NAME, + limit=1000, + order=Order.CLICK_COUNT, reverse=False, ) for station in stations: @@ -302,7 +303,8 @@ class RadioBrowserProvider(MusicProvider): filter_by=FilterBy.COUNTRY_CODE_EXACT, filter_term=country_code, hide_broken=True, - order=Order.NAME, + limit=1000, + order=Order.CLICK_COUNT, reverse=False, ) for station in stations: diff --git a/music_assistant/server/providers/spotify/__init__.py b/music_assistant/server/providers/spotify/__init__.py index 13388f09..8b0914a1 100644 --- a/music_assistant/server/providers/spotify/__init__.py +++ b/music_assistant/server/providers/spotify/__init__.py @@ -563,28 +563,29 @@ class SpotifyProvider(MusicProvider): auth_info = await self.login() librespot = await self.get_librespot_binary() spotify_uri = f"spotify://track:{streamdetails.item_id}" - args = [ - librespot, - "-c", - CACHE_DIR, - "-M", - "256M", - "--passthrough", - "-b", - "320", - "--backend", - "pipe", - "--single-track", - spotify_uri, - "--token", - auth_info["access_token"], - ] - if seek_position: - args += ["--start-position", str(int(seek_position))] - chunk_size = get_chunksize(streamdetails.audio_format) - stderr = None if self.logger.isEnabledFor(VERBOSE_LOG_LEVEL) else False - self.logger.log(VERBOSE_LOG_LEVEL, f"Start streaming {spotify_uri} using librespot") for retry in (True, False): + args = [ + librespot, + "-c", + CACHE_DIR, + "-M", + "256M", + "--passthrough", + "-b", + "320", + "--backend", + "pipe", + "--single-track", + spotify_uri, + "--token", + auth_info["access_token"], + ] + if seek_position: + args += ["--start-position", str(int(seek_position))] + chunk_size = get_chunksize(streamdetails.audio_format) + stderr = None if self.logger.isEnabledFor(VERBOSE_LOG_LEVEL) else False + self.logger.log(VERBOSE_LOG_LEVEL, f"Start streaming {spotify_uri} using librespot") + async with AsyncProcess( args, stdout=True, @@ -600,7 +601,7 @@ class SpotifyProvider(MusicProvider): raise AudioError( f"Failed to stream {spotify_uri} - error: {librespot_proc.returncode}" ) - # do one retry attempt + # do one retry attempt - accounting for the fact that the token might have expired auth_info = await self.login(force_refresh=True) def _parse_artist(self, artist_obj):