tidalpy: a Python library for Tidal#

tidalpy (distribution binary-tidal-py) is a clean-room reimplementation of the proven Tidal-Media-Downloader core, as a Python library: a catalog and playback API, the OAuth device flow, stream resolution, resumable downloads, FLAC-in-MP4 remuxing, and metadata tagging. All of the CLI baggage is gone.

It is a library, not a tool. The client can be anything: a real-time search UI, an indexer, a player, a Django/Celery sync job, a plain download script. Each one gets the same small, structured, in-process API. The hard parts of Tidal are inside the library: two parallel catalog APIs (v1 and v2), the quality-downgrade chain, rate limiting with retry, and atomic file writes.

What it does#

  • Catalog and real-time search. Full search, the keystroke suggest endpoint, the genre “Discover” browse, artist discographies, playlists, mixes, lyrics, and track credits. Results are plain JSON-safe dicts or typed models.

  • Ids-first fetching. tracks_by_ids, albums_by_ids, artists_by_ids, and playlists_by_ids resolve N ids in one catalog round trip per 50, and the key-based siblings — single track_by_isrc / album_by_upc, bulk tracks_by_isrcs / albums_by_upcs — answer the same question from CDDB ISRCs or barcodes, for hosts that hold keys instead of links.

  • Streaming, without disk. stream and iter_streams hand plaintext audio bytes to the host (S3, a database, a pipe, a player). playback tells you what will be played before you stream a single byte.

  • Downloading. Resumable runs, quality downgrade when a profile is unavailable, per-item failure isolation. Files land through .part with atomic promotion, so a rerun skips what is already complete.

  • Auth. The device flow (approve in a browser), adopting a token from a web session, refresh, and logout.

How it behaves#

  • No global state. Every TidalClient owns its session, token, and caches. Two clients in one process never share credentials, so the library is safe under Django multi-worker and Celery prefork.

  • Per-item failure isolation. A collection download never dies on one bad item. Failures land in a structured DownloadReport with a machine code and a reason for each.

  • Standard ``logging`` only. Nothing is printed to stdout. Progress arrives as a callback that receives plain JSON-safe dicts.

  • Best effort is labelled. A missing or corrupt side file degrades to a documented default. It never crashes a run, and it never silently changes the result.

Contents#

History

Key classes#