Integrate Deezer download source into orchestrator

Added deezer_dl to all orchestrator methods: init, is_configured,
get_source_status, check_connection, search (single + hybrid), download,
get_all_downloads, get_download_status, cancel_download, clear_completed,
cancel_all, and reload_settings. Full parity with existing 5 sources.
This commit is contained in:
Broque Thomas 2026-03-23 15:34:27 -07:00
parent 2ae5050ef1
commit 9c60040a9b

View file

@ -1,13 +1,14 @@
""" """
Download Orchestrator Download Orchestrator
Routes downloads between Soulseek, YouTube, Tidal, Qobuz, and HiFi based on configuration. Routes downloads between Soulseek, YouTube, Tidal, Qobuz, HiFi, and Deezer based on configuration.
Supports six modes: Supports seven modes:
- Soulseek Only: Traditional behavior - Soulseek Only: Traditional behavior
- YouTube Only: YouTube-exclusive downloads - YouTube Only: YouTube-exclusive downloads
- Tidal Only: Tidal-exclusive downloads - Tidal Only: Tidal-exclusive downloads
- Qobuz Only: Qobuz-exclusive downloads - Qobuz Only: Qobuz-exclusive downloads
- HiFi Only: Free lossless downloads via public hifi-api instances - HiFi Only: Free lossless downloads via public hifi-api instances
- Deezer Only: Deezer downloads via ARL authentication
- Hybrid: Try primary source first, fallback to others - Hybrid: Try primary source first, fallback to others
""" """
@ -22,6 +23,7 @@ from core.youtube_client import YouTubeClient
from core.tidal_download_client import TidalDownloadClient from core.tidal_download_client import TidalDownloadClient
from core.qobuz_client import QobuzClient from core.qobuz_client import QobuzClient
from core.hifi_client import HiFiClient from core.hifi_client import HiFiClient
from core.deezer_download_client import DeezerDownloadClient
logger = get_logger("download_orchestrator") logger = get_logger("download_orchestrator")
@ -41,6 +43,7 @@ class DownloadOrchestrator:
self.tidal = TidalDownloadClient() self.tidal = TidalDownloadClient()
self.qobuz = QobuzClient() self.qobuz = QobuzClient()
self.hifi = HiFiClient() self.hifi = HiFiClient()
self.deezer_dl = DeezerDownloadClient()
# Load mode from config # Load mode from config
self.mode = config_manager.get('download_source.mode', 'soulseek') self.mode = config_manager.get('download_source.mode', 'soulseek')
@ -66,6 +69,12 @@ class DownloadOrchestrator:
self.soulseek._setup_client() self.soulseek._setup_client()
logger.info(f"🔄 Soulseek client config reloaded") logger.info(f"🔄 Soulseek client config reloaded")
# Reconnect Deezer if ARL changed
deezer_arl = config_manager.get('deezer_download.arl', '')
if deezer_arl:
self.deezer_dl.reconnect(deezer_arl)
self.deezer_dl._quality = config_manager.get('deezer_download.quality', 'flac')
logger.info(f"🔄 Download Orchestrator settings reloaded - Mode: {self.mode}") logger.info(f"🔄 Download Orchestrator settings reloaded - Mode: {self.mode}")
def is_configured(self) -> bool: def is_configured(self) -> bool:
@ -84,8 +93,10 @@ class DownloadOrchestrator:
return self.qobuz.is_configured() return self.qobuz.is_configured()
elif self.mode == 'hifi': elif self.mode == 'hifi':
return self.hifi.is_configured() return self.hifi.is_configured()
elif self.mode == 'deezer_dl':
return self.deezer_dl.is_configured()
elif self.mode == 'hybrid': elif self.mode == 'hybrid':
clients = {'soulseek': self.soulseek, 'youtube': self.youtube, 'tidal': self.tidal, 'qobuz': self.qobuz, 'hifi': self.hifi} clients = {'soulseek': self.soulseek, 'youtube': self.youtube, 'tidal': self.tidal, 'qobuz': self.qobuz, 'hifi': self.hifi, 'deezer_dl': self.deezer_dl}
sources = self.hybrid_order if self.hybrid_order else [self.hybrid_primary, self.hybrid_secondary] sources = self.hybrid_order if self.hybrid_order else [self.hybrid_primary, self.hybrid_secondary]
return any(clients[s].is_configured() for s in sources if s in clients) return any(clients[s].is_configured() for s in sources if s in clients)
@ -99,6 +110,7 @@ class DownloadOrchestrator:
'tidal': self.tidal, 'tidal': self.tidal,
'qobuz': self.qobuz, 'qobuz': self.qobuz,
'hifi': self.hifi, 'hifi': self.hifi,
'deezer_dl': self.deezer_dl,
} }
return {name: client.is_configured() for name, client in clients.items()} return {name: client.is_configured() for name, client in clients.items()}
@ -118,16 +130,19 @@ class DownloadOrchestrator:
return await self.qobuz.check_connection() return await self.qobuz.check_connection()
elif self.mode == 'hifi': elif self.mode == 'hifi':
return await self.hifi.check_connection() return await self.hifi.check_connection()
elif self.mode == 'deezer_dl':
return await self.deezer_dl.check_connection()
elif self.mode == 'hybrid': elif self.mode == 'hybrid':
soulseek_ok = await self.soulseek.check_connection() soulseek_ok = await self.soulseek.check_connection()
youtube_ok = await self.youtube.check_connection() youtube_ok = await self.youtube.check_connection()
tidal_ok = await self.tidal.check_connection() tidal_ok = await self.tidal.check_connection()
qobuz_ok = await self.qobuz.check_connection() qobuz_ok = await self.qobuz.check_connection()
hifi_ok = await self.hifi.check_connection() hifi_ok = await self.hifi.check_connection()
deezer_ok = await self.deezer_dl.check_connection()
logger.info(f" Soulseek: {'' if soulseek_ok else ''} | YouTube: {'' if youtube_ok else ''} | Tidal: {'' if tidal_ok else ''} | Qobuz: {'' if qobuz_ok else ''} | HiFi: {'' if hifi_ok else ''}") logger.info(f" Soulseek: {'' if soulseek_ok else ''} | YouTube: {'' if youtube_ok else ''} | Tidal: {'' if tidal_ok else ''} | Qobuz: {'' if qobuz_ok else ''} | HiFi: {'' if hifi_ok else ''} | Deezer: {'' if deezer_ok else ''}")
return soulseek_ok or youtube_ok or tidal_ok or qobuz_ok or hifi_ok return soulseek_ok or youtube_ok or tidal_ok or qobuz_ok or hifi_ok or deezer_ok
return False return False
@ -163,6 +178,10 @@ class DownloadOrchestrator:
logger.info(f"🔍 Searching HiFi: {query}") logger.info(f"🔍 Searching HiFi: {query}")
return await self.hifi.search(query, timeout, progress_callback) return await self.hifi.search(query, timeout, progress_callback)
elif self.mode == 'deezer_dl':
logger.info(f"🔍 Searching Deezer: {query}")
return await self.deezer_dl.search(query, timeout, progress_callback)
elif self.mode == 'hybrid': elif self.mode == 'hybrid':
clients = { clients = {
'soulseek': self.soulseek, 'soulseek': self.soulseek,
@ -170,6 +189,7 @@ class DownloadOrchestrator:
'tidal': self.tidal, 'tidal': self.tidal,
'qobuz': self.qobuz, 'qobuz': self.qobuz,
'hifi': self.hifi, 'hifi': self.hifi,
'deezer_dl': self.deezer_dl,
} }
# Build ordered source list: prefer hybrid_order, fall back to legacy primary/secondary # Build ordered source list: prefer hybrid_order, fall back to legacy primary/secondary
@ -287,6 +307,9 @@ class DownloadOrchestrator:
elif username == 'hifi': elif username == 'hifi':
logger.info(f"📥 Downloading from HiFi: {filename}") logger.info(f"📥 Downloading from HiFi: {filename}")
return await self.hifi.download(username, filename, file_size) return await self.hifi.download(username, filename, file_size)
elif username == 'deezer_dl':
logger.info(f"📥 Downloading from Deezer: {filename}")
return await self.deezer_dl.download(username, filename, file_size)
else: else:
logger.info(f"📥 Downloading from Soulseek: {filename}") logger.info(f"📥 Downloading from Soulseek: {filename}")
return await self.soulseek.download(username, filename, file_size) return await self.soulseek.download(username, filename, file_size)
@ -304,8 +327,9 @@ class DownloadOrchestrator:
tidal_downloads = await self.tidal.get_all_downloads() tidal_downloads = await self.tidal.get_all_downloads()
qobuz_downloads = await self.qobuz.get_all_downloads() qobuz_downloads = await self.qobuz.get_all_downloads()
hifi_downloads = await self.hifi.get_all_downloads() hifi_downloads = await self.hifi.get_all_downloads()
deezer_downloads = await self.deezer_dl.get_all_downloads()
return soulseek_downloads + youtube_downloads + tidal_downloads + qobuz_downloads + hifi_downloads return soulseek_downloads + youtube_downloads + tidal_downloads + qobuz_downloads + hifi_downloads + deezer_downloads
async def get_download_status(self, download_id: str) -> Optional[DownloadStatus]: async def get_download_status(self, download_id: str) -> Optional[DownloadStatus]:
""" """
@ -342,6 +366,11 @@ class DownloadOrchestrator:
if status: if status:
return status return status
# Try Deezer
status = await self.deezer_dl.get_download_status(download_id)
if status:
return status
return None return None
async def cancel_download(self, download_id: str, username: str = None, remove: bool = False) -> bool: async def cancel_download(self, download_id: str, username: str = None, remove: bool = False) -> bool:
@ -365,6 +394,8 @@ class DownloadOrchestrator:
return await self.qobuz.cancel_download(download_id, username, remove) return await self.qobuz.cancel_download(download_id, username, remove)
elif username == 'hifi': elif username == 'hifi':
return await self.hifi.cancel_download(download_id, username, remove) return await self.hifi.cancel_download(download_id, username, remove)
elif username == 'deezer_dl':
return await self.deezer_dl.cancel_download(download_id, username, remove)
elif username: elif username:
return await self.soulseek.cancel_download(download_id, username, remove) return await self.soulseek.cancel_download(download_id, username, remove)
@ -386,7 +417,11 @@ class DownloadOrchestrator:
return True return True
hifi_cancelled = await self.hifi.cancel_download(download_id, username, remove) hifi_cancelled = await self.hifi.cancel_download(download_id, username, remove)
return hifi_cancelled if hifi_cancelled:
return True
deezer_cancelled = await self.deezer_dl.cancel_download(download_id, username, remove)
return deezer_cancelled
async def signal_download_completion(self, download_id: str, username: str, remove: bool = True) -> bool: async def signal_download_completion(self, download_id: str, username: str, remove: bool = True) -> bool:
""" """
@ -415,8 +450,9 @@ class DownloadOrchestrator:
tidal_cleared = await self.tidal.clear_all_completed_downloads() tidal_cleared = await self.tidal.clear_all_completed_downloads()
qobuz_cleared = await self.qobuz.clear_all_completed_downloads() qobuz_cleared = await self.qobuz.clear_all_completed_downloads()
hifi_cleared = await self.hifi.clear_all_completed_downloads() hifi_cleared = await self.hifi.clear_all_completed_downloads()
deezer_cleared = await self.deezer_dl.clear_all_completed_downloads()
return soulseek_cleared and youtube_cleared and tidal_cleared and qobuz_cleared and hifi_cleared return soulseek_cleared and youtube_cleared and tidal_cleared and qobuz_cleared and hifi_cleared and deezer_cleared
# ===== Soulseek-specific methods (for backwards compatibility) ===== # ===== Soulseek-specific methods (for backwards compatibility) =====
# These are internal methods that some parts of the codebase use directly # These are internal methods that some parts of the codebase use directly
@ -479,4 +515,5 @@ class DownloadOrchestrator:
await self.tidal.clear_all_completed_downloads() await self.tidal.clear_all_completed_downloads()
await self.qobuz.clear_all_completed_downloads() await self.qobuz.clear_all_completed_downloads()
await self.hifi.clear_all_completed_downloads() await self.hifi.clear_all_completed_downloads()
await self.deezer_dl.clear_all_completed_downloads()
return soulseek_ok return soulseek_ok