Fix Deezer download integration: add deezer_dl to all streaming source checks

- Added deezer_dl to 7 hardcoded streaming source username tuples in web_server
- Streaming sources now bypass Soulseek filename-matching engine in get_valid_candidates
  (API search results are already properly matched, no need for P2P filename scoring)
- Fixes download progress tracking, completion detection, and candidate filtering for Deezer
This commit is contained in:
Broque Thomas 2026-03-23 15:57:53 -07:00
parent 9c60040a9b
commit 7eed88c72c

View file

@ -1876,7 +1876,7 @@ def get_cached_transfer_data():
all_downloads = run_async(soulseek_client.get_all_downloads())
for download in all_downloads:
# Only add streaming source downloads (Soulseek ones are already in the lookup)
if download.username in ('youtube', 'tidal', 'qobuz', 'hifi'):
if download.username in ('youtube', 'tidal', 'qobuz', 'hifi', 'deezer_dl'):
key = _make_context_key(download.username, download.filename)
# Convert DownloadStatus to transfer dict format
live_transfers_lookup[key] = {
@ -2203,7 +2203,7 @@ class WebUIDownloadMonitor:
all_downloads = run_async(soulseek_client.get_all_downloads())
for download in all_downloads:
# Only add streaming source downloads (Soulseek ones are already in the lookup from slskd API)
if download.username in ('youtube', 'tidal', 'qobuz', 'hifi'):
if download.username in ('youtube', 'tidal', 'qobuz', 'hifi', 'deezer_dl'):
key = _make_context_key(download.username, download.filename)
# Convert DownloadStatus to transfer dict format for monitor compatibility
live_transfers[key] = {
@ -7188,7 +7188,7 @@ def stream_enhanced_search_track():
_hybrid_order = config_manager.get('download_source.hybrid_order', [])
_hybrid_first = _hybrid_order[0] if _hybrid_order else config_manager.get('download_source.hybrid_primary', 'soulseek')
if download_mode in ('youtube', 'tidal', 'qobuz', 'hifi') or (download_mode == 'hybrid' and _hybrid_first in ('youtube', 'tidal', 'qobuz', 'hifi')):
if download_mode in ('youtube', 'tidal', 'qobuz', 'hifi', 'deezer_dl') or (download_mode == 'hybrid' and _hybrid_first in ('youtube', 'tidal', 'qobuz', 'hifi', 'deezer_dl')):
# YouTube/Tidal mode: Include artist for better context
# Primary query: Artist + Track
if artist_name and track_name:
@ -7365,7 +7365,7 @@ def start_download():
if download_id:
# Register download for post-processing (simple transfer to /Transfer)
context_key = _make_context_key(username, filename)
is_streaming_source = username in ('youtube', 'tidal', 'qobuz', 'hifi')
is_streaming_source = username in ('youtube', 'tidal', 'qobuz', 'hifi', 'deezer_dl')
with matched_context_lock:
matched_downloads_context[context_key] = {
'search_result': {
@ -7743,7 +7743,7 @@ def get_download_status():
all_streaming_downloads = run_async(soulseek_client.get_all_downloads())
for download in all_streaming_downloads:
if download.username in ('youtube', 'tidal', 'qobuz', 'hifi'):
if download.username in ('youtube', 'tidal', 'qobuz', 'hifi', 'deezer_dl'):
source_label = download.username.title()
# Convert DownloadStatus to transfer format that frontend expects
streaming_transfer = {
@ -21956,16 +21956,29 @@ def get_valid_candidates(results, spotify_track, query):
Soulseek search results against a Spotify track to find the best, most
accurate download candidates.
"""
if not results:
if not results:
return []
# Uses the existing, powerful matching engine for scoring
# Streaming sources (YouTube, Tidal, Qobuz, HiFi, Deezer) return structured API results
# with proper artist/title metadata — bypass Soulseek filename-parsing matching engine
# and trust the search API's results directly
_streaming_sources = ("youtube", "tidal", "qobuz", "hifi", "deezer_dl")
if results[0].username in _streaming_sources:
source_label = results[0].username.replace('_dl', '').title()
print(f"🎵 [{source_label}] Streaming source — using API results directly ({len(results)} candidates)")
for r in results:
r.confidence = 1.0
r.version_type = 'original'
return results
# Uses the existing, powerful matching engine for scoring (Soulseek P2P results)
_max_q = config_manager.get('soulseek.max_peer_queue', 0) or 0
initial_candidates = matching_engine.find_best_slskd_matches_enhanced(spotify_track, results, max_peer_queue=_max_q)
if not initial_candidates:
return []
# Skip quality filtering for YouTube/Tidal/Qobuz results (quality is fixed by source, not user-selectable per-result)
is_streaming_source = initial_candidates[0].username in ("youtube", "tidal", "qobuz", "hifi") if initial_candidates else False
# Skip quality filtering for streaming source results that somehow got here
is_streaming_source = initial_candidates[0].username in _streaming_sources if initial_candidates else False
if is_streaming_source:
source_label = initial_candidates[0].username.title()
@ -24472,7 +24485,7 @@ def _try_source_reuse(task_id, batch_id, track):
if not source_tracks or not last_source:
_sr.info(f"Skipped — no source_tracks or no last_source")
return False
if last_source.get('username') in ('youtube', 'tidal', 'qobuz', 'hifi'):
if last_source.get('username') in ('youtube', 'tidal', 'qobuz', 'hifi', 'deezer_dl'):
_sr.info(f"Skipped — {last_source.get('username')} source (no folder-based reuse)")
return False
@ -24574,7 +24587,7 @@ def _store_batch_source(batch_id, username, filename):
"""Browse the successful download's folder and store results on the batch for reuse."""
_sr = source_reuse_logger
_sr.info(f"_store_batch_source called: batch={batch_id}, user={username}, file={filename}")
if not batch_id or username in ('youtube', 'tidal', 'qobuz', 'hifi'):
if not batch_id or username in ('youtube', 'tidal', 'qobuz', 'hifi', 'deezer_dl'):
_sr.info(f"Skipped — no batch_id or streaming source ({username})")
return
@ -26917,6 +26930,74 @@ def deezer_download_test():
return jsonify({'success': False, 'error': str(e)})
@app.route('/api/deezer-download/test-search', methods=['GET'])
def deezer_download_test_search():
"""Test Deezer download search (temporary testing endpoint)."""
try:
query = request.args.get('q', '')
if not query:
return jsonify({'success': False, 'error': 'No query provided'})
arl = config_manager.get('deezer_download.arl', '')
if not arl:
return jsonify({'success': False, 'error': 'No ARL configured'})
from core.deezer_download_client import DeezerDownloadClient
client = DeezerDownloadClient()
if not client.is_authenticated():
client.reconnect(arl)
if not client.is_authenticated():
return jsonify({'success': False, 'error': 'Authentication failed'})
tracks, albums = client._search_sync(query)
results = []
for t in tracks[:10]:
results.append({
'title': t.title,
'artist': t.artist,
'album': t.album,
'quality': t.quality,
'bitrate': t.bitrate,
'duration_ms': t.duration,
'size': t.size,
'filename': t.filename,
})
return jsonify({'success': True, 'count': len(tracks), 'results': results})
except Exception as e:
logger.error(f"Deezer search test failed: {e}")
return jsonify({'success': False, 'error': str(e)})
@app.route('/api/deezer-download/test-download', methods=['POST'])
def deezer_download_test_download():
"""Test Deezer download of a single track (temporary testing endpoint)."""
try:
data = request.get_json() or {}
filename = data.get('filename', '')
if not filename:
return jsonify({'success': False, 'error': 'No filename provided (use track_id||display_name format)'})
arl = config_manager.get('deezer_download.arl', '')
if not arl:
return jsonify({'success': False, 'error': 'No ARL configured'})
from core.deezer_download_client import DeezerDownloadClient
client = DeezerDownloadClient()
if not client.is_authenticated():
client.reconnect(arl)
if not client.is_authenticated():
return jsonify({'success': False, 'error': 'Authentication failed'})
download_id = run_async(client.download('deezer_dl', filename))
if not download_id:
return jsonify({'success': False, 'error': 'Download failed to start'})
return jsonify({'success': True, 'download_id': download_id, 'message': 'Download started — check logs'})
except Exception as e:
logger.error(f"Deezer download test failed: {e}")
return jsonify({'success': False, 'error': str(e)})
# ===================================================================
# TIDAL DOWNLOAD AUTH ENDPOINTS
# ===================================================================