matching engine functionality for discovery modal
This commit is contained in:
parent
bf454e63eb
commit
eadb283522
1 changed files with 277 additions and 82 deletions
359
web_server.py
359
web_server.py
|
|
@ -9957,37 +9957,112 @@ def _run_tidal_discovery_worker(playlist_id):
|
|||
|
||||
|
||||
def _search_spotify_for_tidal_track(tidal_track):
|
||||
"""Search Spotify for a Tidal track (simplified version of sync.py logic)"""
|
||||
"""Search Spotify for a Tidal track using matching_engine for better accuracy"""
|
||||
if not spotify_client or not spotify_client.is_authenticated():
|
||||
return None
|
||||
|
||||
|
||||
try:
|
||||
# Construct search query like sync.py does
|
||||
# Get track info
|
||||
track_name = tidal_track.name
|
||||
artists = tidal_track.artists or []
|
||||
|
||||
|
||||
if not artists:
|
||||
return None
|
||||
|
||||
# Try different search combinations (like sync.py TidalSpotifyDiscoveryWorker)
|
||||
search_queries = [
|
||||
f'track:"{track_name}" artist:"{artists[0]}"',
|
||||
f'"{track_name}" "{artists[0]}"',
|
||||
f'{track_name} {artists[0]}'
|
||||
]
|
||||
|
||||
for query in search_queries:
|
||||
|
||||
artist_name = artists[0] # Use primary artist
|
||||
|
||||
print(f"🔍 Tidal track: '{artist_name}' - '{track_name}'")
|
||||
|
||||
# Use matching engine to generate search queries (with fallback)
|
||||
try:
|
||||
# Create a temporary SpotifyTrack-like object for the matching engine
|
||||
temp_track = type('TempTrack', (), {
|
||||
'name': track_name,
|
||||
'artists': [artist_name],
|
||||
'album': None
|
||||
})()
|
||||
search_queries = matching_engine.generate_download_queries(temp_track)
|
||||
print(f"🔍 Generated {len(search_queries)} search queries for Tidal track")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Matching engine failed for Tidal, falling back to basic queries: {e}")
|
||||
# Fallback to original simple queries
|
||||
search_queries = [
|
||||
f'track:"{track_name}" artist:"{artist_name}"',
|
||||
f'"{track_name}" "{artist_name}"',
|
||||
f'{track_name} {artist_name}'
|
||||
]
|
||||
|
||||
# Find best match using confidence scoring
|
||||
best_match = None
|
||||
best_confidence = 0.0
|
||||
min_confidence = 0.7 # Higher threshold for Tidal since data is cleaner
|
||||
|
||||
for query_idx, search_query in enumerate(search_queries):
|
||||
try:
|
||||
results = spotify_client.search_tracks(query, limit=5)
|
||||
if results and len(results) > 0:
|
||||
# Return first match (could add matching logic like sync.py)
|
||||
return results[0]
|
||||
print(f"🔍 Tidal query {query_idx + 1}/{len(search_queries)}: {search_query}")
|
||||
results = spotify_client.search_tracks(search_query, limit=5)
|
||||
|
||||
if not results:
|
||||
continue
|
||||
|
||||
# Score each result using matching engine
|
||||
for result in results:
|
||||
try:
|
||||
# Calculate confidence using matching engine's similarity scoring (with fallback)
|
||||
try:
|
||||
artist_confidence = 0.0
|
||||
if result.artists:
|
||||
# Get best artist match confidence
|
||||
for result_artist in result.artists:
|
||||
artist_sim = matching_engine.similarity_score(
|
||||
matching_engine.normalize_string(artist_name),
|
||||
matching_engine.normalize_string(result_artist)
|
||||
)
|
||||
artist_confidence = max(artist_confidence, artist_sim)
|
||||
|
||||
# Calculate title confidence
|
||||
title_confidence = matching_engine.similarity_score(
|
||||
matching_engine.normalize_string(track_name),
|
||||
matching_engine.normalize_string(result.name)
|
||||
)
|
||||
|
||||
# Combined confidence (equal weighting for Tidal clean data)
|
||||
combined_confidence = (artist_confidence * 0.5 + title_confidence * 0.5)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Matching engine scoring failed for Tidal, using first match: {e}")
|
||||
# Fallback: just take the first result if matching engine fails
|
||||
combined_confidence = 1.0 # Set high to accept this match
|
||||
best_match = result
|
||||
break
|
||||
|
||||
print(f"🔍 Tidal candidate: '{result.artists[0]}' - '{result.name}' (confidence: {combined_confidence:.3f})")
|
||||
|
||||
# Update best match if this is better
|
||||
if combined_confidence > best_confidence and combined_confidence >= min_confidence:
|
||||
best_confidence = combined_confidence
|
||||
best_match = result
|
||||
print(f"✅ New best Tidal match: {result.artists[0]} - {result.name} (confidence: {combined_confidence:.3f})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error processing Tidal search result: {e}")
|
||||
continue
|
||||
|
||||
# If we found a very high confidence match, stop searching
|
||||
if best_confidence >= 0.9:
|
||||
print(f"🎯 High confidence Tidal match found ({best_confidence:.3f}), stopping search")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Search error for query '{query}': {e}")
|
||||
print(f"❌ Error in Tidal Spotify search for query '{search_query}': {e}")
|
||||
continue
|
||||
|
||||
return None
|
||||
|
||||
|
||||
if best_match:
|
||||
print(f"✅ Final Tidal match: {best_match.artists[0]} - {best_match.name} (confidence: {best_confidence:.3f})")
|
||||
else:
|
||||
print(f"❌ No suitable Tidal match found (best confidence was {best_confidence:.3f}, required {min_confidence:.3f})")
|
||||
|
||||
return best_match
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error searching Spotify for Tidal track: {e}")
|
||||
return None
|
||||
|
|
@ -10301,45 +10376,122 @@ def _run_youtube_discovery_worker(url_hash):
|
|||
|
||||
print(f"🔍 Searching Spotify for: '{cleaned_artist}' - '{cleaned_title}'")
|
||||
|
||||
# Try multiple search strategies
|
||||
# Try multiple search strategies using matching_engine for better accuracy
|
||||
spotify_track = None
|
||||
best_confidence = 0.0
|
||||
min_confidence = 0.6 # Keep same threshold as before
|
||||
|
||||
# Strategy 1: Use matching_engine search queries (with fallback)
|
||||
try:
|
||||
# Create a temporary SpotifyTrack-like object for the matching engine
|
||||
temp_track = type('TempTrack', (), {
|
||||
'name': cleaned_title,
|
||||
'artists': [cleaned_artist],
|
||||
'album': None
|
||||
})()
|
||||
search_queries = matching_engine.generate_download_queries(temp_track)
|
||||
print(f"🔍 Generated {len(search_queries)} search queries for YouTube track")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Matching engine failed for YouTube, falling back to basic query: {e}")
|
||||
# Fallback to original simple query
|
||||
search_queries = [f"artist:{cleaned_artist} track:{cleaned_title}"]
|
||||
|
||||
for query_idx, search_query in enumerate(search_queries):
|
||||
try:
|
||||
print(f"🔍 YouTube query {query_idx + 1}/{len(search_queries)}: {search_query}")
|
||||
spotify_results = spotify_client.search_tracks(search_query, limit=5)
|
||||
|
||||
if not spotify_results:
|
||||
continue
|
||||
|
||||
# Score each result using matching engine
|
||||
for spotify_result in spotify_results:
|
||||
try:
|
||||
# Calculate confidence using matching engine's similarity scoring (with fallback)
|
||||
try:
|
||||
artist_confidence = 0.0
|
||||
if spotify_result.artists:
|
||||
# Get best artist match confidence
|
||||
for result_artist in spotify_result.artists:
|
||||
artist_sim = matching_engine.similarity_score(
|
||||
matching_engine.normalize_string(cleaned_artist),
|
||||
matching_engine.normalize_string(result_artist)
|
||||
)
|
||||
artist_confidence = max(artist_confidence, artist_sim)
|
||||
|
||||
# Calculate title confidence
|
||||
title_confidence = matching_engine.similarity_score(
|
||||
matching_engine.normalize_string(cleaned_title),
|
||||
matching_engine.normalize_string(spotify_result.name)
|
||||
)
|
||||
|
||||
# Combined confidence (70% title, 30% artist - same as original)
|
||||
combined_confidence = (title_confidence * 0.7 + artist_confidence * 0.3)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Matching engine scoring failed for YouTube, using basic similarity: {e}")
|
||||
# Fallback to original character overlap method
|
||||
def _calculate_similarity_fallback(str1, str2):
|
||||
if not str1 or not str2:
|
||||
return 0
|
||||
str1 = str1.lower().strip()
|
||||
str2 = str2.lower().strip()
|
||||
if str1 == str2:
|
||||
return 1.0
|
||||
set1 = set(str1.replace(' ', ''))
|
||||
set2 = set(str2.replace(' ', ''))
|
||||
if not set1 or not set2:
|
||||
return 0
|
||||
intersection = len(set1.intersection(set2))
|
||||
union = len(set1.union(set2))
|
||||
return intersection / union if union > 0 else 0
|
||||
|
||||
title_score = _calculate_similarity_fallback(cleaned_title, spotify_result.name)
|
||||
artist_score = _calculate_similarity_fallback(cleaned_artist, spotify_result.artists[0] if spotify_result.artists else "")
|
||||
combined_confidence = (title_score * 0.7) + (artist_score * 0.3)
|
||||
|
||||
print(f"🔍 YouTube candidate: '{spotify_result.artists[0]}' - '{spotify_result.name}' (confidence: {combined_confidence:.3f})")
|
||||
|
||||
# Update best match if this is better
|
||||
if combined_confidence > best_confidence and combined_confidence >= min_confidence:
|
||||
best_confidence = combined_confidence
|
||||
spotify_track = spotify_result
|
||||
print(f"✅ New best YouTube match: {spotify_result.artists[0]} - {spotify_result.name} (confidence: {combined_confidence:.3f})")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error processing YouTube search result: {e}")
|
||||
continue
|
||||
|
||||
# If we found a very high confidence match, stop searching
|
||||
if best_confidence >= 0.9:
|
||||
print(f"🎯 High confidence YouTube match found ({best_confidence:.3f}), stopping search")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error in YouTube search for query '{search_query}': {e}")
|
||||
continue
|
||||
|
||||
if spotify_track:
|
||||
print(f"✅ Strategy 1 YouTube match: {spotify_track.artists[0]} - {spotify_track.name} (confidence: {best_confidence:.3f})")
|
||||
|
||||
# Strategy 1: Standard search
|
||||
query = f"artist:{cleaned_artist} track:{cleaned_title}"
|
||||
spotify_results = spotify_client.search_tracks(query, limit=5)
|
||||
|
||||
if spotify_results:
|
||||
# Find best match using similarity
|
||||
best_match = None
|
||||
best_score = 0
|
||||
|
||||
for spotify_result in spotify_results:
|
||||
# Calculate similarity score
|
||||
title_score = _calculate_similarity(cleaned_title.lower(), spotify_result.name.lower())
|
||||
artist_score = _calculate_similarity(cleaned_artist.lower(), spotify_result.artists[0].lower())
|
||||
combined_score = (title_score * 0.7) + (artist_score * 0.3)
|
||||
|
||||
if combined_score > best_score and combined_score > 0.6:
|
||||
best_match = spotify_result
|
||||
best_score = combined_score
|
||||
|
||||
spotify_track = best_match
|
||||
|
||||
# Strategy 2: Swapped search (if first failed)
|
||||
# Strategy 2: Swapped search (if first failed) - keep simple for fallback
|
||||
if not spotify_track:
|
||||
print("🔄 YouTube Strategy 2: Trying swapped search (artist/title reversed)")
|
||||
query = f"artist:{cleaned_title} track:{cleaned_artist}"
|
||||
spotify_results = spotify_client.search_tracks(query, limit=3)
|
||||
if spotify_results:
|
||||
spotify_track = spotify_results[0]
|
||||
|
||||
# Strategy 3: Raw data search (if still failed)
|
||||
print(f"✅ Strategy 2 YouTube match (swapped): {spotify_track.artists[0]} - {spotify_track.name}")
|
||||
|
||||
# Strategy 3: Raw data search (if still failed) - keep simple for fallback
|
||||
if not spotify_track:
|
||||
raw_title = track['raw_title']
|
||||
raw_artist = track['raw_artist']
|
||||
print(f"🔄 YouTube Strategy 3: Trying raw data search: '{raw_artist} {raw_title}'")
|
||||
query = f"{raw_artist} {raw_title}"
|
||||
spotify_results = spotify_client.search_tracks(query, limit=3)
|
||||
if spotify_results:
|
||||
spotify_track = spotify_results[0]
|
||||
print(f"✅ Strategy 3 YouTube match (raw): {spotify_track.artists[0]} - {spotify_track.name}")
|
||||
|
||||
# Create result entry
|
||||
result = {
|
||||
|
|
@ -12826,55 +12978,98 @@ def _run_beatport_discovery_worker(url_hash):
|
|||
|
||||
print(f"🔍 Searching Spotify for: '{track_artist}' - '{track_title}'")
|
||||
|
||||
# Try multiple search strategies
|
||||
# Use matching engine for sophisticated track matching (like other discovery processes)
|
||||
spotify_track = None
|
||||
|
||||
# Clean track title for search (remove remix info)
|
||||
import re
|
||||
clean_title = re.sub(r'\s*\([^)]*\)', '', track_title).strip() # Remove (Extended Mix), (Original Mix), etc.
|
||||
clean_title = re.sub(r'\s*\[[^\]]*\]', '', clean_title).strip() # Remove [brackets]
|
||||
|
||||
# Strategy 1: Simple search with cleaned terms
|
||||
search_query = f"{track_artist} {clean_title}"
|
||||
print(f"🔍 Search query: {search_query}")
|
||||
|
||||
# Generate search queries using matching engine (with fallback)
|
||||
try:
|
||||
search_results = spotify_client.search_tracks(search_query, limit=10)
|
||||
print(f"🔍 Search results type: {type(search_results)}, length: {len(search_results) if search_results else 0}")
|
||||
# Create a temporary SpotifyTrack-like object for the matching engine
|
||||
temp_track = type('TempTrack', (), {
|
||||
'name': track_title,
|
||||
'artists': [track_artist],
|
||||
'album': None
|
||||
})()
|
||||
search_queries = matching_engine.generate_download_queries(temp_track)
|
||||
print(f"🔍 Generated {len(search_queries)} search queries using matching engine")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Matching engine failed for Beatport, falling back to basic queries: {e}")
|
||||
# Fallback to basic search queries
|
||||
search_queries = [
|
||||
f"{track_artist} {track_title}",
|
||||
f'artist:"{track_artist}" track:"{track_title}"',
|
||||
f'"{track_artist}" "{track_title}"'
|
||||
]
|
||||
|
||||
# Find best match from search_tracks result
|
||||
if search_results:
|
||||
# Try each search query until we find a good match
|
||||
best_match = None
|
||||
best_confidence = 0.0
|
||||
min_confidence = 0.6 # Minimum confidence threshold for accepting a match
|
||||
|
||||
for query_idx, search_query in enumerate(search_queries):
|
||||
try:
|
||||
print(f"🔍 Query {query_idx + 1}/{len(search_queries)}: {search_query}")
|
||||
search_results = spotify_client.search_tracks(search_query, limit=10)
|
||||
|
||||
if not search_results:
|
||||
continue
|
||||
|
||||
# Use matching engine to find the best match from search results
|
||||
for result in search_results:
|
||||
try:
|
||||
# Check if artist matches (case insensitive, flexible)
|
||||
result_artists = [artist.lower() for artist in result.artists]
|
||||
artist_match = any(track_artist.lower() in artist for artist in result_artists) or any(artist in track_artist.lower() for artist in result_artists)
|
||||
# Calculate confidence using matching engine's similarity scoring (with fallback)
|
||||
try:
|
||||
artist_confidence = 0.0
|
||||
if result.artists:
|
||||
# Get best artist match confidence
|
||||
result_artist_names = [artist for artist in result.artists]
|
||||
for result_artist in result_artist_names:
|
||||
artist_sim = matching_engine.similarity_score(
|
||||
matching_engine.normalize_string(track_artist),
|
||||
matching_engine.normalize_string(result_artist)
|
||||
)
|
||||
artist_confidence = max(artist_confidence, artist_sim)
|
||||
|
||||
# Check if title matches (case insensitive, flexible)
|
||||
title_match = clean_title.lower() in result.name.lower() or result.name.lower() in clean_title.lower()
|
||||
# Calculate title confidence
|
||||
title_confidence = matching_engine.similarity_score(
|
||||
matching_engine.normalize_string(track_title),
|
||||
matching_engine.normalize_string(result.name)
|
||||
)
|
||||
|
||||
# Combined confidence (weighted toward artist matching for dance music)
|
||||
combined_confidence = (artist_confidence * 0.6 + title_confidence * 0.4)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Matching engine scoring failed for Beatport, using basic matching: {e}")
|
||||
# Fallback to simple string matching
|
||||
artist_match = any(track_artist.lower() in artist.lower() for artist in result.artists) if result.artists else False
|
||||
title_match = track_title.lower() in result.name.lower() or result.name.lower() in track_title.lower()
|
||||
combined_confidence = 0.8 if (artist_match and title_match) else 0.4 if (artist_match or title_match) else 0.1
|
||||
|
||||
print(f"🔍 Match candidate: '{result.artists[0]}' - '{result.name}' (confidence: {combined_confidence:.3f})")
|
||||
|
||||
# Update best match if this is better
|
||||
if combined_confidence > best_confidence and combined_confidence >= min_confidence:
|
||||
best_confidence = combined_confidence
|
||||
best_match = result
|
||||
print(f"✅ New best match: {result.artists[0]} - {result.name} (confidence: {combined_confidence:.3f})")
|
||||
|
||||
if artist_match and title_match:
|
||||
spotify_track = result
|
||||
print(f"✅ Found match: {result.artists[0]} - {result.name}")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"❌ Error processing search result: {e}")
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"❌ Error in Spotify search: {e}")
|
||||
|
||||
# Strategy 2: Try artist-only search if no match
|
||||
if not spotify_track:
|
||||
print(f"🔍 Trying artist-only search: {track_artist}")
|
||||
search_results = spotify_client.search_tracks(track_artist, limit=5)
|
||||
# If we found a very high confidence match, stop searching
|
||||
if best_confidence >= 0.9:
|
||||
print(f"🎯 High confidence match found ({best_confidence:.3f}), stopping search")
|
||||
break
|
||||
|
||||
if search_results:
|
||||
for result in search_results:
|
||||
result_artists = [artist.lower() for artist in result.artists]
|
||||
if any(track_artist.lower() in artist for artist in result_artists):
|
||||
print(f"✅ Found by artist: {result.artists[0]} - {result.name}")
|
||||
spotify_track = result
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"❌ Error in Spotify search for query '{search_query}': {e}")
|
||||
continue
|
||||
|
||||
spotify_track = best_match
|
||||
if spotify_track:
|
||||
print(f"✅ Final match selected: {spotify_track.artists[0]} - {spotify_track.name} (confidence: {best_confidence:.3f})")
|
||||
else:
|
||||
print(f"❌ No suitable match found (best confidence was {best_confidence:.3f}, required {min_confidence:.3f})")
|
||||
|
||||
# Create result entry
|
||||
result_entry = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue