From 91ff68730b8ffbf53392ac97ff875871ee31a952 Mon Sep 17 00:00:00 2001 From: Aaron Loo Date: Sun, 12 Nov 2023 12:10:24 -0800 Subject: [PATCH] [soundcloud] improved track iteration (#922) --- .../server/providers/soundcloud/__init__.py | 12 +++--- .../soundcloudpy/asyncsoundcloudpy.py | 40 ++++++++++++++++--- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/music_assistant/server/providers/soundcloud/__init__.py b/music_assistant/server/providers/soundcloud/__init__.py index 0a8021bb..5941cd5b 100644 --- a/music_assistant/server/providers/soundcloud/__init__.py +++ b/music_assistant/server/providers/soundcloud/__init__.py @@ -192,12 +192,7 @@ class SoundcloudMusicProvider(MusicProvider): async def get_library_tracks(self) -> AsyncGenerator[Track, None]: """Retrieve library tracks from Soundcloud.""" time_start = time.time() - tracks = await self._soundcloud.get_tracks_liked() - self.logger.debug( - "Processing Soundcloud library tracks took %s seconds", - round(time.time() - time_start, 2), - ) - for item in tracks["collection"]: + async for item in self._soundcloud.get_tracks_liked(): track = await self._soundcloud.get_track_details(item) try: yield await self._parse_track(track[0]) @@ -207,6 +202,11 @@ class SoundcloudMusicProvider(MusicProvider): self.logger.debug("Parse track failed: %s", track, exc_info=error) continue + self.logger.debug( + "Processing Soundcloud library tracks took %s seconds", + round(time.time() - time_start, 2), + ) + async def get_artist(self, prov_artist_id) -> Artist: """Get full artist details by id.""" artist_obj = await self._soundcloud.get_user_details(user_id=prov_artist_id) diff --git a/music_assistant/server/providers/soundcloud/soundcloudpy/asyncsoundcloudpy.py b/music_assistant/server/providers/soundcloud/soundcloudpy/asyncsoundcloudpy.py index f1f25c66..9ba86ac8 100644 --- a/music_assistant/server/providers/soundcloud/soundcloudpy/asyncsoundcloudpy.py +++ b/music_assistant/server/providers/soundcloud/soundcloudpy/asyncsoundcloudpy.py @@ -6,6 +6,7 @@ Original package https://github.com/naim-prog/soundcloud-py """ from __future__ import annotations +from collections.abc import AsyncGenerator from typing import TYPE_CHECKING BASE_URL = "https://api-v2.soundcloud.com" @@ -118,13 +119,42 @@ class SoundcloudAsyncAPI: headers=self.headers, ) - async def get_tracks_liked(self, limit=50): - """:param limit: number of tracks to get""" - return await self.get( + async def get_tracks_liked(self, limit: int = 0) -> AsyncGenerator[int, None]: + """Obtain the authenticated user's liked tracks. + + :param limit: number of tracks to get. if 0, will fetch all tracks. + :returns: list of track ids liked by the current user + """ + query_limit = limit + if query_limit == 0: + # NOTE(2023-11-11): At the time of writing, soundcloud does not look like it caps + # the limit. However, we still implement pagination for future proofing. + query_limit = 100 + + url = ( f"{BASE_URL}/me/track_likes/ids?client_id={self.client_id}&" - f"limit={limit}&app_version={self.app_version}", - headers=self.headers, + f"limit={query_limit}&app_version={self.app_version}" ) + num_items = 0 + while True: + response = await self.get(url, headers=self.headers) + + # Sanity check. + if "collection" not in response: + raise RuntimeError("Unexpected Soundcloud API response") + + for item in response["collection"]: + num_items += 1 + if limit > 0 and num_items >= limit: + return + + yield item + + # Handle case when results requested exceeds number of actual results. + if len(response["collection"]) < query_limit: + return + + url = response["next_href"] async def get_track_by_genre_recent(self, genre, limit=10): """Get track by genre recent. -- 2.34.1