Skip to content

Tidal: download Max (HI_RES_LOSSLESS) via DASH, fix ffmpeg on Windows, optional AAC→FLAC - #1044

Open
Wariipana wants to merge 3 commits into
nathom:devfrom
Wariipana:tidal-hires-dash
Open

Wariipana wants to merge 3 commits into
nathom:devfrom
Wariipana:tidal-hires-dash

Conversation

@Wariipana

Copy link
Copy Markdown

Fixes #981. Builds on the idea in #998, but works on Windows without changing the event loop policy and downloads segments concurrently.

Problem

  1. Max quality never downloads. Tidal serves HI_RES_LOSSLESS as an MPEG-DASH manifest (application/dash+xml). get_downloadable() runs json.loads() on it, gets JSONDecodeError, logs "Retrying with lower quality" and ends up with AAC 320.
  2. ffmpeg never runs on Windows. rip/main.py sets WindowsSelectorEventLoopPolicy, and that loop does not implement asyncio subprocesses, so every asyncio.create_subprocess_exec raises NotImplementedError. That breaks --codec / [conversion] on Windows today, and it is also why the ffmpeg remux in (fixes #981) HI_RES_LOSSLESS downloads via MPEG-DASH manifest support #998 fails for Windows users in that thread.

Changes

  • converter.run_ffmpeg(): runs ffmpeg with subprocess.run in a worker thread (asyncio.to_thread) with stdin closed. Used by Converter.convert() and concat_audio_files(). Works on every platform and with any event loop.
  • TidalDashDownloadable: get_downloadable() checks manifestMimeType first. For DASH, _parse_mpd() reads the single Representation and the $Number$ SegmentTemplate; segments are fetched concurrently (up to 16 at a time, results kept in order), joined byte for byte after the init segment and remuxed to FLAC with -c:a copy (no re-encoding). Progress is reported per segment. A manifest layout we don't understand raises NonStreamableError instead of downloading the wrong thing.
  • Optional AAC→FLAC ([tidal] convert_aac_to_flac = false, or --aac-to-flac): tracks that Tidal only delivers as AAC are converted to 16-bit FLAC before tagging, so tags and cover art are written natively. Skipped when [conversion] is enabled. The dataclass field has a default, so existing configs without the key keep loading and no config version bump is needed. It does not recover quality lost to AAC; it is for people who want a uniform FLAC library.

No change to CLIENT_ID is needed: the current one already returns HI_RES_LOSSLESS DASH manifests.

Testing

Windows 11, Python 3.11, ffmpeg 8.1, Tidal Max subscription, running rip from this branch:

Case Result
rip -q 3 url .../track/77646170 (Max available) FLAC, 24-bit / 192 kHz, tags + cover
rip -q 3 --aac-to-flac url .../track/103805726 (only AAC available) FLAC, 16-bit / 44.1 kHz, tags + cover
rip -q 3 url .../track/103805726 (same, no flag) AAC .m4a, unchanged behaviour
  • New tests/test_tidal_dash.py (MPD parsing, segment URLs, unsupported layouts).
  • Full suite: 62 passed, 7 skipped (credentials). test_meta.py::test_album_metadata_qobuz fails on dev without these changes too.

Notes

streamrip sets WindowsSelectorEventLoopPolicy on Windows, and that loop
does not implement asyncio subprocesses: every create_subprocess_exec call
raises NotImplementedError, so --codec / [conversion] and segment
concatenation always failed there. Run ffmpeg through subprocess.run in a
worker thread instead, with stdin closed so it never reads from the TTY.
Tidal serves Max quality as an MPEG-DASH manifest (application/dash+xml)
instead of the JSON manifest used by the other tiers. get_downloadable()
tried json.loads() on it, hit JSONDecodeError and silently retried at a
lower quality, so Max always ended up as AAC.

Parse the MPD (single Representation, $Number$ SegmentTemplate), fetch
the init segment and media segments concurrently, join them in order and
remux to FLAC with ffmpeg without re-encoding. Unknown manifest layouts
raise NonStreamableError instead of downloading the wrong thing.

Fixes nathom#981
Tracks that Tidal only delivers as AAC (.m4a) can now be converted to
16-bit FLAC with ffmpeg, via [tidal] convert_aac_to_flac = true or the
--aac-to-flac flag. It runs before tagging so metadata and cover art are
written natively to the FLAC file, and it is skipped when the general
[conversion] setting is enabled. The option defaults to false, so
existing config files without the key keep loading.
@thenktor

thenktor commented Oct 30, 2026 •

Copy link
Copy Markdown

Hi,

I've instelled your fork with pip3 install --upgrade git+https://github.com/Wariipana/streamrip.git@tidal-hires-dash
The I've tried to download something:

rip --no-db url https://tidal.com/album/544118021 ## got FLAC
rip --no-db url https://tidal.com/album/558242992 ## got M4A

Tested with q3. When trying q2, both return m4a. No way to download 2: 16/44.1 "HiFi" FLAC like mentioned in config file?

So it seems to work for some. I'm new to Tidal: Does it mean they do not have everything lossless?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Streamrip downloads lower quality that specified with max Tidal subscription.

2 participants