Improve Genius artist search for manual matching (#233)

The manual match modal for Genius only returned 0 or 1 artist result
because search_artist() searched songs (per_page=5), extracted the
primary artist, and returned the first match or None.

Added search_artists() that returns multiple unique artists extracted
from song results with broader search (per_page=20). The manual match
endpoint now shows up to 8 artist candidates and multiple track results
instead of one-or-nothing. Also shows the Genius URL as extra info.
This commit is contained in:
Broque Thomas 2026-04-01 08:31:17 -07:00
parent 982ca77501
commit 778e68a844
2 changed files with 47 additions and 17 deletions

View file

@ -212,24 +212,47 @@ class GeniusClient:
Returns: Returns:
Artist dict with: id, name, url, image_url Artist dict with: id, name, url, image_url
""" """
hits = self.search(artist_name, per_page=5) artists = self.search_artists(artist_name, limit=10)
if not hits: if not artists:
return None return None
artist_lower = artist_name.lower().strip() artist_lower = artist_name.lower().strip()
for hit in hits: for a in artists:
result = hit.get('result', {}) a_name = (a.get('name') or '').lower()
primary = result.get('primary_artist', {}) if artist_lower in a_name or a_name in artist_lower:
primary_name = (primary.get('name') or '').lower() logger.debug(f"Found artist: {a.get('name')}")
if primary and (artist_lower in primary_name or primary_name in artist_lower): return a
logger.debug(f"Found artist: {primary.get('name')}")
return primary
# No confident match — let the worker mark as not_found and retry later # No confident match — let the worker mark as not_found and retry later
logger.debug(f"No artist match found in search results for: {artist_name}") logger.debug(f"No artist match found in search results for: {artist_name}")
return None return None
@rate_limited
def search_artists(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
"""
Search for artists by name. Extracts unique artists from song results.
Returns:
List of artist dicts with: id, name, url, image_url
"""
hits = self.search(query, per_page=min(limit * 2, 20))
if not hits:
return []
seen_ids = set()
artists = []
for hit in hits:
result = hit.get('result', {})
primary = result.get('primary_artist', {})
if primary and primary.get('id') and primary['id'] not in seen_ids:
seen_ids.add(primary['id'])
artists.append(primary)
if len(artists) >= limit:
break
return artists
@rate_limited @rate_limited
def get_artist(self, artist_id: int) -> Optional[Dict[str, Any]]: def get_artist(self, artist_id: int) -> Optional[Dict[str, Any]]:
""" """

View file

@ -12614,15 +12614,22 @@ def _search_service(service, entity_type, query):
raise ValueError("Genius worker not initialized") raise ValueError("Genius worker not initialized")
client = genius_worker.client client = genius_worker.client
if entity_type == 'artist': if entity_type == 'artist':
result = client.search_artist(query) artists = client.search_artists(query, limit=8)
if result: return [{'id': str(a.get('id', '')), 'name': a.get('name', ''),
return [{'id': str(result.get('id', '')), 'name': result.get('name', ''), 'image': a.get('image_url'), 'extra': a.get('url', '')} for a in artists]
'image': result.get('image_url'), 'extra': ''}]
elif entity_type == 'track': elif entity_type == 'track':
result = client.search_song(query, '') # Search with broader results for manual matching
if result: hits = client.search(f"{query}", per_page=10)
return [{'id': str(result.get('id', '')), 'name': result.get('title', ''), results = []
'image': result.get('song_art_image_url'), 'extra': result.get('artist_names', '')}] seen_ids = set()
for hit in hits:
r = hit.get('result', {})
rid = r.get('id')
if rid and rid not in seen_ids:
seen_ids.add(rid)
results.append({'id': str(rid), 'name': r.get('title', ''),
'image': r.get('song_art_image_url'), 'extra': r.get('artist_names', '')})
return results
return [] return []
elif service == 'tidal': elif service == 'tidal':