From 3f04d7f9840e7c2de3aca22f3c84ca86f525c065 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Thu, 24 Jul 2025 16:10:07 -0700 Subject: [PATCH] better --- core/plex_client.py | 26 +++++++++-------- ui/pages/sync.py | 69 ++++++++++++++++++++++++++++++++++++--------- 2 files changed, 71 insertions(+), 24 deletions(-) diff --git a/core/plex_client.py b/core/plex_client.py index c84035db..dbc25383 100644 --- a/core/plex_client.py +++ b/core/plex_client.py @@ -248,48 +248,52 @@ class PlexClient: logger.error(f"Error searching for track '{title}' by '{artist}': {e}") return None - def search_tracks(self, title: str, artist: str, limit: int = 10) -> List[PlexTrackInfo]: + def search_tracks(self, title: str, artist: str, limit: int = 15) -> List[PlexTrackInfo]: """ Searches for tracks in the Plex music library using a more robust, two-step method that is more compatible with different Plex server versions. """ if not self.music_library: + logger.warning("Plex music library not found. Cannot perform search.") return [] try: # Step 1: Search for the artist first. This is generally reliable. artist_results = self.music_library.searchArtists(title=artist, limit=1) - results = [] + candidate_tracks = [] if artist_results: # If artist is found, get all their tracks and filter by title in Python. - # This avoids the API filter issue entirely. + # This avoids potential API filter issues where special characters in the title + # might cause the search to fail. plex_artist = artist_results[0] all_artist_tracks = plex_artist.tracks() # Use a case-insensitive substring match to find potential tracks. - # The matching engine will do the final, more precise comparison. + # The matching engine will do the final, more precise comparison later. lower_title = title.lower() for track in all_artist_tracks: if lower_title in track.title.lower(): - results.append(track) + candidate_tracks.append(track) else: # Fallback: If the artist wasn't found, search for the track title # across the entire library. This is less precise but better than nothing. logger.debug(f"Artist '{artist}' not found. Falling back to title search for '{title}'.") - results = self.music_library.searchTracks(title=title, limit=limit) + candidate_tracks = self.music_library.searchTracks(title=title, limit=limit) - tracks = [] - for result in results[:limit]: # Apply limit after filtering - tracks.append(PlexTrackInfo.from_plex_track(result)) + # Convert the raw Plex track objects to our simplified PlexTrackInfo dataclass. + # Apply the limit here to the final list of candidates. + tracks = [PlexTrackInfo.from_plex_track(track) for track in candidate_tracks[:limit]] if tracks: - logger.debug(f"Plex search for '{title}' by '{artist}' found {len(tracks)} potential matches.") + logger.debug(f"Plex search for title='{title}' by artist='{artist}' found {len(tracks)} potential matches.") return tracks except Exception as e: - logger.error(f"Error searching Plex tracks for '{title}': {e}") + logger.error(f"Error searching Plex tracks for title='{title}', artist='{artist}': {e}") + import traceback + traceback.print_exc() return [] def get_library_stats(self) -> Dict[str, int]: diff --git a/ui/pages/sync.py b/ui/pages/sync.py index 56172ff7..8b041b12 100644 --- a/ui/pages/sync.py +++ b/ui/pages/sync.py @@ -120,28 +120,71 @@ class PlaylistTrackAnalysisWorker(QRunnable): def _check_track_in_plex(self, spotify_track): """ - Check if a Spotify track exists in Plex by searching Plex and using the - MusicMatchingEngine to find the best match. + Check if a Spotify track exists in Plex by trying several search strategies + and using the MusicMatchingEngine to find the best match. """ try: # Use the first artist for the primary search query artist_name = spotify_track.artists[0] if spotify_track.artists else "" + original_title = spotify_track.name + + # --- Generate a list of search queries, from most specific to most broad --- + search_queries = [] + + # Strategy 1: Original, unmodified title. Catches exact matches. + search_queries.append(original_title) + + # Strategy 2: Title with content after a hyphen removed. + # e.g., "Song Title - Remaster" -> "Song Title" + if " - " in original_title: + title_before_hyphen = original_title.split(' - ')[0].strip() + if title_before_hyphen: + search_queries.append(title_before_hyphen) + + # Strategy 3: Title with parenthetical/bracketed content removed. + # (Uses the simple cleaner from this file for an intermediate search) + cleaned_for_search = clean_track_name_for_search(original_title) + if cleaned_for_search.lower() != original_title.lower(): + search_queries.append(cleaned_for_search) - # Use the cleaned track name to get a broader set of potential matches from Plex - search_title = clean_track_name_for_search(spotify_track.name) + # Strategy 4: A "base" title with all extra info removed (remixes, feats, etc.) + # using the more aggressive cleaning from the matching engine. + base_title = self.matching_engine.clean_title(original_title) + if base_title.lower() != cleaned_for_search.lower() and base_title.lower() != original_title.lower(): + search_queries.append(base_title) + + # Remove duplicate queries that might have resulted from the cleaning steps, preserving order. + unique_queries = list(dict.fromkeys(search_queries)) - # Call the updated search_tracks with separate artist and title - potential_plex_matches = self.plex_client.search_tracks( - title=search_title, - artist=artist_name, - limit=10 - ) + print(f"🧠 Generated search queries for '{original_title}': {unique_queries}") + + # --- Execute searches and collect all potential matches --- + all_potential_matches = [] + found_match_ids = set() + + for query_title in unique_queries: + if self._cancelled: + return None, 0.0 + + # Call the updated search_tracks with the query title and artist + potential_plex_matches = self.plex_client.search_tracks( + title=query_title, + artist=artist_name, + limit=15 # Increased limit to get more candidates + ) + + for track in potential_plex_matches: + if track.id not in found_match_ids: + all_potential_matches.append(track) + found_match_ids.add(track.id) - if not potential_plex_matches: + if not all_potential_matches: + print(f"❌ No Plex candidates found for '{original_title}' after trying all strategies.") return None, 0.0 - # Use the matching engine to find the best match among the candidates. - match_result = self.matching_engine.find_best_match(spotify_track, potential_plex_matches) + # --- Use the matching engine to find the best match among ALL candidates --- + print(f"✅ Found {len(all_potential_matches)} potential Plex matches for '{original_title}'. Scoring now...") + match_result = self.matching_engine.find_best_match(spotify_track, all_potential_matches) # Return the best Plex track found and its confidence score. return match_result.plex_track, match_result.confidence