Streaming (host-owned file I/O)#

client.stream(url) resolves a track link and returns a MediaStream of plaintext audio bytes — decrypted if Tidal served an encrypted rendition, remuxed containers resolved to their audio payload. No disk I/O happens anywhere in the library. This is the integration path for hosts that store media in S3, PostgreSQL, or anywhere else the library knows nothing about.

Basic use#

with client.stream("https://tidal.com/track/12345678") as media:
    for chunk in media.chunks():      # 256 KiB chunks by default
        sink.write(chunk)

MediaStream is a thin, thread-reading wrapper around the resolved StreamInfo:

  • media.chunks(chunk_size) — lazy iterator of bytes (the chunk size is a hint, not a promise);

  • media.read(n=-1) / media.readinto(buf) — buffered reads, with a proper leftover buffer (a partial read never loses bytes);

  • media.size — total plaintext size when it can be determined (cached), else -1;

  • media.delivered_quality, media.codec — what you’re getting;

  • media.track_id — the Tidal track id (for naming the stored object);

  • context manager / media.close() — releases the client’s operation registration;

  • iterating raises CancelledError when the client’s cancellation fires (by design: the host decides what to do with the partial data it already has).

S3 multipart example#

import boto3
from tidalpy import Config, TidalClient

s3 = boto3.client("s3")
client = TidalClient(Config(token_path="~/.tidalpy/token.json"))

with client.stream(track_url) as media:
    key = f"tidal/{media.track_id}.flac"
    upload = s3.create_multipart_upload(Bucket=BUCKET, Key=key)
    part_number = 0
    parts = []
    for chunk in media.chunks():
        part_number += 1
        resp = s3.upload_part(
            Bucket=BUCKET, Key=key,
            PartNumber=part_number, Body=chunk,
        )
        parts.append({"ETag": resp["ETag"],
                      "PartNumber": part_number})
    s3.complete_multipart_upload(
        Bucket=BUCKET, Key=key,
        MultipartUpload={"UploadId": upload["UploadId"], "Parts": parts},
    )

Caveats#

  • One track per stream. For collections, iterate client.list_items(url) and stream each track link.

  • The stream is pulled by the host: a slow sink simply slows the network (there is no unbounded buffering).

  • Streams do not write tags — the host owns the final object.