"""Single-track stream search — finds the best Soulseek result for a track play preview. Builds a small ordered list of search query variants (artist+title, artist+cleaned title; or title-only when the stream source is Soulseek itself) and walks them until one returns a usable match through the matching engine. Stream source resolution: - If `download_source.stream_source` is "youtube" (default), use the YouTube downloader for previews — instant, no auth pressure on the download stack. - If it's "active", mirror the user's download mode (tidal / qobuz / hifi / deezer_dl / lidarr) — but coerce Soulseek to YouTube because Soulseek is too slow for streaming previews. """ from __future__ import annotations import logging import re from typing import Callable, Optional logger = logging.getLogger(__name__) def _resolve_effective_stream_mode(config_manager) -> str: """Pick the streaming source based on settings.""" stream_source = config_manager.get('download_source.stream_source', 'youtube') download_mode = config_manager.get('download_source.mode', 'hybrid') if stream_source == 'youtube': return 'youtube' hybrid_order = config_manager.get('download_source.hybrid_order', ['hifi', 'youtube', 'soulseek']) hybrid_first = hybrid_order[0] if hybrid_order else config_manager.get('download_source.hybrid_primary', 'hifi') if download_mode == 'soulseek' or (download_mode == 'hybrid' and hybrid_first == 'soulseek'): logger.info("Stream source is 'active' but primary is Soulseek — falling back to YouTube") return 'youtube' if download_mode == 'hybrid': return hybrid_first return download_mode def _build_stream_queries(track_name: str, artist_name: str, effective_mode: str) -> list[str]: """Build an ordered, deduped list of search queries to try.""" queries: list[str] = [] is_streaming_source = effective_mode in ('youtube', 'tidal', 'qobuz', 'hifi', 'deezer_dl', 'lidarr') if is_streaming_source: if artist_name and track_name: queries.append(f"{artist_name} {track_name}".strip()) cleaned_name = re.sub(r'\s*\([^)]*\)', '', track_name).strip() cleaned_name = re.sub(r'\s*\[[^\]]*\]', '', cleaned_name).strip() if cleaned_name and cleaned_name.lower() != track_name.lower(): queries.append(f"{artist_name} {cleaned_name}".strip()) else: if track_name.strip(): queries.append(track_name.strip()) cleaned_name = re.sub(r'\s*\([^)]*\)', '', track_name).strip() cleaned_name = re.sub(r'\s*\[[^\]]*\]', '', cleaned_name).strip() if cleaned_name and cleaned_name.lower() != track_name.lower(): queries.append(cleaned_name.strip()) seen: set[str] = set() deduped: list[str] = [] for q in queries: if q and q.lower() not in seen: deduped.append(q) seen.add(q.lower()) return deduped def _result_to_dict(best_result) -> dict: return { "username": best_result.username, "filename": best_result.filename, "size": best_result.size, "bitrate": best_result.bitrate, "duration": best_result.duration, "quality": best_result.quality, "free_upload_slots": best_result.free_upload_slots, "upload_speed": best_result.upload_speed, "queue_length": best_result.queue_length, "result_type": "track", } def stream_search_track( *, track_name: str, artist_name: str, album_name: Optional[str], duration_ms: int, config_manager, download_orchestrator, matching_engine, run_async: Callable, ) -> Optional[dict]: """Find the best Soulseek/stream-source result for a single track. Returns the matched result dict on success, or `None` if no query variant produced a usable match. The route layer turns `None` into a 404 response. """ temp_track = type('TempTrack', (), { 'name': track_name, 'artists': [artist_name], 'album': album_name if album_name else None, 'duration_ms': duration_ms, })() effective_mode = _resolve_effective_stream_mode(config_manager) logger.info(f"Stream source effective mode: {effective_mode}") queries = _build_stream_queries(track_name, artist_name, effective_mode) # Map mode name to canonical registry name (legacy 'deezer_dl' # alias resolves to 'deezer' via the orchestrator's registry). stream_client = download_orchestrator.client(effective_mode) use_direct_client = stream_client is not None max_peer_queue = config_manager.get('soulseek.max_peer_queue', 0) or 0 for query_index, query in enumerate(queries): logger.info(f"Stream query {query_index + 1}/{len(queries)}: '{query}'") try: if use_direct_client: tracks_result, _ = run_async(stream_client.search(query, timeout=15)) else: tracks_result, _ = run_async(download_orchestrator.search(query, timeout=15)) if not tracks_result: logger.info(f"No results for query '{query}', trying next...") continue best_matches = matching_engine.find_best_slskd_matches_enhanced( temp_track, tracks_result, max_peer_queue=max_peer_queue ) if best_matches: best = best_matches[0] logger.info(f"Stream match for '{query}': {best.filename} ({best.quality})") return _result_to_dict(best) logger.info(f"No suitable matches for query '{query}', trying next...") except Exception as e: logger.warning(f"Stream search failed for query '{query}': {e}") continue logger.warning(f"No stream match found after {len(queries)} queries") return None