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.
pull/304/head
Broque Thomas 1 month ago
parent 9d77c403cc
commit bf123fed63

@ -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…
Cancel
Save