diff --git a/core/spotify_worker.py b/core/spotify_worker.py index dde78781..a3e9cd89 100644 --- a/core/spotify_worker.py +++ b/core/spotify_worker.py @@ -55,8 +55,13 @@ class SpotifyWorker: self.inter_item_sleep = 1.5 # Between top-level items (each can trigger 5+ paginated calls) self.batch_inter_item_sleep = 0.1 # Between local matches within a batch (no API calls) - # Daily budget — caps how many items this worker processes per calendar day - self.daily_budget = 3000 + # Daily budget — caps how many items this worker processes per calendar day. + # Lowered from 3000 to 500 after Spotify's February 2026 API tightening + # (/v1/search max limit cut from 50 to 10) increased the per-track API call + # cost. Sustained 3000-item runs were tripping Spotify's automated abuse + # detection and earning multi-hour 429 bans. 500/day keeps the worker + # productive without crossing the threshold. + self.daily_budget = 500 self._daily_items_processed = 0 self._daily_date = date.today() diff --git a/web_server.py b/web_server.py index 4364b4b5..fc195926 100644 --- a/web_server.py +++ b/web_server.py @@ -37103,18 +37103,33 @@ def deezer_resume(): # ================================================================================================ # --- Spotify Worker Initialization --- +# The Spotify enrichment worker calls `/v1/search` continuously to match library +# tracks against Spotify's catalog. After Spotify's February 2026 API tightening +# (search limit cut from 50→10, sustained-rate detection more aggressive), running +# this worker when the user has chosen a non-Spotify primary metadata source +# (Deezer, iTunes, Discogs, Hydrabase) generates dead API traffic that triggers +# multi-hour 429 bans and disrupts the user's actual selected source. +# +# Gate the worker at boot: only auto-start when Spotify is the configured primary +# source. Users on other sources can manually unpause the worker from settings if +# they explicitly want background Spotify enrichment. spotify_enrichment_worker = None try: + from core.metadata_service import get_primary_source as _get_primary_source from database.music_database import MusicDatabase spotify_enrichment_db = MusicDatabase() spotify_enrichment_worker = SpotifyWorker(database=spotify_enrichment_db) - if config_manager.get('spotify_enrichment_paused', False): + _primary = _get_primary_source() + _user_paused = config_manager.get('spotify_enrichment_paused', False) + if _user_paused or _primary != 'spotify': spotify_enrichment_worker.paused = True # Set BEFORE start() to prevent race condition spotify_enrichment_worker.start() - if spotify_enrichment_worker.paused: + if not spotify_enrichment_worker.paused: + logger.info("Spotify enrichment worker initialized and started") + elif _user_paused: logger.info("Spotify enrichment worker initialized (paused — restored from config)") else: - logger.info("Spotify enrichment worker initialized and started") + logger.info(f"Spotify enrichment worker initialized (paused — primary metadata source is '{_primary}', not Spotify)") except Exception as e: logger.error(f"Spotify enrichment worker initialization failed: {e}") spotify_enrichment_worker = None