fix(worker): raise http.client header limit so Qobuz tracks stop failing

Bruno Mars "24K Magic" track 1 repeatedly downloaded as a 0-byte file. The
worker log showed the real cause: streamrip's track download hit
HTTPException("got more than 100 headers") — Qobuz's CDN returns >100 response
headers for some tracks and Python's http.client rejects them at the default
_MAXHEADERS=100, so streamrip skips the track and leaves an empty placeholder.

- Raise http.client._MAXHEADERS to 1000 on import of the Qobuz adapter, so those
  responses parse and the track downloads.
- _count_staged_audio now skips 0-byte files (a failed-track placeholder is not a
  real track) — improves the download-progress estimate and stops counting dead
  tracks. (A verify-level "reject 0-byte tracks" safety net is a follow-up under
  #16 per-track duration matching — it needs the pipeline fakes to write real
  files, deferred to avoid disproportionate churn here.)

worker 223 tests / 7-skip.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jonathan
2026-07-14 13:17:20 +02:00
parent 9a95c33b7e
commit bc42e546f0
3 changed files with 28 additions and 7 deletions
@@ -1,8 +1,15 @@
import asyncio import asyncio
import hashlib import hashlib
import http.client
import os import os
from typing import Callable from typing import Callable
# Qobuz's CDN returns responses with more than Python's default 100-header limit for some
# tracks, so http.client raises HTTPException("got more than 100 headers"), streamrip skips
# that track, and it lands as a 0-byte file (seen on Bruno Mars "24K Magic" — track 1). Raise
# the limit process-wide so those downloads succeed. (Set on import of the Qobuz adapter.)
http.client._MAXHEADERS = 1000
def _hashed_password(password: str) -> str: def _hashed_password(password: str) -> str:
"""streamrip's Qobuz email/password login expects the password as an MD5 hex digest.""" """streamrip's Qobuz email/password login expects the password as an MD5 hex digest."""
+9 -2
View File
@@ -18,12 +18,19 @@ _AUDIO_EXT = {".flac", ".mp3", ".m4a", ".opus", ".ogg", ".aac", ".wav"}
def _count_staged_audio(staging: str) -> int: def _count_staged_audio(staging: str) -> int:
"""Count audio files anywhere under the staging dir (streamrip nests them in a subfolder).""" """Count NON-EMPTY audio files anywhere under the staging dir (streamrip nests them in a
subfolder). 0-byte files are skipped: streamrip leaves an empty placeholder when a track
download fails (e.g. Qobuz's >100-header responses), and a dead track must not count toward
completeness — otherwise a broken album imports as 'complete'."""
n = 0 n = 0
for _root, _dirs, files in os.walk(staging): for root, _dirs, files in os.walk(staging):
for f in files: for f in files:
if os.path.splitext(f)[1].lower() in _AUDIO_EXT: if os.path.splitext(f)[1].lower() in _AUDIO_EXT:
try:
if os.path.getsize(os.path.join(root, f)) > 0:
n += 1 n += 1
except OSError:
pass
return n return n
+11 -4
View File
@@ -41,14 +41,21 @@ def test_on_progress_clamps_out_of_range(conn):
def test_count_staged_audio_counts_nested_audio_files_only(tmp_path): def test_count_staged_audio_counts_nested_audio_files_only(tmp_path):
nested = tmp_path / "Artist" / "Album" nested = tmp_path / "Artist" / "Album"
nested.mkdir(parents=True) nested.mkdir(parents=True)
(nested / "01.flac").write_bytes(b"") (nested / "01.flac").write_bytes(b"audio")
(nested / "02.flac").write_bytes(b"") (nested / "02.flac").write_bytes(b"audio")
(nested / "03.flac").write_bytes(b"") (nested / "03.flac").write_bytes(b"audio")
(nested / "cover.jpg").write_bytes(b"") (nested / "cover.jpg").write_bytes(b"img")
assert _count_staged_audio(str(tmp_path)) == 3 assert _count_staged_audio(str(tmp_path)) == 3
def test_count_staged_audio_skips_zero_byte_files(tmp_path):
# a 0-byte file is a failed-track placeholder (e.g. streamrip skipping a track) → not a track
(tmp_path / "01 Good.flac").write_bytes(b"audio")
(tmp_path / "02 Dead.flac").write_bytes(b"")
assert _count_staged_audio(str(tmp_path)) == 1
def test_count_staged_audio_empty_dir_is_zero(tmp_path): def test_count_staged_audio_empty_dir_is_zero(tmp_path):
assert _count_staged_audio(str(tmp_path)) == 0 assert _count_staged_audio(str(tmp_path)) == 0