Skip to content

fix(deezer): recover delisted tracks via FALLBACK instead of a retired CDN URL - #1028

Open
berettavexee wants to merge 2 commits into
nathom:devfrom
berettavexee:fix/retired-legacy-cdn
Open

fix(deezer): recover delisted tracks via FALLBACK instead of a retired CDN URL#1028
berettavexee wants to merge 2 commits into
nathom:devfrom
berettavexee:fix/retired-legacy-cdn

Conversation

@berettavexee

@berettavexee berettavexee commented Aug 18, 2026

Copy link
Copy Markdown

The problem

When get_track_url returns None, get_downloadable falls back to _get_encrypted_file_url, which builds a legacy AES-ECB URL:

url = f"https://e-cdns-proxy-{track_hash[0]}.dzcdn.net/mobile/1/{path}"

Deezer has retired that CDN. None of the sixteen possible hosts resolve any more:

$ for c in 0 1 2 3 4 5 6 7 8 9 a b c d e f; do
    getent hosts e-cdns-proxy-$c.dzcdn.net >/dev/null && echo "$c OK"
  done
$ # no output — all sixteen fail

So the fallback can never succeed. What the user gets instead is the download being attempted, retried, and failing with:

Cannot connect to host e-cdns-proxy-8.dzcdn.net:443 ssl:default [Domain name not found]

which points at their DNS resolver rather than at the real cause.

Impact

Measured on a real run of 50 loved tracks: 8 tracks (16%) went through this path. Each one burned both download attempts before failing, and the run produced 25 ERROR lines about a hostname that no longer exists.

The proportion depends on the account and catalogue — tracks with older or incomplete GW metadata are the ones that reach the fallback.

What those tracks actually are

This turned out to be the interesting part. They are not unavailable — they are delisted: superseded by another release, typically a remaster. Deezer reports FILESIZE_* = 0 for every tier, hands out no URL at any quality, and names the superseding release in FALLBACK.SNG_ID.

That is the same field get_downloadable already follows on WrongGeolocation. The two cases differ only in how Deezer says no — there the API says "not here", here it says nothing at all.

The fix

Two commits:

1. Remove the dead CDN fallback. _get_encrypted_file_url goes, along with the binascii, hashlib and AES imports that only served it.

2. Follow FALLBACK.SNG_ID when no URL comes back, reusing the existing recursion and its is_retry guard (Deezer's fallback chains can point at another dead entry, so the hop is taken once).

Result on the same 50-track run: all 8 tracks recovered, all 8 as FLAC. So the path that previously guaranteed a DNS error now returns the audio the user asked for. Only when there is no fallback at all does it raise, and the message then says what is actually wrong:

Deezer returned no download URL for this track at any quality, and it has
no fallback track (delisted?). The legacy CDN that used to serve as a
fallback has been retired.

Two details worth flagging for review

The fallback is requested at the caller's quality, not the downgraded one. A delisted track reports FILESIZE_* = 0 for every tier, so the existing FILESIZE downgrade loop always bottoms out at MP3_128. Passing that on would fetch the replacement at the lowest quality regardless of what it offers. requested_quality is captured before the loop and used for the new hop only; the WrongGeolocation branch is left as-is to keep this diff focused, though it arguably has the same issue.

The Blowfish key follows the served track. Recursing passes fallback_id as item_id, so dl_info["id"] — from which DeezerDownloadable derives the key — describes the bytes actually being decrypted. This is worth stating because getting it wrong is invisible: the file has the right extension, the right size and the right duration, and only fails when something tries to decode it.

I verified it rather than assuming it: all 48 recovered FLACs pass flac -t, which checks the stream MD5 stored in STREAMINFO. A wrong key could not survive that.

DeezerDownloadable is deliberately left untouched: its is_encrypted regex still has to match the modern /media/ CDN, and its extension fallback for URLs without one remains useful as a defensive measure.

Tests

Four unit tests in tests/test_deezer.py, all using the existing mock_deezer_client fixture:

  • error raised when no URL comes back and there is no fallback
  • no e-cdns-proxy URL can leak into the message
  • a delisted track is recovered through FALLBACK.SNG_ID, at the requested quality, with the id following the served track
  • a fallback that itself resolves nowhere raises instead of recursing

tests/test_meta.py::test_album_metadata_qobuz fails on dev both before and after this change — pre-existing and unrelated.

🤖 Generated with Claude Code

berettavexee and others added 2 commits August 19, 2026 00:24
When get_track_url returns None, get_downloadable fell back to
_get_encrypted_file_url, which builds a legacy AES-ECB URL of the form

    https://e-cdns-proxy-<c>.dzcdn.net/mobile/1/<path>

Deezer has retired that CDN. None of the sixteen possible hosts resolve
any more, so the generated URL can never succeed: the download is
attempted, retried, and fails with "Cannot connect to host ... [Domain
name not found]" -- a DNS error that points at the user's resolver
rather than at the real cause.

Raising here instead makes the failure immediate and says what actually
happened. _get_encrypted_file_url is removed along with the binascii,
hashlib and AES imports that only served it.

DeezerDownloadable is left untouched: its is_encrypted regex still has
to match the modern /media/ CDN, and the extension fallback for URLs
without one remains useful as a defensive measure.
Getting no URL at any quality is the signature of a delisted old-catalog
track: it has not been made unavailable, it has been superseded by
another release (usually a remaster), and Deezer names that release in
FALLBACK.SNG_ID. That is the same field the WrongGeolocation branch
already follows; follow it here too, where the API returns nothing at
all rather than "not here".

These are exactly the tracks that reached the legacy CDN removed in the
previous commit, so the path that could only end in a DNS error now
recovers most of them instead. Measured on a 50-track loved-tracks pull:
8 tracks took this path, all 8 came back as FLAC, and all 8 decoded
clean under `flac -t` (which verifies the stream MD5 in STREAMINFO, so a
wrong Blowfish key would have been caught).

The hop is taken once, guarded by the existing is_retry flag, since
Deezer's FALLBACK chains can point at another dead entry.

The fallback is requested at the caller's quality rather than the one
the FILESIZE downgrade loop landed on: a delisted track reports
FILESIZE_* = 0 for every tier, so that loop always bottoms out at
MP3_128, and passing it on would fetch the replacement at the lowest
quality regardless of what it actually offers.

Recursing passes the fallback id as item_id, so dl_info["id"] -- from
which DeezerDownloadable derives the Blowfish key -- follows the track
actually served.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@berettavexee berettavexee changed the title fix(deezer): fail fast instead of building a URL for Deezer's retired CDN fix(deezer): recover delisted tracks via FALLBACK instead of a retired CDN URL Aug 20, 2026
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.

1 participant