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,32 +706,61 @@ class TidalClient:
playlist_data = response.json() playlist_data = response.json()
# Get playlist tracks # Get playlist tracks with pagination to handle large playlists
tracks_response = self.session.get(
f"{self.base_url}/playlists/{playlist_id}/items",
params={'countryCode': 'US', 'limit': 100},
timeout=10
)
tracks = [] tracks = []
if tracks_response.status_code == 200: offset = 0
tracks_data = tracks_response.json() limit = 100
if 'items' in tracks_data: total_fetched = 0
for item in tracks_data['items']:
# Handle different item structures
track_data = item
if 'item' in item and item.get('type') == 'track':
track_data = item['item']
elif 'resource' in item:
track_data = item['resource']
track = self._parse_track_data(track_data) while True:
if track: logger.info(f"Fetching tracks for playlist {playlist_id}: offset={offset}, limit={limit}")
tracks.append(track)
else: tracks_response = self.session.get(
logger.warning(f"No tracks found in playlist {playlist_id}") f"{self.base_url}/playlists/{playlist_id}/items",
else: params={'countryCode': 'US', 'limit': limit, 'offset': offset},
logger.error(f"Failed to get Tidal playlist tracks: {tracks_response.status_code} - {tracks_response.text}") timeout=10
)
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()
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
track_data = item
if 'item' in item and item.get('type') == 'track':
track_data = item['item']
elif 'resource' in item:
track_data = item['resource']
track = self._parse_track_data(track_data)
if track:
tracks.append(track)
batch_count += 1
total_fetched += batch_count
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),