better
This commit is contained in:
parent
39ca1e369a
commit
3f04d7f984
2 changed files with 71 additions and 24 deletions
|
|
@ -248,48 +248,52 @@ class PlexClient:
|
||||||
logger.error(f"Error searching for track '{title}' by '{artist}': {e}")
|
logger.error(f"Error searching for track '{title}' by '{artist}': {e}")
|
||||||
return None
|
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
|
Searches for tracks in the Plex music library using a more robust, two-step method
|
||||||
that is more compatible with different Plex server versions.
|
that is more compatible with different Plex server versions.
|
||||||
"""
|
"""
|
||||||
if not self.music_library:
|
if not self.music_library:
|
||||||
|
logger.warning("Plex music library not found. Cannot perform search.")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Step 1: Search for the artist first. This is generally reliable.
|
# Step 1: Search for the artist first. This is generally reliable.
|
||||||
artist_results = self.music_library.searchArtists(title=artist, limit=1)
|
artist_results = self.music_library.searchArtists(title=artist, limit=1)
|
||||||
|
|
||||||
results = []
|
candidate_tracks = []
|
||||||
if artist_results:
|
if artist_results:
|
||||||
# If artist is found, get all their tracks and filter by title in Python.
|
# 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]
|
plex_artist = artist_results[0]
|
||||||
all_artist_tracks = plex_artist.tracks()
|
all_artist_tracks = plex_artist.tracks()
|
||||||
|
|
||||||
# Use a case-insensitive substring match to find potential 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()
|
lower_title = title.lower()
|
||||||
for track in all_artist_tracks:
|
for track in all_artist_tracks:
|
||||||
if lower_title in track.title.lower():
|
if lower_title in track.title.lower():
|
||||||
results.append(track)
|
candidate_tracks.append(track)
|
||||||
else:
|
else:
|
||||||
# Fallback: If the artist wasn't found, search for the track title
|
# Fallback: If the artist wasn't found, search for the track title
|
||||||
# across the entire library. This is less precise but better than nothing.
|
# 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}'.")
|
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 = []
|
# Convert the raw Plex track objects to our simplified PlexTrackInfo dataclass.
|
||||||
for result in results[:limit]: # Apply limit after filtering
|
# Apply the limit here to the final list of candidates.
|
||||||
tracks.append(PlexTrackInfo.from_plex_track(result))
|
tracks = [PlexTrackInfo.from_plex_track(track) for track in candidate_tracks[:limit]]
|
||||||
|
|
||||||
if tracks:
|
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
|
return tracks
|
||||||
|
|
||||||
except Exception as e:
|
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 []
|
return []
|
||||||
|
|
||||||
def get_library_stats(self) -> Dict[str, int]:
|
def get_library_stats(self) -> Dict[str, int]:
|
||||||
|
|
|
||||||
|
|
@ -120,28 +120,71 @@ class PlaylistTrackAnalysisWorker(QRunnable):
|
||||||
|
|
||||||
def _check_track_in_plex(self, spotify_track):
|
def _check_track_in_plex(self, spotify_track):
|
||||||
"""
|
"""
|
||||||
Check if a Spotify track exists in Plex by searching Plex and using the
|
Check if a Spotify track exists in Plex by trying several search strategies
|
||||||
MusicMatchingEngine to find the best match.
|
and using the MusicMatchingEngine to find the best match.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Use the first artist for the primary search query
|
# Use the first artist for the primary search query
|
||||||
artist_name = spotify_track.artists[0] if spotify_track.artists else ""
|
artist_name = spotify_track.artists[0] if spotify_track.artists else ""
|
||||||
|
original_title = spotify_track.name
|
||||||
|
|
||||||
# Use the cleaned track name to get a broader set of potential matches from Plex
|
# --- Generate a list of search queries, from most specific to most broad ---
|
||||||
search_title = clean_track_name_for_search(spotify_track.name)
|
search_queries = []
|
||||||
|
|
||||||
# Call the updated search_tracks with separate artist and title
|
# Strategy 1: Original, unmodified title. Catches exact matches.
|
||||||
potential_plex_matches = self.plex_client.search_tracks(
|
search_queries.append(original_title)
|
||||||
title=search_title,
|
|
||||||
artist=artist_name,
|
|
||||||
limit=10
|
|
||||||
)
|
|
||||||
|
|
||||||
if not potential_plex_matches:
|
# 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)
|
||||||
|
|
||||||
|
# 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))
|
||||||
|
|
||||||
|
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 all_potential_matches:
|
||||||
|
print(f"❌ No Plex candidates found for '{original_title}' after trying all strategies.")
|
||||||
return None, 0.0
|
return None, 0.0
|
||||||
|
|
||||||
# Use the matching engine to find the best match among the candidates.
|
# --- Use the matching engine to find the best match among ALL candidates ---
|
||||||
match_result = self.matching_engine.find_best_match(spotify_track, potential_plex_matches)
|
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 the best Plex track found and its confidence score.
|
||||||
return match_result.plex_track, match_result.confidence
|
return match_result.plex_track, match_result.confidence
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue