From 800727299f3658197561fc522852ad7e79362cf1 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Wed, 1 Apr 2026 22:56:06 -0700 Subject: [PATCH] =?UTF-8?q?Fix=20library=20playback=20from=20search=20?= =?UTF-8?q?=E2=80=94=20album=20art,=20double-fire,=20path=20resolution?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Strip cloned inline onclick on global search play button swap to prevent simultaneous stream search + library play - Include album thumb_url in library-check response and resolve relative Plex paths to full URLs with base URL + token - Pass album art through to playLibraryTrack from both global and enhanced search library check handlers - Add Plex music library locations as candidate dirs in _resolve_library_file_path - Remove debug console.log from _gsDeactivate --- web_server.py | 34 ++++++++++++++++++++++++++++++---- webui/static/script.js | 6 +++--- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/web_server.py b/web_server.py index ee1502e0..8a6bed22 100644 --- a/web_server.py +++ b/web_server.py @@ -7404,13 +7404,13 @@ def enhanced_search_library_check(): owned_albums = {r[0] for r in cursor.fetchall()} cursor.execute(""" - SELECT LOWER(t.title) || '|||' || LOWER(a.name), t.id, t.file_path, t.title, a.name, al.title + SELECT LOWER(t.title) || '|||' || LOWER(a.name), t.id, t.file_path, t.title, a.name, al.title, al.thumb_url FROM tracks t JOIN artists a ON a.id = t.artist_id JOIN albums al ON al.id = t.album_id """) owned_tracks = {} for r in cursor.fetchall(): if r[0] not in owned_tracks: # Keep first match only - owned_tracks[r[0]] = {'track_id': r[1], 'file_path': r[2], 'title': r[3], 'artist_name': r[4], 'album_title': r[5]} + owned_tracks[r[0]] = {'track_id': r[1], 'file_path': r[2], 'title': r[3], 'artist_name': r[4], 'album_title': r[5], 'album_thumb_url': r[6]} # O(1) lookups per item album_results = [] @@ -7418,11 +7418,27 @@ def enhanced_search_library_check(): key = (a.get('name', '').lower() + '|||' + a.get('artist', '').split(',')[0].strip().lower()) album_results.append(key in owned_albums) + # Resolve Plex thumb URLs (relative paths need base URL + token) + _plex_base = '' + _plex_token = '' + if plex_client and plex_client.server: + _plex_base = getattr(plex_client.server, '_baseurl', '') or '' + _plex_token = getattr(plex_client.server, '_token', '') or '' + if not _plex_base: + _pc = config_manager.get_plex_config() + _plex_base = (_pc.get('base_url', '') or '').rstrip('/') + _plex_token = _plex_token or _pc.get('token', '') + track_results = [] for t in tracks: key = (t.get('name', '').lower() + '|||' + t.get('artist', '').split(',')[0].strip().lower()) match = owned_tracks.get(key) if match: + # Resolve thumb URL + thumb = match.get('album_thumb_url') or '' + if thumb and not thumb.startswith('http') and _plex_base and thumb.startswith('/'): + thumb = f"{_plex_base}{thumb}?X-Plex-Token={_plex_token}" if _plex_token else f"{_plex_base}{thumb}" + match['album_thumb_url'] = thumb track_results.append({'in_library': True, **match}) else: track_results.append({'in_library': False}) @@ -12234,14 +12250,24 @@ def _resolve_library_file_path(file_path): return file_path # Try resolving server-side paths to local directories - # Check both transfer path (final music location) and download path (intermediate) + # Check transfer path, download path, and media server library paths transfer_dir = docker_resolve_path(config_manager.get('soulseek.transfer_path', './Transfer')) download_dir = docker_resolve_path(config_manager.get('soulseek.download_path', './downloads')) + + # Also check the media server's music library path (handles Docker↔host path mismatch) + library_dirs = set() + try: + if plex_client and plex_client.server and plex_client.music_library: + for loc in plex_client.music_library.locations: + library_dirs.add(loc) + except Exception: + pass + path_parts = file_path.replace('\\', '/').split('/') # Try progressively shorter path suffixes against each candidate directory # (skip index 0 to avoid drive letter issues) - for base_dir in [transfer_dir, download_dir]: + for base_dir in [transfer_dir, download_dir] + list(library_dirs): if not base_dir or not os.path.isdir(base_dir): continue for i in range(1, len(path_parts)): diff --git a/webui/static/script.js b/webui/static/script.js index e306f58c..1f938b00 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -8269,7 +8269,7 @@ function initializeSearchModeToggle() { newBtn.addEventListener('click', (e) => { e.stopPropagation(); playLibraryTrack( - { id: trackInfo.track_id, title: trackInfo.title, file_path: trackInfo.file_path }, + { id: trackInfo.track_id, title: trackInfo.title, file_path: trackInfo.file_path, _stats_image: trackInfo.album_thumb_url || null }, trackInfo.album_title || '', trackInfo.artist_name || '' ); @@ -16921,7 +16921,6 @@ function _gsUpdateVisibility() { } function _gsDeactivate() { - console.log('[GSearch] _gsDeactivate called', new Error().stack.split('\n')[2]?.trim()); const bar = document.getElementById('gsearch-bar'); const shortcut = document.getElementById('gsearch-shortcut'); if (bar) bar.classList.remove('active'); @@ -17243,13 +17242,14 @@ async function _gsLibraryCheck() { const playBtn = el.querySelector('.gsearch-play-btn'); if (playBtn) { const newBtn = playBtn.cloneNode(true); + newBtn.removeAttribute('onclick'); newBtn.title = 'Play from library'; newBtn.style.background = 'rgba(76,175,80,0.15)'; newBtn.style.color = '#4caf50'; newBtn.addEventListener('click', e => { e.stopPropagation(); playLibraryTrack( - { id: tr.track_id, title: tr.title, file_path: tr.file_path }, + { id: tr.track_id, title: tr.title, file_path: tr.file_path, _stats_image: tr.album_thumb_url || null }, tr.album_title || '', tr.artist_name || '' );