update discovery modal to collect album info

This commit is contained in:
Broque Thomas 2025-11-24 14:22:32 -08:00
parent 178bb7696d
commit 3be1dc9ace
2 changed files with 142 additions and 43 deletions

View file

@ -12610,12 +12610,30 @@ def _run_tidal_discovery_worker(playlist_id):
'status': 'not_found'
}
if spotify_track:
if isinstance(spotify_track, tuple):
# Function now returns (Track, raw_data)
track_obj, raw_track_data = spotify_track
# Use full album object from raw API response
album_obj = raw_track_data.get('album', {}) if raw_track_data else {}
result['spotify_data'] = {
'id': track_obj.id,
'name': track_obj.name,
'artists': track_obj.artists, # Already a list of strings
'album': album_obj, # Full album object with images
'duration_ms': track_obj.duration_ms,
'external_urls': track_obj.external_urls
}
result['status'] = 'found'
successful_discoveries += 1
state['spotify_matches'] = successful_discoveries
elif spotify_track:
# Fallback for old format (shouldn't happen after update)
result['spotify_data'] = {
'id': spotify_track.id,
'name': spotify_track.name,
'artists': spotify_track.artists, # Already a list of strings
'album': spotify_track.album, # Already a string
'artists': spotify_track.artists,
'album': {'name': spotify_track.album, 'album_type': 'album', 'images': []},
'duration_ms': spotify_track.duration_ms,
'external_urls': spotify_track.external_urls
}
@ -12698,19 +12716,28 @@ def _search_spotify_for_tidal_track(tidal_track):
# Find best match using confidence scoring
best_match = None
best_match_raw = None # Store raw Spotify API data for full album info
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:
print(f"🔍 Tidal query {query_idx + 1}/{len(search_queries)}: {search_query}")
# Get raw Spotify API response to access full album object with images
raw_results = spotify_client.sp.search(q=search_query, type='track', limit=5)
if not raw_results or 'tracks' not in raw_results or not raw_results['tracks']['items']:
continue
# Also get Track objects for matching logic
results = spotify_client.search_tracks(search_query, limit=5)
if not results:
continue
# Score each result using matching engine
for result in results:
for idx, result in enumerate(results):
raw_track = raw_results['tracks']['items'][idx] if idx < len(raw_results['tracks']['items']) else None
try:
# Calculate confidence using matching engine's similarity scoring (with fallback)
try:
@ -12745,6 +12772,7 @@ def _search_spotify_for_tidal_track(tidal_track):
if combined_confidence > best_confidence and combined_confidence >= min_confidence:
best_confidence = combined_confidence
best_match = result
best_match_raw = raw_track # Store raw data with full album object
print(f"✅ New best Tidal match: {result.artists[0]} - {result.name} (confidence: {combined_confidence:.3f})")
except Exception as e:
@ -12762,10 +12790,10 @@ def _search_spotify_for_tidal_track(tidal_track):
if best_match:
print(f"✅ Final Tidal match: {best_match.artists[0]} - {best_match.name} (confidence: {best_confidence:.3f})")
return (best_match, best_match_raw) # Return both Track object and raw data
else:
print(f"❌ No suitable Tidal match found (best confidence was {best_confidence:.3f}, required {min_confidence:.3f})")
return best_match
return None
except Exception as e:
print(f"❌ Error searching Spotify for Tidal track: {e}")
@ -13179,16 +13207,26 @@ def _run_youtube_discovery_worker(url_hash):
# Fallback to original simple query
search_queries = [f"artist:{cleaned_artist} track:{cleaned_title}"]
# Store raw Spotify data for best match
best_raw_track = None
for query_idx, search_query in enumerate(search_queries):
try:
print(f"🔍 YouTube query {query_idx + 1}/{len(search_queries)}: {search_query}")
# Get raw Spotify API response to access full album object with images
raw_results = spotify_client.sp.search(q=search_query, type='track', limit=5)
if not raw_results or 'tracks' not in raw_results or not raw_results['tracks']['items']:
continue
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:
for result_idx, spotify_result in enumerate(spotify_results):
raw_track = raw_results['tracks']['items'][result_idx] if result_idx < len(raw_results['tracks']['items']) else None
try:
# Calculate confidence using matching engine's similarity scoring (with fallback)
try:
@ -13238,6 +13276,7 @@ def _run_youtube_discovery_worker(url_hash):
if combined_confidence > best_confidence and combined_confidence >= min_confidence:
best_confidence = combined_confidence
spotify_track = spotify_result
best_raw_track = raw_track # Store raw data with full album object
print(f"✅ New best YouTube match: {spotify_result.artists[0]} - {spotify_result.name} (confidence: {combined_confidence:.3f})")
except Exception as e:
@ -13291,11 +13330,17 @@ def _run_youtube_discovery_worker(url_hash):
if spotify_track:
state['spotify_matches'] += 1
# Use full album object from raw Spotify data if available
album_data = best_raw_track.get('album', {}) if best_raw_track else {}
if not album_data:
# Fallback to string album name
album_data = {'name': spotify_track.album, 'album_type': 'album', 'images': []}
result['spotify_data'] = {
'id': spotify_track.id,
'name': spotify_track.name,
'artists': spotify_track.artists,
'album': spotify_track.album,
'album': album_data, # Full album object with images
'duration_ms': spotify_track.duration_ms
}
@ -17868,19 +17913,27 @@ def _run_beatport_discovery_worker(url_hash):
# Try each search query until we find a good match
best_match = None
best_raw_track = None # Store raw Spotify data for full album info
best_confidence = 0.0
min_confidence = 0.75 # Increased threshold to avoid bad matches like "Dolce" for "Fancy $hit"
for query_idx, search_query in enumerate(search_queries):
try:
print(f"🔍 Query {query_idx + 1}/{len(search_queries)}: {search_query}")
# Get raw Spotify API response to access full album object with images
raw_results = spotify_client.sp.search(q=search_query, type='track', limit=10)
if not raw_results or 'tracks' not in raw_results or not raw_results['tracks']['items']:
continue
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:
for result_idx, result in enumerate(search_results):
raw_track = raw_results['tracks']['items'][result_idx] if result_idx < len(raw_results['tracks']['items']) else None
try:
# Calculate confidence using matching engine's similarity scoring (with fallback)
try:
@ -17938,6 +17991,7 @@ def _run_beatport_discovery_worker(url_hash):
core_title_confidence >= min_core_title_confidence):
best_confidence = combined_confidence
best_match = result
best_raw_track = raw_track # Store raw data with full album object
print(f"✅ New best match: {result.artists[0]} - {result.name} (confidence: {combined_confidence:.3f})")
except Exception as e:
@ -17988,10 +18042,16 @@ def _run_beatport_discovery_worker(url_hash):
# Single artist case
formatted_artists = [{'name': str(spotify_track.artists)}]
# Use full album object from raw Spotify data if available
album_data = best_raw_track.get('album', {}) if best_raw_track else {}
if not album_data:
# Fallback to string album name
album_data = {'name': spotify_track.album, 'album_type': 'album', 'images': []}
result_entry['spotify_data'] = {
'name': spotify_track.name,
'artists': formatted_artists, # Now formatted as list of objects with 'name' property
'album': spotify_track.album, # Already a string
'album': album_data, # Full album object with images
'id': spotify_track.id
# Remove uri for now since it's causing errors
}

View file

@ -8807,38 +8807,35 @@ async function addModalTracksToWishlist(playlistId) {
artists: formattedArtists
};
// Use track's own album data if available (has correct album_type)
// If missing, fetch from Spotify using track ID
// Use track's own album data if available
// Convert string album names to objects if needed (no Spotify fetch!)
let trackAlbum = track.album;
let trackAlbumType = track.album?.album_type || 'album';
let trackAlbumType = 'album';
if (!trackAlbum || !trackAlbum.name) {
// Try to fetch track data from Spotify to get album info
if (track.spotify_track_id || track.id) {
try {
const spotifyTrackId = track.spotify_track_id || track.id;
const trackInfoResponse = await fetch(`/api/spotify/track/${spotifyTrackId}`);
const trackInfo = await trackInfoResponse.json();
if (trackInfo && trackInfo.album) {
trackAlbum = trackInfo.album;
trackAlbumType = trackInfo.album.album_type || 'album';
console.log(`✅ Fetched album data for "${track.name}"`);
} else {
console.warn(`⚠️ Skipping track "${track.name}" - could not fetch album data`);
errorCount++;
continue;
}
} catch (fetchError) {
console.error(`❌ Error fetching track data for "${track.name}":`, fetchError);
errorCount++;
continue;
}
} else {
console.warn(`⚠️ Skipping track "${track.name}" - missing album data and track ID`);
errorCount++;
continue;
// Handle both object and string album formats
if (typeof trackAlbum === 'string') {
// Album is just a string - convert to minimal object
trackAlbum = {
name: trackAlbum,
album_type: 'album',
images: []
};
trackAlbumType = 'album';
} else if (trackAlbum && typeof trackAlbum === 'object') {
// Album is already an object - extract album_type
trackAlbumType = trackAlbum.album_type || 'album';
// Ensure it has a name
if (!trackAlbum.name) {
trackAlbum.name = 'Unknown Album';
}
} else {
// No album data at all - create minimal object
trackAlbum = {
name: 'Unknown Album',
album_type: 'album',
images: []
};
trackAlbumType = 'album';
}
const response = await fetch('/api/add-album-to-wishlist', {
@ -11848,11 +11845,22 @@ async function startTidalDownloadMissing(urlHash) {
spotifyTracks.push(result.spotify_data);
} else if (result.spotify_track && result.status_class === 'found') {
// Build from individual fields (automatic discovery format)
// Convert album to proper object format for wishlist compatibility
const albumData = result.spotify_album || 'Unknown Album';
const albumObject = typeof albumData === 'object' && albumData !== null
? albumData
: {
name: typeof albumData === 'string' ? albumData : 'Unknown Album',
album_type: 'album',
images: []
};
spotifyTracks.push({
id: result.spotify_id || 'unknown',
name: result.spotify_track || 'Unknown Track',
artists: result.spotify_artist ? [result.spotify_artist] : ['Unknown Artist'],
album: result.spotify_album || 'Unknown Album'
album: albumObject,
duration_ms: 0
});
}
}
@ -13889,11 +13897,21 @@ async function startBeatportDownloadMissing(urlHash) {
track = result.spotify_data;
} else {
// Build from individual fields (automatic discovery format)
// Convert album to proper object format for wishlist compatibility
const albumData = result.spotify_album || 'Unknown Album';
const albumObject = typeof albumData === 'object' && albumData !== null
? albumData
: {
name: typeof albumData === 'string' ? albumData : 'Unknown Album',
album_type: 'album',
images: []
};
track = {
id: result.spotify_id || 'unknown',
name: result.spotify_track || 'Unknown Track',
artists: result.spotify_artist ? [result.spotify_artist] : ['Unknown Artist'],
album: result.spotify_album || 'Unknown Album',
album: albumObject,
duration_ms: 0
};
}
@ -13908,11 +13926,21 @@ async function startBeatportDownloadMissing(urlHash) {
} else {
track.artists = ['Unknown Artist'];
}
// Ensure album is an object (in case it was converted back to string somehow)
const albumForReturn = typeof track.album === 'object' && track.album !== null
? track.album
: {
name: typeof track.album === 'string' ? track.album : 'Unknown Album',
album_type: 'album',
images: []
};
return {
id: track.id,
name: track.name,
artists: track.artists,
album: track.album || 'Unknown Album',
album: albumForReturn,
duration_ms: track.duration_ms || 0,
external_urls: track.external_urls || {}
};
@ -16374,11 +16402,22 @@ async function startYouTubeDownloadMissing(urlHash) {
return result.spotify_data;
} else {
// Build from individual fields (automatic discovery format)
// Convert album to proper object format for wishlist compatibility
const albumData = result.spotify_album || 'Unknown Album';
const albumObject = typeof albumData === 'object' && albumData !== null
? albumData
: {
name: typeof albumData === 'string' ? albumData : 'Unknown Album',
album_type: 'album',
images: []
};
return {
id: result.spotify_id || 'unknown',
name: result.spotify_track || 'Unknown Track',
artists: result.spotify_artist ? [result.spotify_artist] : ['Unknown Artist'],
album: result.spotify_album || 'Unknown Album'
album: albumObject,
duration_ms: 0
};
}
});