grab all tracks from tidal playlists correctly

This commit is contained in:
Broque Thomas 2025-11-09 20:42:29 -08:00
parent 208b233497
commit 77c05b0315

View file

@ -545,10 +545,11 @@ class TidalClient:
logger.info(f"Using V2 endpoint to fetch playlists for user ID: {user_id}") logger.info(f"Using V2 endpoint to fetch playlists for user ID: {user_id}")
# Step 2: Construct the correct V2 endpoint and parameters. # Step 2: Construct the correct V2 endpoint and parameters.
# NOTE: We don't include 'items' here because the V2 API only includes ~20 tracks
# We'll fetch full track lists separately for each playlist
endpoint = f"{self.base_url}/playlists" endpoint = f"{self.base_url}/playlists"
params = { params = {
'countryCode': 'US', 'countryCode': 'US',
'include': 'items,items.artists', # Include both track data AND artist data
'filter[r.owners.id]': user_id 'filter[r.owners.id]': user_id
} }
@ -564,16 +565,12 @@ class TidalClient:
data = response.json() data = response.json()
playlists = [] playlists = []
# Step 3: Create lookup maps from the 'included' data for efficient access. # Step 3: Process the playlists from the main 'data' array.
included_data = data.get('included', [])
track_details_map = {item['id']: item for item in included_data if item['type'] == 'tracks'}
artist_details_map = {item['id']: item for item in included_data if item['type'] == 'artists'}
# Step 4: Process the playlists from the main 'data' array.
for playlist_data in data.get('data', []): for playlist_data in data.get('data', []):
attributes = playlist_data.get('attributes', {}) attributes = playlist_data.get('attributes', {})
playlist_id = playlist_data.get('id') playlist_id = playlist_data.get('id')
# Create playlist with basic metadata first
new_playlist = Playlist( new_playlist = Playlist(
id=str(playlist_id), id=str(playlist_id),
name=attributes.get('name', 'Unknown Playlist'), name=attributes.get('name', 'Unknown Playlist'),
@ -582,16 +579,16 @@ class TidalClient:
public=attributes.get('accessType') == 'PUBLIC' public=attributes.get('accessType') == 'PUBLIC'
) )
# Step 5: Match track stubs to the full details from the lookup map. # Step 4: Fetch ALL tracks for this playlist using the paginated get_playlist() method
track_stubs = playlist_data.get('relationships', {}).get('items', {}).get('data', []) # This ensures we get all tracks, not just the first ~20
for stub in track_stubs: logger.info(f"Fetching full track list for playlist: {new_playlist.name} ({playlist_id})")
track_id = stub.get('id') full_playlist = self.get_playlist(playlist_id)
full_track_data = track_details_map.get(track_id)
if full_track_data: if full_playlist and full_playlist.tracks:
track = self._parse_json_api_track(full_track_data, artist_details_map) new_playlist.tracks = full_playlist.tracks
if track: logger.info(f"Added {len(full_playlist.tracks)} tracks to playlist {new_playlist.name}")
new_playlist.tracks.append(track) else:
logger.warning(f"Could not fetch tracks for playlist {playlist_id}, it will have 0 tracks")
playlists.append(new_playlist) playlists.append(new_playlist)
@ -709,18 +706,39 @@ class TidalClient:
playlist_data = response.json() playlist_data = response.json()
# Get playlist tracks # Get playlist tracks with pagination to handle large playlists
tracks = []
offset = 0
limit = 100
total_fetched = 0
while True:
logger.info(f"Fetching tracks for playlist {playlist_id}: offset={offset}, limit={limit}")
tracks_response = self.session.get( tracks_response = self.session.get(
f"{self.base_url}/playlists/{playlist_id}/items", f"{self.base_url}/playlists/{playlist_id}/items",
params={'countryCode': 'US', 'limit': 100}, params={'countryCode': 'US', 'limit': limit, 'offset': offset},
timeout=10 timeout=10
) )
tracks = [] if tracks_response.status_code != 200:
if tracks_response.status_code == 200: logger.error(f"Failed to get Tidal playlist tracks at offset {offset}: {tracks_response.status_code} - {tracks_response.text}")
break
tracks_data = tracks_response.json() tracks_data = tracks_response.json()
if 'items' in tracks_data:
for item in tracks_data['items']: if 'items' not in tracks_data:
logger.warning(f"No items found in playlist {playlist_id} response at offset {offset}")
break
items = tracks_data['items']
if len(items) == 0:
logger.info(f"No more tracks found at offset {offset}, stopping pagination")
break
# Process this batch of tracks
batch_count = 0
for item in items:
# Handle different item structures # Handle different item structures
track_data = item track_data = item
if 'item' in item and item.get('type') == 'track': if 'item' in item and item.get('type') == 'track':
@ -731,10 +749,18 @@ class TidalClient:
track = self._parse_track_data(track_data) track = self._parse_track_data(track_data)
if track: if track:
tracks.append(track) tracks.append(track)
else: batch_count += 1
logger.warning(f"No tracks found in playlist {playlist_id}")
else: total_fetched += batch_count
logger.error(f"Failed to get Tidal playlist tracks: {tracks_response.status_code} - {tracks_response.text}") logger.info(f"Fetched {batch_count} tracks in this batch, {total_fetched} total so far")
# If we got fewer items than the limit, we've reached the end
if len(items) < limit:
logger.info(f"Received {len(items)} items (less than limit {limit}), pagination complete")
break
# Move to next page
offset += limit
playlist = Playlist( playlist = Playlist(
id=playlist_data.get('id', playlist_id), id=playlist_data.get('id', playlist_id),