Improve Tidal download failure diagnostics and error messaging
Track per-quality-tier failure reasons across all failure paths (stream error, empty manifest, download exception, stub file, MP4 extraction failure) and include them in the exhausted-tiers log message so failures are diagnosable from logs. When HiRes is configured with no fallback and all tiers are exhausted, log an actionable hint directing the user to enable Quality Fallback. Surface Tidal-specific error messages in the UI task on retry exhaustion: distinguishes HiRes-unavailable (with actionable guidance) from general Tidal auth/quality failures, rather than showing the generic Soulseek error string.
This commit is contained in:
parent
c1ef32acd2
commit
6e405143a7
2 changed files with 60 additions and 3 deletions
|
|
@ -448,6 +448,8 @@ class TidalDownloadClient:
|
|||
|
||||
MIN_AUDIO_SIZE = 100 * 1024 # 100KB
|
||||
|
||||
quality_error_reasons = []
|
||||
|
||||
for q_key in chain:
|
||||
q_info = QUALITY_MAP[q_key]
|
||||
|
||||
|
|
@ -456,18 +458,24 @@ class TidalDownloadClient:
|
|||
self.session.audio_quality = q_info['tidal_quality']
|
||||
stream = track.get_stream()
|
||||
if not stream or not stream.manifest_mime_type:
|
||||
reason = f"{q_key}: no stream returned"
|
||||
logger.warning(f"Quality {q_key} returned no stream, trying next")
|
||||
quality_error_reasons.append(reason)
|
||||
continue
|
||||
logger.info(f"Got Tidal stream at quality: {q_key}")
|
||||
except Exception as e:
|
||||
reason = f"{q_key}: {type(e).__name__}: {e}"
|
||||
logger.warning(f"Quality {q_key} unavailable: {e}")
|
||||
quality_error_reasons.append(reason)
|
||||
continue
|
||||
|
||||
# --- Step 2: Parse manifest ---
|
||||
manifest = stream.get_stream_manifest()
|
||||
urls = manifest.get_urls()
|
||||
if not urls:
|
||||
reason = f"{q_key}: manifest returned no URLs"
|
||||
logger.warning(f"No download URLs for quality {q_key}, trying next")
|
||||
quality_error_reasons.append(reason)
|
||||
continue
|
||||
|
||||
download_url = urls[0]
|
||||
|
|
@ -483,6 +491,20 @@ class TidalDownloadClient:
|
|||
else:
|
||||
extension = q_info.get('extension', 'flac')
|
||||
|
||||
# Verify quality wasn't silently downgraded: if HiRes was requested but the
|
||||
# codec/manifest points to standard FLAC, log a clear warning.
|
||||
if q_key == 'hires' and codec:
|
||||
codec_lower = codec.lower()
|
||||
if 'flac' in codec_lower or 'alac' in codec_lower:
|
||||
# HiRes should be 24-bit — we can't confirm bit-depth from the codec
|
||||
# string alone, but we log the received codec so users can diagnose.
|
||||
logger.info(f"HiRes stream codec: {codec} (verify file bit-depth after download)")
|
||||
elif 'mp4a' in codec_lower or 'aac' in codec_lower:
|
||||
logger.warning(
|
||||
f"HiRes requested but received AAC stream (codec: {codec}) — "
|
||||
f"account may not have HiRes subscription or track isn't available in HiRes"
|
||||
)
|
||||
|
||||
# Build output filename
|
||||
safe_name = re.sub(r'[<>:"/\\|?*]', '_', display_name)
|
||||
out_filename = f"{safe_name}.{extension}"
|
||||
|
|
@ -533,6 +555,7 @@ class TidalDownloadClient:
|
|||
|
||||
except Exception as dl_err:
|
||||
logger.warning(f"Download failed at quality {q_key}: {dl_err}")
|
||||
quality_error_reasons.append(f"{q_key}: download error: {type(dl_err).__name__}: {dl_err}")
|
||||
out_path.unlink(missing_ok=True)
|
||||
continue
|
||||
|
||||
|
|
@ -542,6 +565,7 @@ class TidalDownloadClient:
|
|||
f"Tidal download too small at {q_key} ({downloaded} bytes) — "
|
||||
f"likely a stub/preview for '{display_name}'. Trying next quality."
|
||||
)
|
||||
quality_error_reasons.append(f"{q_key}: file too small ({downloaded} bytes), likely a stub")
|
||||
out_path.unlink(missing_ok=True)
|
||||
continue
|
||||
|
||||
|
|
@ -555,6 +579,7 @@ class TidalDownloadClient:
|
|||
f"Cannot extract FLAC from MP4 container at {q_key} — "
|
||||
f"deleting and trying next quality"
|
||||
)
|
||||
quality_error_reasons.append(f"{q_key}: FLAC extraction from MP4 container failed")
|
||||
out_path.unlink(missing_ok=True)
|
||||
continue
|
||||
|
||||
|
|
@ -565,6 +590,7 @@ class TidalDownloadClient:
|
|||
f"Final file too small after processing at {q_key} "
|
||||
f"({final_size} bytes) — trying next quality"
|
||||
)
|
||||
quality_error_reasons.append(f"{q_key}: final file too small after extraction ({final_size} bytes)")
|
||||
out_path.unlink(missing_ok=True)
|
||||
continue
|
||||
|
||||
|
|
@ -572,8 +598,21 @@ class TidalDownloadClient:
|
|||
logger.info(f"Tidal download complete ({q_key}): {out_path} ({final_size / (1024*1024):.1f} MB)")
|
||||
return str(out_path)
|
||||
|
||||
# All quality tiers exhausted
|
||||
logger.error(f"No Tidal quality tier produced a valid download for '{display_name}'")
|
||||
# All quality tiers exhausted — build a diagnostic message
|
||||
# Re-use quality_key/allow_fallback already read above to stay consistent
|
||||
# with how the chain was built (avoids config-change-mid-download inconsistency).
|
||||
reasons_str = '; '.join(quality_error_reasons) if quality_error_reasons else 'unknown'
|
||||
if quality_key == 'hires' and not allow_fallback:
|
||||
hint = (
|
||||
" HiRes quality is unavailable for this track on your account or in your region. "
|
||||
"Enable 'Quality Fallback' in Tidal settings to fall back to Lossless automatically."
|
||||
)
|
||||
else:
|
||||
hint = ""
|
||||
logger.error(
|
||||
f"No Tidal quality tier produced a valid download for '{display_name}'."
|
||||
f"{hint} Failure reasons: [{reasons_str}]"
|
||||
)
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -2887,7 +2887,25 @@ class WebUIDownloadMonitor:
|
|||
sources_str = f' (tried {len(tried_sources)} source{"s" if len(tried_sources) != 1 else ""})' if tried_sources else ''
|
||||
print(f"Task failed after 3 error retry attempts")
|
||||
task['status'] = 'failed'
|
||||
task['error_message'] = f'Soulseek transfer errored 3 times for "{track_label}"{sources_str} — all sources failed or became unavailable'
|
||||
# Tidal-specific error: check if this was a quality issue.
|
||||
# task['username'] is popped on error-retry (line ~2866) so we can't rely on it;
|
||||
# used_sources keys are formatted as "{username}_{filename}", so startswith is exact.
|
||||
is_tidal = any(s.startswith('tidal_') for s in tried_sources)
|
||||
if is_tidal:
|
||||
tidal_quality = config_manager.get('tidal_download.quality', 'lossless')
|
||||
allow_fb = config_manager.get('tidal_download.allow_fallback', True)
|
||||
if tidal_quality == 'hires' and not allow_fb:
|
||||
task['error_message'] = (
|
||||
f'Tidal download failed for "{track_label}" — HiRes quality is unavailable for this track '
|
||||
f'on your account or in your region. Enable "Quality Fallback" in Tidal settings to fall back to Lossless.'
|
||||
)
|
||||
else:
|
||||
task['error_message'] = (
|
||||
f'Tidal download failed for "{track_label}"{sources_str} — '
|
||||
f'check Tidal authentication and quality settings.'
|
||||
)
|
||||
else:
|
||||
task['error_message'] = f'Soulseek transfer errored 3 times for "{track_label}"{sources_str} — all sources failed or became unavailable'
|
||||
|
||||
# CRITICAL: Notify batch manager so track is added to permanently_failed_tracks
|
||||
batch_id = task.get('batch_id')
|
||||
|
|
|
|||
Loading…
Reference in a new issue