fix: force IPv4 in worker, count real Qobuz files, flatten nested output
- worker had no IPv6 route -> Qobuz CDN (dual-stack) failed ~half the tracks with 'Network is unreachable'; disable IPv6 in the container (IPv4 only). - StreamripClient counted metadata tracks (always full) -> a partial download falsely imported; now count the actual files written (short count -> needs_attention). - streamrip writes into a nested 'Artist - Album [..]/' folder the tagger never saw; flatten audio files up into the album dir so tagging/organization runs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,37 @@ def _hashed_password(password: str) -> str:
|
||||
return hashlib.md5(password.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
_AUDIO_EXT = {".flac", ".mp3", ".m4a", ".opus", ".ogg", ".aac", ".wav"}
|
||||
|
||||
|
||||
def _flatten_audio(dest: str) -> int:
|
||||
"""streamrip writes into a nested `Artist - Album [...]/` subfolder. Move any audio
|
||||
files up into `dest`, remove the now-empty subfolders, and return the count of audio
|
||||
files directly in `dest` — the REAL number downloaded, so a partial download (streamrip
|
||||
continues past per-track failures) reports its true short count to the completeness check.
|
||||
"""
|
||||
dest = os.path.abspath(dest)
|
||||
for root, _dirs, files in os.walk(dest):
|
||||
if os.path.abspath(root) == dest:
|
||||
continue
|
||||
for name in files:
|
||||
if os.path.splitext(name)[1].lower() not in _AUDIO_EXT:
|
||||
continue
|
||||
src = os.path.join(root, name)
|
||||
target = os.path.join(dest, name)
|
||||
if os.path.exists(target):
|
||||
base, ext = os.path.splitext(name)
|
||||
target = os.path.join(dest, f"{base} ({os.path.basename(root)}){ext}")
|
||||
os.rename(src, target)
|
||||
for root, _dirs, _files in os.walk(dest, topdown=False):
|
||||
if os.path.abspath(root) != dest:
|
||||
try:
|
||||
os.rmdir(root)
|
||||
except OSError:
|
||||
pass # subfolder still has non-audio files (e.g. cover art) — leave it
|
||||
return sum(1 for f in os.listdir(dest) if os.path.splitext(f)[1].lower() in _AUDIO_EXT)
|
||||
|
||||
|
||||
class StreamripClient:
|
||||
"""Real Qobuz client backed by streamrip (v2, async → sync via asyncio.run).
|
||||
|
||||
@@ -76,9 +107,12 @@ class StreamripClient:
|
||||
return results
|
||||
|
||||
def download(self, source_ref: str, dest: str, on_progress: Callable[[float], None]) -> dict:
|
||||
return asyncio.run(self._download(source_ref, dest, on_progress))
|
||||
asyncio.run(self._download(source_ref, dest, on_progress))
|
||||
# Count the files actually written and flatten streamrip's nested output into dest
|
||||
# so the tag stage can organize it. A partial download -> short count -> needs_attention.
|
||||
return {"track_count": _flatten_audio(dest), "path": dest}
|
||||
|
||||
async def _download(self, album_id: str, dest: str, on_progress: Callable[[float], None]) -> dict:
|
||||
async def _download(self, album_id: str, dest: str, on_progress: Callable[[float], None]) -> None:
|
||||
os.makedirs(dest, exist_ok=True)
|
||||
from streamrip.client import QobuzClient
|
||||
from streamrip.db import Database, Dummy
|
||||
@@ -98,8 +132,6 @@ class StreamripClient:
|
||||
if album is None:
|
||||
raise RuntimeError(f"could not resolve Qobuz album {album_id}")
|
||||
await album.rip()
|
||||
track_count = len(getattr(album, "tracks", []) or [])
|
||||
on_progress(1.0)
|
||||
finally:
|
||||
await client.session.close()
|
||||
return {"track_count": track_count, "path": dest}
|
||||
|
||||
Reference in New Issue
Block a user