EbbLabs/python-tidal

Funky Pagination Behavior Fetching User Favorites

Opened this issue · 1 comments

I ran into some rather bizarre pagination behavior when using the user.favorites.tracks() function.

When passing a page_size parameter I never receive exactly that many tracks back regardless of which offset or page_size I supply.

I'm spitballing that the reason is due to the Tidal API's internal cursor skipping over tracks that may have been deleted or geo-locked since the user added them to their favorites. So even when a page size of say 500 is requested I typically see track counts like 418, 433, 494, etc actually returning.

This is the function I came up with to get around this funky behavior which I hope is helpful for others.

Perhaps this would make sense to implement as a function in the library itself?

"""
Fetch all favorite tracks for a Tidal user, regardless of how many there are.
Deduplicates tracks by ID after fetching all pages.
"""
def fetch_all_user_favorite_tracks(user, page_size=1000):
    all_tracks = []
    offset = 0
    while True:
        new_tracks = user.favorites.tracks(
            limit=page_size,
            offset=offset,
            order=ItemOrder.Date,
            order_direction=OrderDirection.Ascending
        )
        if not new_tracks:
            break
        new_track_count = len(new_tracks)
        print(f"Offset: {offset}, Fetched: {new_track_count}")
        all_tracks.extend(new_tracks)
        offset += new_track_count
        sleep(0.1)

    print(f"Total tracks fetched (with possible duplicates): {len(all_tracks)}")

    seen_track_ids = set()
    deduped_tracks = []
    for track in all_tracks:
        track_id = getattr(track, "id", None)
        if track_id is not None and track_id not in seen_track_ids:
            seen_track_ids.add(track_id)
            deduped_tracks.append(track)

    print(f"Total deduplicated tracks: {len(deduped_tracks)}")

    return deduped_tracks

I am planning to add the following code from mopidy-tidal that essentially achieves the same thing, and fetches the objects in parallel. It works with all types (tracks, albums, artists)

I think it also handles duplicates properly, but it might need to be tested further.

https://github.com/tehkillerbee/mopidy-tidal/blob/main/mopidy_tidal/workers.py

You can see an example usage here:
https://github.com/tehkillerbee/mopidy-tidal/blob/7ca32f0ee3cf7d4eea03930027d491eade528386/mopidy_tidal/library.py#L263