From 3a09041549a36cecdd37f97c9b30db85db70fef0 Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Mon, 9 Dec 2024 13:30:31 +0100 Subject: [PATCH 1/2] Get all tracks for apple music playlist The current implementation using the Apple Music API "?include=tracks" only returns the first 100 tracks. To get the rest if there are more, we need to fetch them separately using another endpoint ("/tracks") --- troi/playlist.py | 2 +- troi/tools/apple_lookup.py | 6 +----- troi/tools/utils.py | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/troi/playlist.py b/troi/playlist.py index 33923e0c..b1dc7cc4 100755 --- a/troi/playlist.py +++ b/troi/playlist.py @@ -591,7 +591,7 @@ class RecordingsFromMusicServiceElement(Element): def __init__(self, token=None, playlist_id=None, music_service=None, apple_user_token=None): """ Args: - playlist_id: id of the Spotify playlist to be used for creating the playlist element + playlist_id: id of the playlist to be used for creating the playlist element token: the Spotify token to fetch the playlist tracks music_service: the name of the music service to be used for fetching the playlist data apple_music_token (optional): the user token for Apple Music API diff --git a/troi/tools/apple_lookup.py b/troi/tools/apple_lookup.py index d6ac382e..1f66817a 100644 --- a/troi/tools/apple_lookup.py +++ b/troi/tools/apple_lookup.py @@ -33,11 +33,7 @@ def get_tracks_from_apple_playlist(developer_token, user_token, playlist_id): """ Get tracks from the Apple Music playlist. """ apple = AppleMusicAPI(developer_token, user_token) - response = apple.get_playlist_tracks(playlist_id) - - tracks = response["data"][0]["relationships"]["tracks"]["data"] - name = response["data"][0]["attributes"]["name"] - description = response["data"][0]["attributes"].get("description", {}).get("standard", "") + tracks, name, description = apple.get_playlist_tracks(playlist_id) mapped_tracks = [ { diff --git a/troi/tools/utils.py b/troi/tools/utils.py index 48a17a8e..42f44869 100644 --- a/troi/tools/utils.py +++ b/troi/tools/utils.py @@ -59,8 +59,36 @@ def playlist_add_tracks(self, playlist_id, track_ids): def get_playlist_tracks(self, playlist_id): url = f"{APPLE_MUSIC_URL}/me/library/playlists/{playlist_id}?include=tracks" - response = self.session.get(url, headers=self.headers) - return response.json() + response = self.session.get(url, headers=self.headers).json() + tracks = response["data"][0]["relationships"]["tracks"]["data"] + total_tracks_count = response["data"][0]["relationships"]["tracks"]["meta"]["total"] + playlist_name = response["data"][0]["attributes"]["name"] + playlist_description = response["data"][0]["attributes"].get( + "description", {}).get("standard", "") + + # apple music returns only the first 100 tracks with "/playlists/{playlist_id}?include=tracks" + # and we need to fetch the rest of the tracks using the "playlists/{playlist_id}/tracks" endpoint + if len(tracks) < total_tracks_count: + offset = len(tracks) + # endpoint returns 100 tracks per call max -> run iteratively with an offset until there are no more tracks + while True: + url = f"{APPLE_MUSIC_URL}/me/library/playlists/{playlist_id}/tracks?limit=100&offset={offset}" + response = self.session.get(url, headers=self.headers).json() + try: + tracks_data = response["data"] + tracks.extend(tracks_data) if tracks_data else None + + if len(tracks_data) == 0 or len(tracks) == total_tracks_count: + break + # set new offset for the next loop + offset = offset + len(tracks_data) + except KeyError as err: + # No data returned from API call, could be an error, do nothing + logger.error( + "Failed to fetch Apple playlist tracks: %s, ignoring error..." % err) + break + + return tracks, playlist_name, playlist_description class SoundCloudException(Exception): From a82eaa37c03f19f5236a38c07b73de803366c650 Mon Sep 17 00:00:00 2001 From: Monkey Do Date: Tue, 10 Dec 2024 11:49:37 +0100 Subject: [PATCH 2/2] Catch error code+message from AppleMusic API Parse the response body for errors, log any error found --- troi/tools/utils.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/troi/tools/utils.py b/troi/tools/utils.py index 42f44869..1e17a46c 100644 --- a/troi/tools/utils.py +++ b/troi/tools/utils.py @@ -75,6 +75,15 @@ def get_playlist_tracks(self, playlist_id): url = f"{APPLE_MUSIC_URL}/me/library/playlists/{playlist_id}/tracks?limit=100&offset={offset}" response = self.session.get(url, headers=self.headers).json() try: + if "errors" in response: + # https: // developer.apple.com/documentation/applemusicapi/errorsresponse + error_objects = response["errors"] + for error_object in error_objects: + error_message = error_object["detail"] if "detail" in error_object else error_object["title"] + logger.error( + f"Error code {error_object['status']}: {error_message}") + break + tracks_data = response["data"] tracks.extend(tracks_data) if tracks_data else None