diff --git a/config/settings.py b/config/settings.py index c755e88a..1a6e69d2 100644 --- a/config/settings.py +++ b/config/settings.py @@ -380,6 +380,7 @@ class ConfigManager: "hybrid_primary": "soulseek", # Legacy: primary source for hybrid mode "hybrid_secondary": "youtube", # Legacy: fallback source for hybrid mode "hybrid_order": [], # Ordered list of sources for hybrid mode (overrides primary/secondary) + "stream_source": "youtube", # Options: "youtube" (instant, default), "active" (use download source; falls back to youtube if soulseek) }, "tidal_download": { "quality": "lossless", # Options: "low", "high", "lossless", "hires" diff --git a/entrypoint.sh b/entrypoint.sh index 1c9f1382..4845aff1 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -69,6 +69,10 @@ chown -R soulsync:soulsync /app/config /app/data /app/logs /app/downloads /app/T echo "✅ Configuration initialized successfully" +# Auto-update yt-dlp — YouTube changes their API frequently and stale versions break downloads +echo "🔄 Updating yt-dlp..." +pip install -U yt-dlp --quiet --no-cache-dir 2>/dev/null && echo " ✅ yt-dlp updated" || echo " ⚠️ yt-dlp update failed (will use existing version)" + # Display final user info echo "👤 Running as:" echo " User: $(id -u soulsync):$(id -g soulsync) ($(id -un soulsync):$(id -gn soulsync))" diff --git a/web_server.py b/web_server.py index 23c75ea2..dc517597 100644 --- a/web_server.py +++ b/web_server.py @@ -7234,18 +7234,34 @@ def stream_enhanced_search_track(): 'duration_ms': duration_ms })() - # Generate search queries based on download source mode - # - Soulseek: Track name only (avoids keyword filtering on artist names) - # - YouTube: Include artist name (provides context to find actual music) + # Determine effective stream source + # stream_source: "youtube" (default, instant) or "active" (use download source) + stream_source = config_manager.get('download_source.stream_source', 'youtube') download_mode = config_manager.get('download_source.mode', 'soulseek') + + # Resolve the effective mode for streaming + if stream_source == 'youtube': + effective_mode = 'youtube' + else: + # "active" mode — use download source, but fall back to YouTube if Soulseek + _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 == 'soulseek' or (download_mode == 'hybrid' and _hybrid_first == 'soulseek'): + effective_mode = 'youtube' # Soulseek is too slow for streaming preview + logger.info("▶️ Stream source is 'active' but primary is Soulseek — falling back to YouTube") + elif download_mode == 'hybrid': + effective_mode = _hybrid_first + else: + effective_mode = download_mode + + logger.info(f"▶️ Stream source: {stream_source} → effective: {effective_mode}") + + # Generate search queries based on effective stream mode search_queries = [] import re - _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', '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 effective_mode in ('youtube', 'tidal', 'qobuz', 'hifi', 'deezer_dl'): + # Streaming sources: Include artist for better context if artist_name and track_name: search_queries.append(f"{artist_name} {track_name}".strip()) @@ -7255,14 +7271,12 @@ def stream_enhanced_search_track(): if cleaned_name and cleaned_name.lower() != track_name.lower(): search_queries.append(f"{artist_name} {cleaned_name}".strip()) - logger.info(f"🔍 {download_mode.title()} mode: Searching with artist + track name: {search_queries}") + logger.info(f"🔍 {effective_mode.title()} stream: Searching with artist + track name: {search_queries}") else: # Soulseek mode: Track name only to avoid keyword filtering - # Primary query: Full track name if track_name.strip(): search_queries.append(track_name.strip()) - # Cleaned query: Remove parentheses and brackets cleaned_name = re.sub(r'\s*\([^)]*\)', '', track_name).strip() cleaned_name = re.sub(r'\s*\[[^\]]*\]', '', cleaned_name).strip() @@ -7281,13 +7295,27 @@ def stream_enhanced_search_track(): search_queries = unique_queries + # Select the search client based on effective stream mode + _stream_clients = { + 'youtube': soulseek_client.youtube, + 'tidal': soulseek_client.tidal, + 'qobuz': soulseek_client.qobuz, + 'hifi': soulseek_client.hifi, + 'deezer_dl': soulseek_client.deezer_dl, + } + stream_client = _stream_clients.get(effective_mode) + use_direct_client = stream_client is not None + # Try queries sequentially until we find a good match for query_index, query in enumerate(search_queries): logger.info(f"🔍 Query {query_index + 1}/{len(search_queries)}: '{query}'") try: - # Search slskd with timeout - tracks_result, _ = run_async(soulseek_client.search(query, timeout=15)) + # Search using the stream source client (not the download source) + if use_direct_client: + tracks_result, _ = run_async(stream_client.search(query, timeout=15)) + else: + tracks_result, _ = run_async(soulseek_client.search(query, timeout=15)) if tracks_result: logger.info(f"✅ Found {len(tracks_result)} results for query: '{query}'") diff --git a/webui/index.html b/webui/index.html index 22e4f9d5..e4439d6d 100644 --- a/webui/index.html +++ b/webui/index.html @@ -3991,6 +3991,18 @@ +