fix(deezer): recover delisted tracks via FALLBACK instead of a retired CDN URL - #1028
Open
berettavexee wants to merge 2 commits into
Open
fix(deezer): recover delisted tracks via FALLBACK instead of a retired CDN URL#1028berettavexee wants to merge 2 commits into
berettavexee wants to merge 2 commits into
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
When
get_track_urlreturnsNone,get_downloadablefalls back to_get_encrypted_file_url, which builds a legacy AES-ECB URL:Deezer has retired that CDN. None of the sixteen possible hosts resolve any more:
So the fallback can never succeed. What the user gets instead is the download being attempted, retried, and failing with:
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_* = 0for every tier, hands out no URL at any quality, and names the superseding release inFALLBACK.SNG_ID.That is the same field
get_downloadablealready follows onWrongGeolocation. 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_urlgoes, along with thebinascii,hashlibandAESimports that only served it.2. Follow
FALLBACK.SNG_IDwhen no URL comes back, reusing the existing recursion and itsis_retryguard (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:
Two details worth flagging for review
The fallback is requested at the caller's quality, not the downgraded one. A delisted track reports
FILESIZE_* = 0for every tier, so the existing FILESIZE downgrade loop always bottoms out atMP3_128. Passing that on would fetch the replacement at the lowest quality regardless of what it offers.requested_qualityis captured before the loop and used for the new hop only; theWrongGeolocationbranch 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_idasitem_id, sodl_info["id"]— from whichDeezerDownloadablederives 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 inSTREAMINFO. A wrong key could not survive that.DeezerDownloadableis deliberately left untouched: itsis_encryptedregex 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 existingmock_deezer_clientfixture:e-cdns-proxyURL can leak into the messageFALLBACK.SNG_ID, at the requested quality, with the id following the served tracktests/test_meta.py::test_album_metadata_qobuzfails ondevboth before and after this change — pre-existing and unrelated.🤖 Generated with Claude Code