Fix CDN download resilience & rewrite Qobuz authentication#326
Fix CDN download resilience & rewrite Qobuz authentication#326Zachery2008 wants to merge 3 commits intovitiko98:masterfrom
Conversation
Refactor tqdm_download to use requests instead of urllib for downloading files. Remove unused imports and handle download in chunks with requests.
|
Yes, please! |
mzilinski
left a comment
There was a problem hiding this comment.
Review PR #326
Guter Ansatz — CDN-Probleme bei großen Hi-Res Downloads sind ein reales Problem. Hier mein detailliertes Feedback:
✅ Positiv
- Exponentieller Backoff: Sinnvolle Retry-Strategie mit steigenden Wartezeiten (2s, 4s, 8s, 16s).
- Frische URL bei Retry: Wichtig, da Akamai signierte URLs nach einer gewissen Zeit ablehnt. Guter Ansatz mit
get_track_url(). - Graceful Skip: Album-Download bricht nicht mehr komplett ab wenn ein einzelner Track fehlschlägt — deutlich bessere UX.
- Timeout hinzugefügt:
timeout=(10, 60)intqdm_downloadverhindert endloses Hängen. Gute Ergänzung. - Incomplete-Download-Check verbessert:
download_size < totalstatttotal != download_sizeist semantisch richtiger und robuster (z.B. beicontent-length: 0).
⚠️ Verbesserungsvorschläge
-
Partielle Datei-Bereinigung: Bei fehlgeschlagenen Retries wird
filename(die.tmp-Datei) gelöscht — gut. Aber wenn der letzte Versuch intqdm_downloadmit einer unerwarteten Exception (nicht in der catch-Liste) fehlschlägt, bleibt eine teilweise.tmp-Datei zurück. Einfinally-Block oder ein generellerer Cleanup wäre robuster. -
time.sleep(1)zwischen Tracks: Der Delay ist nur indownload_release, nicht in anderen Download-Pfaden. Außerdem wird der Sleep auch bei Demo-/nicht-streamable Tracks ausgeführt — man könnte den Sleep hinter den erfolgreichen Download verschieben. -
Redundanter Exception-Typ:
ConnectionError(built-in) undrequests.exceptions.ConnectionErrorsind in Python identisch —requestsre-exportiert den built-in. Kein Bug, aber redundant in der catch-Liste.
💬 Frage
- Wurde das mit einem ganzen Album getestet, bei dem ein Track reproduzierbar fehlschlägt? Das Retry + Skip-Verhalten wäre interessant im echten Betrieb zu validieren.
Fazit
Solider PR, der ein echtes Problem löst. Die Kernlogik ist korrekt. Die obigen Punkte sind Verfeinerungen, keine Blocker.
Update zu meinem Review — kritische Bugs gefundenBei genauerer Analyse des Codes sind drei kritische Probleme aufgefallen, die ich im ursprünglichen Review übersehen habe: 1. Retry wird nie ausgelöst — fehlender HTTP-Statuscheck
Fix: 2. Stale URL bei URL-Refresh-Fehler → Retry schlägt immer fehlWenn Fix: Bei Refresh-Fehler 3. Memory Leak — Response wird nie geschlossen
Fix: Falls gewünscht, kann ich einen separaten PR mit den Korrekturen erstellen. |
When downloading large Hi-Res FLAC files (e.g. 24-bit/96kHz), the Akamai CDN
occasionally drops the connection after delivering only 1 byte. This causes
IncompleteRead / ChunkedEncodingError exceptions that abort the entire
album download with no recovery. Additionally, Qobuz deprecated the old
GET-based
user/loginendpoint and the browser-based OAuth redirect flowrequires Qobuz to whitelist redirect URLs, which doesn't work for local CLI
apps — making it impossible to log in without manually copying a token from
DevTools.
The CDN issue is server-side — confirmed via curl that specific files can be
persistently broken on a regional edge node while other tracks from the same
album download fine.
Changes
qobuz_dl/downloader.py
with increasing delays (2s, 4s, 8s, 16s) before giving up.
get_track_url(), since Akamai rejects reused/stale signed URLs.log instead of aborting the entire album. The partial .tmp file is cleaned up.
timeout=(10, 60)to the download request(10s connect, 60s read) to prevent indefinite hangs.
downloads to reduce CDN throttling on large albums.
qobuz_dl/qopy.py — Authentication rewrite
Qobuz deprecated the old GET-based
user/loginendpoint. Auth has beenrewritten to support two flows:
_login_with_password()method sends astandard POST to
user/loginwith email and password (matching the webplayer's own login mechanism). Returns a
user_auth_tokenon success.auth()checks if the stored credentialis a short password (< 60 chars) or a long token, and tries POST login first
when it looks like a password. Existing token-based configs continue to work
unchanged.
user/get: After obtaining a token (either fromPOST login or config), validates it against the
user/getendpoint. Handlesthe flat response structure (credential at top level, not nested under
"user").user_auth_token, it ispersisted back to
config.iniautomatically.qobuz_dl/cli.py — Config wizard update
qobuz-dl -rnow asks for your actual Qobuz passwordand attempts POST login to obtain a token automatically.
reCAPTCHA in the future), falls back to manual
user_auth_tokenpaste withinstructions.
(
oauth.py) has been removed — it required Qobuz to whitelist the redirectURL, which doesn't work for local CLI apps.