diff --git a/core/audiodb_worker.py b/core/audiodb_worker.py index cd58473d..cf4ef82e 100644 --- a/core/audiodb_worker.py +++ b/core/audiodb_worker.py @@ -129,6 +129,18 @@ class AudioDBWorker: continue self.current_item = item + # Guard: skip items with None/NULL IDs to prevent infinite enrichment loops + item_id = item.get('id') or item.get('artist_id') or item.get('album_id') + if item_id is None: + logger.warning(f"Skipping {item.get('type', 'unknown')} with NULL id: {item.get('name', '?')} — marking as error") + try: + itype = item.get('type', '') + table = 'artists' if 'artist' in itype else ('albums' if 'album' in itype else 'tracks') + # Can't mark status without an ID — just skip + except Exception: + pass + continue + self._process_item(item) @@ -151,7 +163,7 @@ class AudioDBWorker: cursor.execute(""" SELECT id, name FROM artists - WHERE audiodb_match_status IS NULL + WHERE audiodb_match_status IS NULL AND id IS NOT NULL ORDER BY id ASC LIMIT 1 """) @@ -164,7 +176,7 @@ class AudioDBWorker: SELECT a.id, a.title, ar.name AS artist_name, ar.audiodb_id AS artist_audiodb_id FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE a.audiodb_match_status IS NULL + WHERE a.audiodb_match_status IS NULL AND a.id IS NOT NULL ORDER BY a.id ASC LIMIT 1 """) @@ -177,7 +189,7 @@ class AudioDBWorker: SELECT t.id, t.title, ar.name AS artist_name, ar.audiodb_id AS artist_audiodb_id FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE t.audiodb_match_status IS NULL + WHERE t.audiodb_match_status IS NULL AND t.id IS NOT NULL ORDER BY t.id ASC LIMIT 1 """) diff --git a/core/deezer_worker.py b/core/deezer_worker.py index d0b9c4fd..8a4fe282 100644 --- a/core/deezer_worker.py +++ b/core/deezer_worker.py @@ -129,6 +129,18 @@ class DeezerWorker: continue self.current_item = item + # Guard: skip items with None/NULL IDs to prevent infinite enrichment loops + item_id = item.get('id') or item.get('artist_id') or item.get('album_id') + if item_id is None: + logger.warning(f"Skipping {item.get('type', 'unknown')} with NULL id: {item.get('name', '?')} — marking as error") + try: + itype = item.get('type', '') + table = 'artists' if 'artist' in itype else ('albums' if 'album' in itype else 'tracks') + # Can't mark status without an ID — just skip + except Exception: + pass + continue + self._process_item(item) @@ -151,7 +163,7 @@ class DeezerWorker: cursor.execute(""" SELECT id, name FROM artists - WHERE deezer_match_status IS NULL + WHERE deezer_match_status IS NULL AND id IS NOT NULL ORDER BY id ASC LIMIT 1 """) @@ -164,7 +176,7 @@ class DeezerWorker: SELECT a.id, a.title, ar.name AS artist_name, ar.deezer_id AS artist_deezer_id FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE a.deezer_match_status IS NULL + WHERE a.deezer_match_status IS NULL AND a.id IS NOT NULL ORDER BY a.id ASC LIMIT 1 """) @@ -177,7 +189,7 @@ class DeezerWorker: SELECT t.id, t.title, ar.name AS artist_name, ar.deezer_id AS artist_deezer_id FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE t.deezer_match_status IS NULL + WHERE t.deezer_match_status IS NULL AND t.id IS NOT NULL ORDER BY t.id ASC LIMIT 1 """) diff --git a/core/genius_worker.py b/core/genius_worker.py index 73782a98..caf16e26 100644 --- a/core/genius_worker.py +++ b/core/genius_worker.py @@ -143,6 +143,18 @@ class GeniusWorker: continue self.current_item = item + # Guard: skip items with None/NULL IDs to prevent infinite enrichment loops + item_id = item.get('id') or item.get('artist_id') or item.get('album_id') + if item_id is None: + logger.warning(f"Skipping {item.get('type', 'unknown')} with NULL id: {item.get('name', '?')} — marking as error") + try: + itype = item.get('type', '') + table = 'artists' if 'artist' in itype else ('albums' if 'album' in itype else 'tracks') + # Can't mark status without an ID — just skip + except Exception: + pass + continue + self._process_item(item) # Genius rate limiting is conservative (500ms per call) + lyrics scraping @@ -167,7 +179,7 @@ class GeniusWorker: cursor.execute(""" SELECT id, name FROM artists - WHERE genius_match_status IS NULL + WHERE genius_match_status IS NULL AND id IS NOT NULL ORDER BY id ASC LIMIT 1 """) @@ -180,7 +192,7 @@ class GeniusWorker: SELECT t.id, t.title, ar.name AS artist_name FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE t.genius_match_status IS NULL + WHERE t.genius_match_status IS NULL AND t.id IS NOT NULL ORDER BY t.id ASC LIMIT 1 """) diff --git a/core/itunes_worker.py b/core/itunes_worker.py index e1e72734..60342d46 100644 --- a/core/itunes_worker.py +++ b/core/itunes_worker.py @@ -130,6 +130,18 @@ class iTunesWorker: continue self.current_item = item + # Guard: skip items with None/NULL IDs to prevent infinite enrichment loops + item_id = item.get('id') or item.get('artist_id') or item.get('album_id') + if item_id is None: + logger.warning(f"Skipping {item.get('type', 'unknown')} with NULL id: {item.get('name', '?')} — marking as error") + try: + itype = item.get('type', '') + table = 'artists' if 'artist' in itype else ('albums' if 'album' in itype else 'tracks') + # Can't mark status without an ID — just skip + except Exception: + pass + continue + self._process_item(item) # Sleep depends on item type — search items need more delay @@ -158,7 +170,7 @@ class iTunesWorker: cursor.execute(""" SELECT id, name FROM artists - WHERE itunes_match_status IS NULL + WHERE itunes_match_status IS NULL AND id IS NOT NULL ORDER BY id ASC LIMIT 1 """) @@ -174,7 +186,7 @@ class iTunesWorker: AND ar.itunes_artist_id IS NOT NULL AND EXISTS ( SELECT 1 FROM albums al - WHERE al.artist_id = ar.id AND al.itunes_match_status IS NULL + WHERE al.artist_id = ar.id AND al.itunes_match_status IS NULL AND al.id IS NOT NULL ) ORDER BY ar.id ASC LIMIT 1 @@ -198,7 +210,7 @@ class iTunesWorker: AND al.itunes_album_id IS NOT NULL AND EXISTS ( SELECT 1 FROM tracks t - WHERE t.album_id = al.id AND t.itunes_match_status IS NULL + WHERE t.album_id = al.id AND t.itunes_match_status IS NULL AND t.id IS NOT NULL ) ORDER BY al.id ASC LIMIT 1 @@ -219,7 +231,7 @@ class iTunesWorker: SELECT a.id, a.title, ar.name AS artist_name FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE a.itunes_match_status IS NULL + WHERE a.itunes_match_status IS NULL AND a.id IS NOT NULL ORDER BY a.id ASC LIMIT 1 """) @@ -232,7 +244,7 @@ class iTunesWorker: SELECT t.id, t.title, ar.name AS artist_name FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE t.itunes_match_status IS NULL + WHERE t.itunes_match_status IS NULL AND t.id IS NOT NULL ORDER BY t.id ASC LIMIT 1 """) @@ -717,7 +729,7 @@ class iTunesWorker: cursor = conn.cursor() cursor.execute(""" SELECT id, title, track_number FROM tracks - WHERE album_id = ? AND itunes_match_status IS NULL + WHERE album_id = ? AND itunes_match_status IS NULL AND id IS NOT NULL ORDER BY id ASC """, (album_id,)) return [{'id': row[0], 'title': row[1], 'track_number': row[2]} for row in cursor.fetchall()] diff --git a/core/lastfm_worker.py b/core/lastfm_worker.py index 0c398136..d162cef3 100644 --- a/core/lastfm_worker.py +++ b/core/lastfm_worker.py @@ -143,6 +143,18 @@ class LastFMWorker: continue self.current_item = item + # Guard: skip items with None/NULL IDs to prevent infinite enrichment loops + item_id = item.get('id') or item.get('artist_id') or item.get('album_id') + if item_id is None: + logger.warning(f"Skipping {item.get('type', 'unknown')} with NULL id: {item.get('name', '?')} — marking as error") + try: + itype = item.get('type', '') + table = 'artists' if 'artist' in itype else ('albums' if 'album' in itype else 'tracks') + # Can't mark status without an ID — just skip + except Exception: + pass + continue + self._process_item(item) # Last.fm allows 5 req/sec but we use multiple calls per item @@ -165,7 +177,7 @@ class LastFMWorker: cursor.execute(""" SELECT id, name FROM artists - WHERE lastfm_match_status IS NULL + WHERE lastfm_match_status IS NULL AND id IS NOT NULL ORDER BY id ASC LIMIT 1 """) @@ -178,7 +190,7 @@ class LastFMWorker: SELECT a.id, a.title, ar.name AS artist_name FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE a.lastfm_match_status IS NULL + WHERE a.lastfm_match_status IS NULL AND a.id IS NOT NULL ORDER BY a.id ASC LIMIT 1 """) @@ -191,7 +203,7 @@ class LastFMWorker: SELECT t.id, t.title, ar.name AS artist_name FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE t.lastfm_match_status IS NULL + WHERE t.lastfm_match_status IS NULL AND t.id IS NOT NULL ORDER BY t.id ASC LIMIT 1 """) diff --git a/core/musicbrainz_worker.py b/core/musicbrainz_worker.py index dd4d53e0..3934c6f2 100644 --- a/core/musicbrainz_worker.py +++ b/core/musicbrainz_worker.py @@ -130,6 +130,18 @@ class MusicBrainzWorker: # Set current item for UI tracking self.current_item = item + # Guard: skip items with None/NULL IDs to prevent infinite enrichment loops + item_id = item.get('id') or item.get('artist_id') or item.get('album_id') + if item_id is None: + logger.warning(f"Skipping {item.get('type', 'unknown')} with NULL id: {item.get('name', '?')} — marking as error") + try: + itype = item.get('type', '') + table = 'artists' if 'artist' in itype else ('albums' if 'album' in itype else 'tracks') + # Can't mark status without an ID — just skip + except Exception: + pass + continue + # Process the item self._process_item(item) @@ -155,7 +167,7 @@ class MusicBrainzWorker: cursor.execute(""" SELECT id, name FROM artists - WHERE musicbrainz_match_status IS NULL + WHERE musicbrainz_match_status IS NULL AND id IS NOT NULL ORDER BY id ASC LIMIT 1 """) @@ -168,7 +180,7 @@ class MusicBrainzWorker: SELECT a.id, a.title, ar.name AS artist_name FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE a.musicbrainz_match_status IS NULL + WHERE a.musicbrainz_match_status IS NULL AND a.id IS NOT NULL ORDER BY a.id ASC LIMIT 1 """) @@ -181,7 +193,7 @@ class MusicBrainzWorker: SELECT t.id, t.title, ar.name AS artist_name FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE t.musicbrainz_match_status IS NULL + WHERE t.musicbrainz_match_status IS NULL AND t.id IS NOT NULL ORDER BY t.id ASC LIMIT 1 """) diff --git a/core/qobuz_worker.py b/core/qobuz_worker.py index fc227c76..59abbac6 100644 --- a/core/qobuz_worker.py +++ b/core/qobuz_worker.py @@ -151,6 +151,18 @@ class QobuzWorker: continue self.current_item = item + # Guard: skip items with None/NULL IDs to prevent infinite enrichment loops + item_id = item.get('id') or item.get('artist_id') or item.get('album_id') + if item_id is None: + logger.warning(f"Skipping {item.get('type', 'unknown')} with NULL id: {item.get('name', '?')} — marking as error") + try: + itype = item.get('type', '') + table = 'artists' if 'artist' in itype else ('albums' if 'album' in itype else 'tracks') + # Can't mark status without an ID — just skip + except Exception: + pass + continue + self._process_item(item) @@ -174,7 +186,7 @@ class QobuzWorker: cursor.execute(""" SELECT id, name FROM artists - WHERE qobuz_match_status IS NULL + WHERE qobuz_match_status IS NULL AND id IS NOT NULL ORDER BY id ASC LIMIT 1 """) @@ -187,7 +199,7 @@ class QobuzWorker: SELECT a.id, a.title, ar.name AS artist_name, ar.qobuz_id AS artist_qobuz_id FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE a.qobuz_match_status IS NULL + WHERE a.qobuz_match_status IS NULL AND a.id IS NOT NULL ORDER BY a.id ASC LIMIT 1 """) @@ -200,7 +212,7 @@ class QobuzWorker: SELECT t.id, t.title, ar.name AS artist_name, ar.qobuz_id AS artist_qobuz_id FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE t.qobuz_match_status IS NULL + WHERE t.qobuz_match_status IS NULL AND t.id IS NOT NULL ORDER BY t.id ASC LIMIT 1 """) diff --git a/core/spotify_worker.py b/core/spotify_worker.py index 5074e73e..8e1d52ed 100644 --- a/core/spotify_worker.py +++ b/core/spotify_worker.py @@ -168,6 +168,18 @@ class SpotifyWorker: continue self.current_item = item + # Guard: skip items with None/NULL IDs to prevent infinite enrichment loops + item_id = item.get('id') or item.get('artist_id') or item.get('album_id') + if item_id is None: + logger.warning(f"Skipping {item.get('type', 'unknown')} with NULL id: {item.get('name', '?')} — marking as error") + try: + itype = item.get('type', '') + table = 'artists' if 'artist' in itype else ('albums' if 'album' in itype else 'tracks') + # Can't mark status without an ID — just skip + except Exception: + pass + continue + self._process_item(item) time.sleep(self.inter_item_sleep) @@ -193,7 +205,7 @@ class SpotifyWorker: cursor.execute(""" SELECT id, name FROM artists - WHERE spotify_match_status IS NULL + WHERE spotify_match_status IS NULL AND id IS NOT NULL ORDER BY id ASC LIMIT 1 """) @@ -209,7 +221,7 @@ class SpotifyWorker: AND ar.spotify_artist_id IS NOT NULL AND EXISTS ( SELECT 1 FROM albums al - WHERE al.artist_id = ar.id AND al.spotify_match_status IS NULL + WHERE al.artist_id = ar.id AND al.spotify_match_status IS NULL AND al.id IS NOT NULL ) ORDER BY ar.id ASC LIMIT 1 @@ -233,7 +245,7 @@ class SpotifyWorker: AND al.spotify_album_id IS NOT NULL AND EXISTS ( SELECT 1 FROM tracks t - WHERE t.album_id = al.id AND t.spotify_match_status IS NULL + WHERE t.album_id = al.id AND t.spotify_match_status IS NULL AND t.id IS NOT NULL ) ORDER BY al.id ASC LIMIT 1 @@ -254,7 +266,7 @@ class SpotifyWorker: SELECT a.id, a.title, ar.name AS artist_name FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE a.spotify_match_status IS NULL + WHERE a.spotify_match_status IS NULL AND a.id IS NOT NULL ORDER BY a.id ASC LIMIT 1 """) @@ -267,7 +279,7 @@ class SpotifyWorker: SELECT t.id, t.title, ar.name AS artist_name FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE t.spotify_match_status IS NULL + WHERE t.spotify_match_status IS NULL AND t.id IS NOT NULL ORDER BY t.id ASC LIMIT 1 """) @@ -758,7 +770,7 @@ class SpotifyWorker: cursor = conn.cursor() cursor.execute(""" SELECT id, title, track_number FROM tracks - WHERE album_id = ? AND spotify_match_status IS NULL + WHERE album_id = ? AND spotify_match_status IS NULL AND id IS NOT NULL ORDER BY id ASC """, (album_id,)) return [{'id': row[0], 'title': row[1], 'track_number': row[2]} for row in cursor.fetchall()] diff --git a/core/tidal_worker.py b/core/tidal_worker.py index 27f5a78d..aaf57053 100644 --- a/core/tidal_worker.py +++ b/core/tidal_worker.py @@ -164,6 +164,18 @@ class TidalWorker: continue self.current_item = item + # Guard: skip items with None/NULL IDs to prevent infinite enrichment loops + item_id = item.get('id') or item.get('artist_id') or item.get('album_id') + if item_id is None: + logger.warning(f"Skipping {item.get('type', 'unknown')} with NULL id: {item.get('name', '?')} — marking as error") + try: + itype = item.get('type', '') + table = 'artists' if 'artist' in itype else ('albums' if 'album' in itype else 'tracks') + # Can't mark status without an ID — just skip + except Exception: + pass + continue + self._process_item(item) @@ -186,7 +198,7 @@ class TidalWorker: cursor.execute(""" SELECT id, name FROM artists - WHERE tidal_match_status IS NULL + WHERE tidal_match_status IS NULL AND id IS NOT NULL ORDER BY id ASC LIMIT 1 """) @@ -199,7 +211,7 @@ class TidalWorker: SELECT a.id, a.title, ar.name AS artist_name, ar.tidal_id AS artist_tidal_id FROM albums a JOIN artists ar ON a.artist_id = ar.id - WHERE a.tidal_match_status IS NULL + WHERE a.tidal_match_status IS NULL AND a.id IS NOT NULL ORDER BY a.id ASC LIMIT 1 """) @@ -212,7 +224,7 @@ class TidalWorker: SELECT t.id, t.title, ar.name AS artist_name, ar.tidal_id AS artist_tidal_id FROM tracks t JOIN artists ar ON t.artist_id = ar.id - WHERE t.tidal_match_status IS NULL + WHERE t.tidal_match_status IS NULL AND t.id IS NOT NULL ORDER BY t.id ASC LIMIT 1 """) diff --git a/web_server.py b/web_server.py index caf31528..32ea2c24 100644 --- a/web_server.py +++ b/web_server.py @@ -15364,7 +15364,7 @@ def _embed_source_ids(audio_file, metadata: dict): print(f"🎵 Deezer track matched: {dz_track_id}") # Get full track details for BPM and ISRC - dz_details = dz_client.get_track(dz_track_id) + dz_details = dz_client.get_track_details(dz_track_id) if dz_details: bpm_val = dz_details.get('bpm') if bpm_val and bpm_val > 0: