Make Spotify a selectable metadata source instead of auto-override

Spotify was automatically the primary metadata source whenever
authenticated, ignoring the user's fallback dropdown selection. Now:

- Dropdown renamed "Fallback Source" → "Primary Source" with Spotify
  added as an option alongside iTunes, Deezer, Discogs
- User's selection drives search results, discovery page, status display
- Enhanced search uses selected primary source first, others as tabs
- Discovery page filters by selected source's artist IDs
- Spotify auth still works for playlists, followed artists, enrichment
- Default is 'deezer' — users who want Spotify select it explicitly
pull/289/head
Broque Thomas 1 month ago
parent db5bdb9c59
commit 3c211eaac8

@ -4424,13 +4424,19 @@ def get_status():
if is_rate_limited or cooldown_remaining > 0:
# During rate limit or post-ban cooldown, skip the auth probe entirely.
# Probing Spotify here would reset the rate limit timer.
music_source = _get_metadata_fallback_source() # App uses fallback during ban
music_source = _get_metadata_fallback_source()
if music_source == 'spotify':
music_source = 'deezer' # Spotify rate limited — can't use it
spotify_response_time = 0
else:
spotify_start = time.time()
spotify_connected = spotify_client.is_spotify_authenticated() if spotify_client else False
spotify_response_time = (time.time() - spotify_start) * 1000
music_source = 'spotify' if spotify_connected else _get_metadata_fallback_source()
# Use whatever the user configured as primary metadata source
music_source = _get_metadata_fallback_source()
# If user selected spotify but it's not authed, fall back
if music_source == 'spotify' and not spotify_connected:
music_source = 'deezer'
_status_cache['spotify'] = {
'connected': True, # Always true — iTunes fallback is always available
@ -5981,7 +5987,7 @@ def test_connection_endpoint():
current_time = time.time()
if service == 'spotify':
_status_cache['spotify']['connected'] = True
_status_cache['spotify']['source'] = 'spotify' if (spotify_client and spotify_client.is_spotify_authenticated()) else _get_metadata_fallback_source()
_status_cache['spotify']['source'] = _get_metadata_fallback_source()
_status_cache_timestamps['spotify'] = current_time
print("✅ Updated Spotify status cache after successful test")
elif service in ['plex', 'jellyfin', 'navidrome']:
@ -6031,7 +6037,7 @@ def test_dashboard_connection_endpoint():
current_time = time.time()
if service == 'spotify':
_status_cache['spotify']['connected'] = True
_status_cache['spotify']['source'] = 'spotify' if (spotify_client and spotify_client.is_spotify_authenticated()) else _get_metadata_fallback_source()
_status_cache['spotify']['source'] = _get_metadata_fallback_source()
_status_cache_timestamps['spotify'] = current_time
print("✅ Updated Spotify status cache after successful dashboard test")
elif service in ['plex', 'jellyfin', 'navidrome']:
@ -7698,23 +7704,22 @@ def enhanced_search():
hydrabase_worker.enqueue(query, 'albums')
hydrabase_worker.enqueue(query, 'artists')
# Search primary source synchronously — use is_spotify_authenticated()
# (is_authenticated() always returns True because fallback is always available)
if spotify_client and spotify_client.is_spotify_authenticated():
try:
primary_results = _enhanced_search_source(query, spotify_client)
primary_source = "spotify"
except Exception as e:
logger.debug(f"Spotify search failed: {e}")
# Search using the user's configured primary metadata source
fb_source = _get_metadata_fallback_source()
try:
primary_results = _enhanced_search_source(query, _get_metadata_fallback_client())
primary_source = fb_source
except Exception as e:
logger.debug(f"Primary source ({fb_source}) search failed: {e}")
# If Spotify unavailable, use configured fallback as primary
if primary_results is empty_source:
fb_source = _get_metadata_fallback_source()
try:
primary_results = _enhanced_search_source(query, _get_metadata_fallback_client())
primary_source = fb_source
except Exception as e:
logger.debug(f"Fallback ({fb_source}) search failed: {e}")
# If primary source failed and it wasn't Spotify, try Spotify as fallback
if primary_results is empty_source and fb_source != 'spotify':
if spotify_client and spotify_client.is_spotify_authenticated():
try:
primary_results = _enhanced_search_source(query, spotify_client)
primary_source = "spotify"
except Exception as e:
logger.debug(f"Spotify fallback search failed: {e}")
# Determine which alternate sources are available (for frontend to fetch async)
spotify_available = bool(spotify_client and spotify_client.is_spotify_authenticated())
@ -32748,16 +32753,22 @@ def _get_deezer_client():
return _deezer_client_instance
def _get_metadata_fallback_source():
"""Get the configured metadata fallback source ('itunes', 'deezer', 'discogs', or 'hydrabase')."""
"""Get the configured primary metadata source.
Returns 'spotify', 'itunes', 'deezer', 'discogs', or 'hydrabase'."""
try:
return config_manager.get('metadata.fallback_source', 'deezer') or 'deezer'
except Exception:
return 'itunes'
return 'deezer'
def _get_metadata_fallback_client():
"""Get the active metadata fallback client based on settings.
Returns an iTunesClient, DeezerClient, DiscogsClient, or HydrabaseClient instance with identical interfaces."""
"""Get the active metadata client based on settings.
Returns a SpotifyClient, iTunesClient, DeezerClient, DiscogsClient, or HydrabaseClient instance."""
source = _get_metadata_fallback_source()
if source == 'spotify':
if spotify_client and spotify_client.is_spotify_authenticated():
return spotify_client
# Spotify selected but not authed — fall back to deezer
return _get_deezer_client()
if source == 'deezer':
return _get_deezer_client()
if source == 'discogs':
@ -32765,7 +32776,6 @@ def _get_metadata_fallback_client():
if token:
from core.discogs_client import DiscogsClient
return DiscogsClient(token=token)
# No token — fall back to iTunes
from core.itunes_client import iTunesClient
return iTunesClient()
if source == 'hydrabase':
@ -39499,11 +39509,13 @@ metadata_update_executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix=
def _get_active_discovery_source():
"""
Determine which music source is active for discovery.
Returns 'spotify' if Spotify is authenticated, otherwise the configured fallback.
Returns the user's configured primary metadata source.
If the selected source requires auth and isn't available, falls back.
"""
if spotify_client and spotify_client.is_spotify_authenticated():
return 'spotify'
return _get_metadata_fallback_source()
source = _get_metadata_fallback_source()
if source == 'spotify' and not (spotify_client and spotify_client.is_spotify_authenticated()):
return 'deezer'
return source
@app.route('/api/discover/hero', methods=['GET'])

@ -4235,15 +4235,16 @@
<div class="api-service-frame">
<h4 class="service-title" style="color: #e8e8e8;">Metadata Source</h4>
<div class="form-group">
<label>Fallback Source:</label>
<label>Primary Source:</label>
<select id="metadata-fallback-source">
<option value="spotify">Spotify</option>
<option value="itunes">iTunes / Apple Music</option>
<option value="deezer">Deezer</option>
<option value="discogs">Discogs</option>
</select>
</div>
<div class="callback-info">
<div class="callback-help">When Spotify is not connected, this source provides artist, album, and track metadata. Discogs requires a personal token configured above.</div>
<div class="callback-help">The primary source for artist, album, and track metadata. Spotify requires authentication above. Discogs requires a personal token.</div>
</div>
</div>

Loading…
Cancel
Save