Reject Qobuz 30-second sample/preview downloads
Two-layer detection: (1) check the Qobuz API response for sample=True before downloading, and (2) validate actual file duration with mutagen after download — if under 35 seconds, delete and return None. Qobuz returns valid audio files for previews (~2-5MB FLAC) that pass the existing 100KB size check, so duration is the reliable signal.
This commit is contained in:
parent
9d77c403cc
commit
bf123fed63
2 changed files with 24 additions and 0 deletions
|
|
@ -974,6 +974,12 @@ class QobuzClient:
|
|||
logger.error("No Qobuz stream available at any quality")
|
||||
return None
|
||||
|
||||
# Qobuz returns sample=True for 30-second previews (no subscription or region-restricted)
|
||||
if stream_data.get('sample', False):
|
||||
logger.warning(f"Qobuz returned a 30s sample for '{display_name}' — "
|
||||
f"track may require a Qobuz subscription or is region-restricted. Skipping.")
|
||||
return None
|
||||
|
||||
download_url = stream_data['url']
|
||||
|
||||
# Determine file extension from stream response
|
||||
|
|
@ -1065,6 +1071,23 @@ class QobuzClient:
|
|||
out_path.unlink(missing_ok=True)
|
||||
return None
|
||||
|
||||
# Safety net: detect 30-second samples by checking actual file duration.
|
||||
# Qobuz previews are valid audio files (~2-5MB) that pass the size check above.
|
||||
try:
|
||||
from mutagen import File as MutagenFile
|
||||
audio = MutagenFile(str(out_path))
|
||||
if audio and audio.info and audio.info.length:
|
||||
duration_s = audio.info.length
|
||||
if duration_s < 35:
|
||||
logger.warning(
|
||||
f"Qobuz download is only {duration_s:.0f}s — likely a 30s sample/preview "
|
||||
f"for '{display_name}'. Deleting."
|
||||
)
|
||||
out_path.unlink(missing_ok=True)
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.debug(f"Could not check audio duration (non-fatal): {e}")
|
||||
|
||||
final_size = out_path.stat().st_size if out_path.exists() else 0
|
||||
logger.info(f"Qobuz download complete: {out_path} ({final_size / (1024*1024):.1f} MB)")
|
||||
return str(out_path)
|
||||
|
|
|
|||
|
|
@ -3610,6 +3610,7 @@ const WHATS_NEW = {
|
|||
{ title: 'Fix "Replace Lower Quality" Setting Not Persisting', desc: 'The import section appeared twice in the settings save payload — the second instance (with only staging_path) overwrote the first (with replace_lower_quality). Merged into a single block' },
|
||||
{ title: 'Inbound Music Request API', desc: 'New POST /api/v1/request endpoint — trigger downloads from Discord bots, Home Assistant, curl, or any external tool. Async with status polling and optional notify_url callback. New "Webhook Received" automation trigger and "Search & Download" action in the Automation Hub' },
|
||||
{ title: 'Fix Spotify Enrichment Worker Infinite Loop', desc: 'Artists with an existing Spotify ID but no match status got stuck in the enrichment queue — the worker processed them every 3 seconds forever without marking them as done. Now correctly marks them as matched' },
|
||||
{ title: 'Reject Qobuz 30-Second Samples', desc: 'Qobuz previews (30s samples for tracks requiring a subscription or region-restricted) are now detected and rejected. Checks the API sample flag before downloading, and validates file duration after download as a safety net' },
|
||||
|
||||
// --- April 14, 2026 ---
|
||||
{ date: 'April 14, 2026' },
|
||||
|
|
|
|||
Loading…
Reference in a new issue