diff --git a/database/music_database.py b/database/music_database.py index 73f39bfb..ac622c68 100644 --- a/database/music_database.py +++ b/database/music_database.py @@ -4706,18 +4706,26 @@ class MusicDatabase: logger.error(f"Error getting tracks for album {album_id}: {e}") return [] - def search_artists(self, query: str, limit: int = 50) -> List[DatabaseArtist]: - """Search artists by name""" + def search_artists(self, query: str, limit: int = 50, server_source: str = None) -> List[DatabaseArtist]: + """Search artists by name, optionally filtered by server source.""" try: conn = self._get_connection() cursor = conn.cursor() - - cursor.execute(""" - SELECT * FROM artists - WHERE name LIKE ? - ORDER BY name - LIMIT ? - """, (f"%{query}%", limit)) + + if server_source: + cursor.execute(""" + SELECT * FROM artists + WHERE name LIKE ? AND server_source = ? + ORDER BY name + LIMIT ? + """, (f"%{query}%", server_source, limit)) + else: + cursor.execute(""" + SELECT * FROM artists + WHERE name LIKE ? + ORDER BY name + LIMIT ? + """, (f"%{query}%", limit)) rows = cursor.fetchall() diff --git a/web_server.py b/web_server.py index 7f6daf78..a25ebbd5 100644 --- a/web_server.py +++ b/web_server.py @@ -7658,7 +7658,8 @@ def enhanced_search(): try: # Search local database for artists (always) database = get_database() - db_artists_objs = database.search_artists(query, limit=5) + active_server = config_manager.get_active_media_server() + db_artists_objs = database.search_artists(query, limit=5, server_source=active_server) db_artists = [] for artist in db_artists_objs: @@ -14217,10 +14218,37 @@ def sync_artist_library(artist_id): """, (db_artist_id,)) tracks = cursor.fetchall() - # Get artist name for logging - cursor.execute("SELECT name FROM artists WHERE id = ?", (db_artist_id,)) + # Get current artist info + cursor.execute("SELECT name, server_source FROM artists WHERE id = ?", (db_artist_id,)) artist_row = cursor.fetchone() artist_name = artist_row['name'] if artist_row else f'ID {db_artist_id}' + server_source = artist_row['server_source'] if artist_row else None + + # Re-fetch artist name from media server (catches renames in Plex/Jellyfin/Navidrome) + name_updated = False + if server_source: + try: + server_artist = None + if server_source == 'plex' and plex_client and plex_client.server: + try: + server_artist = plex_client.server.fetchItem(int(db_artist_id)) + except Exception: + pass + elif server_source in ('jellyfin', 'navidrome'): + media_client = {'jellyfin': jellyfin_client, 'navidrome': navidrome_client}.get(server_source) + if media_client and hasattr(media_client, 'get_artist_by_id'): + server_artist = media_client.get_artist_by_id(str(db_artist_id)) + + if server_artist and hasattr(server_artist, 'title') and server_artist.title: + new_name = server_artist.title + if new_name != artist_name: + cursor.execute("UPDATE artists SET name = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?", + (new_name, db_artist_id)) + print(f"🔄 [Artist Sync] Name updated: '{artist_name}' → '{new_name}'") + artist_name = new_name + name_updated = True + except Exception as e: + print(f"⚠️ [Artist Sync] Could not refresh name from {server_source}: {e}") stale_tracks = [] valid_tracks = 0 @@ -14264,6 +14292,7 @@ def sync_artist_library(artist_id): return jsonify({ "success": True, "artist_name": artist_name, + "name_updated": name_updated, "valid_tracks": valid_tracks, "stale_removed": len(stale_tracks), "empty_albums_removed": empty_albums_removed diff --git a/webui/static/script.js b/webui/static/script.js index 25dfdc68..da61f242 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -17317,7 +17317,7 @@ function _gsRender(data) { h += tracks.map(t => { const ar = t.artist || (t.artists ? t.artists.join(', ') : ''); const dur = t.duration_ms ? `${Math.floor(t.duration_ms / 60000)}:${String(Math.floor((t.duration_ms % 60000) / 1000)).padStart(2, '0')}` : ''; - return `
${t.image_url ? `` : '🎵'}
${_escToast(t.name)}
${_escToast(ar)}${t.album ? ` · ${_escToast(t.album)}` : ''}
${dur}
`; + return `
${t.image_url ? `` : '🎵'}
${_escToast(t.name)}
${_escToast(ar)}${t.album ? ` · ${_escToast(t.album)}` : ''}
${dur}
`; }).join(''); h += ''; } else if (loading.has('tracks')) { @@ -17428,13 +17428,43 @@ async function _gsClickAlbum(albumId, albumName, artistName, imageUrl, source) { } } -function _gsClickTrack(artistName, trackName) { +async function _gsClickTrack(artistName, trackName, albumName, trackId, imageUrl, durationMs) { _gsDeactivate(); - navigateToPage('downloads'); - setTimeout(() => { - const input = document.getElementById('enhanced-search-input'); - if (input) { input.value = `${artistName} ${trackName}`.trim(); input.dispatchEvent(new Event('input')); } - }, 300); + + // Build enriched track + open download modal directly (same as enhanced search) + const virtualPlaylistId = `gsearch_track_${trackId || (artistName + '_' + trackName).replace(/\s/g, '_')}`; + const enrichedTrack = { + id: trackId || '', + name: trackName, + artists: [artistName], + album: { name: albumName || '', id: null, album_type: 'single', images: imageUrl ? [{ url: imageUrl }] : [], total_tracks: 1 }, + duration_ms: durationMs || 0, + image_url: imageUrl || '', + }; + const albumObject = { + name: albumName || '', id: null, album_type: 'single', + images: imageUrl ? [{ url: imageUrl }] : [], + artists: [{ name: artistName }], total_tracks: 1, + }; + const artistObject = { id: null, name: artistName }; + const playlistName = `${artistName} - ${trackName}`; + + try { + showLoadingOverlay('Loading track...'); + await openDownloadMissingModalForArtistAlbum( + virtualPlaylistId, playlistName, [enrichedTrack], albumObject, artistObject, false + ); + } catch (e) { + console.error('Error opening track download:', e); + // Fallback: navigate to enhanced search + navigateToPage('downloads'); + setTimeout(() => { + const input = document.getElementById('enhanced-search-input'); + if (input) { input.value = `${artistName} ${trackName}`.trim(); input.dispatchEvent(new Event('input')); } + }, 300); + } finally { + hideLoadingOverlay(); + } } async function _gsPlayTrack(trackName, artistName, albumName) {