fix sync for listenbrainz
This commit is contained in:
parent
333d984527
commit
c281382a3b
2 changed files with 120 additions and 46 deletions
|
|
@ -15285,9 +15285,13 @@ def get_listenbrainz_playlist_tracks(playlist_mbid):
|
||||||
# Extract tracks from JSPF format
|
# Extract tracks from JSPF format
|
||||||
jspf_tracks = playlist.get('track', [])
|
jspf_tracks = playlist.get('track', [])
|
||||||
|
|
||||||
# Convert to our standard format
|
# Convert to our standard format - prepare tracks first without cover art
|
||||||
tracks = []
|
tracks = []
|
||||||
for track in jspf_tracks:
|
print(f"🎵 Processing {len(jspf_tracks)} tracks from playlist")
|
||||||
|
|
||||||
|
# First pass: extract all track data without cover art
|
||||||
|
track_data_list = []
|
||||||
|
for idx, track in enumerate(jspf_tracks):
|
||||||
# Get recording MBID from identifier
|
# Get recording MBID from identifier
|
||||||
recording_mbid = None
|
recording_mbid = None
|
||||||
identifiers = track.get('identifier', [])
|
identifiers = track.get('identifier', [])
|
||||||
|
|
@ -15300,10 +15304,23 @@ def get_listenbrainz_playlist_tracks(playlist_mbid):
|
||||||
extension = track.get('extension', {})
|
extension = track.get('extension', {})
|
||||||
mb_data = extension.get('https://musicbrainz.org/doc/jspf#track', {})
|
mb_data = extension.get('https://musicbrainz.org/doc/jspf#track', {})
|
||||||
|
|
||||||
# Extract album cover from extension if available
|
if idx == 0:
|
||||||
album_cover_url = None
|
print(f"📋 Sample track extension data: {extension}")
|
||||||
if mb_data and 'album_cover_url' in mb_data:
|
print(f"📋 Sample mb_data keys: {mb_data.keys() if mb_data else 'None'}")
|
||||||
album_cover_url = mb_data['album_cover_url']
|
|
||||||
|
# Extract release MBID for cover art
|
||||||
|
release_mbid = None
|
||||||
|
if mb_data:
|
||||||
|
# Check in additional_metadata first
|
||||||
|
additional_metadata = mb_data.get('additional_metadata', {})
|
||||||
|
if 'caa_release_mbid' in additional_metadata:
|
||||||
|
release_mbid = additional_metadata['caa_release_mbid']
|
||||||
|
# Fallback to top-level release_mbid
|
||||||
|
elif 'release_mbid' in mb_data:
|
||||||
|
release_mbid = mb_data['release_mbid']
|
||||||
|
|
||||||
|
if idx == 0:
|
||||||
|
print(f"🆔 First track release_mbid: {release_mbid}")
|
||||||
|
|
||||||
track_data = {
|
track_data = {
|
||||||
'track_name': track.get('title', 'Unknown Track'),
|
'track_name': track.get('title', 'Unknown Track'),
|
||||||
|
|
@ -15311,11 +15328,66 @@ def get_listenbrainz_playlist_tracks(playlist_mbid):
|
||||||
'album_name': track.get('album', 'Unknown Album'),
|
'album_name': track.get('album', 'Unknown Album'),
|
||||||
'duration_ms': track.get('duration', 0),
|
'duration_ms': track.get('duration', 0),
|
||||||
'mbid': recording_mbid,
|
'mbid': recording_mbid,
|
||||||
'album_cover_url': album_cover_url,
|
'release_mbid': release_mbid,
|
||||||
|
'album_cover_url': None, # Will be fetched in parallel
|
||||||
'additional_metadata': mb_data
|
'additional_metadata': mb_data
|
||||||
}
|
}
|
||||||
|
|
||||||
tracks.append(track_data)
|
track_data_list.append(track_data)
|
||||||
|
|
||||||
|
# Second pass: fetch cover art in parallel using threading (much faster)
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
import time
|
||||||
|
|
||||||
|
def fetch_cover_art(track_data):
|
||||||
|
"""Fetch cover art for a single track"""
|
||||||
|
release_mbid = track_data.get('release_mbid')
|
||||||
|
if not release_mbid:
|
||||||
|
return None
|
||||||
|
|
||||||
|
try:
|
||||||
|
cover_art_url = f"https://coverartarchive.org/release/{release_mbid}"
|
||||||
|
cover_response = requests.get(cover_art_url, timeout=3)
|
||||||
|
|
||||||
|
if cover_response.status_code == 200:
|
||||||
|
cover_data = cover_response.json()
|
||||||
|
images = cover_data.get('images', [])
|
||||||
|
|
||||||
|
# Get front cover
|
||||||
|
for img in images:
|
||||||
|
if img.get('front'):
|
||||||
|
return img.get('thumbnails', {}).get('small') or img.get('image')
|
||||||
|
|
||||||
|
# Fallback to first image
|
||||||
|
if images:
|
||||||
|
return images[0].get('thumbnails', {}).get('small') or images[0].get('image')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
print(f"🎨 Fetching cover art for {len(track_data_list)} tracks in parallel...")
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
# Fetch up to 10 covers at a time
|
||||||
|
with ThreadPoolExecutor(max_workers=10) as executor:
|
||||||
|
future_to_track = {executor.submit(fetch_cover_art, track): idx
|
||||||
|
for idx, track in enumerate(track_data_list)}
|
||||||
|
|
||||||
|
for future in as_completed(future_to_track):
|
||||||
|
idx = future_to_track[future]
|
||||||
|
try:
|
||||||
|
cover_url = future.result()
|
||||||
|
if cover_url:
|
||||||
|
track_data_list[idx]['album_cover_url'] = cover_url
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
elapsed = time.time() - start_time
|
||||||
|
covers_found = sum(1 for t in track_data_list if t.get('album_cover_url'))
|
||||||
|
print(f"✅ Fetched {covers_found}/{len(track_data_list)} covers in {elapsed:.2f}s")
|
||||||
|
|
||||||
|
tracks = track_data_list
|
||||||
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"success": True,
|
"success": True,
|
||||||
|
|
|
||||||
|
|
@ -26590,54 +26590,52 @@ async function startListenBrainzPlaylistSync(identifier, title, playlistId) {
|
||||||
|
|
||||||
console.log(`✅ Found ${tracks.length} tracks to sync`);
|
console.log(`✅ Found ${tracks.length} tracks to sync`);
|
||||||
|
|
||||||
// Show sync status
|
// Convert ListenBrainz tracks to Spotify format (same as other discover playlists)
|
||||||
const statusDisplay = document.getElementById(`${playlistId}-sync-status`);
|
const spotifyTracks = tracks.map(track => ({
|
||||||
const syncButton = document.getElementById(`${playlistId}-sync-btn`);
|
id: null, // No Spotify ID for ListenBrainz tracks
|
||||||
|
|
||||||
console.log('UI Elements:', {
|
|
||||||
statusDisplay: !!statusDisplay,
|
|
||||||
syncButton: !!syncButton
|
|
||||||
});
|
|
||||||
|
|
||||||
if (statusDisplay) statusDisplay.style.display = 'block';
|
|
||||||
if (syncButton) syncButton.disabled = true;
|
|
||||||
|
|
||||||
// Prepare tracks for sync
|
|
||||||
const tracksForSync = tracks.map(track => ({
|
|
||||||
name: track.track_name,
|
name: track.track_name,
|
||||||
artist: track.artist_name,
|
artists: [track.artist_name], // Array of strings for sync compatibility
|
||||||
album: track.album_name,
|
album: {
|
||||||
mbid: track.mbid || null,
|
name: track.album_name,
|
||||||
duration_ms: track.duration_ms || 0
|
images: track.album_cover_url ? [{ url: track.album_cover_url }] : []
|
||||||
|
},
|
||||||
|
duration_ms: track.duration_ms || 0,
|
||||||
|
mbid: track.mbid // Include MusicBrainz ID for matching
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const virtualPlaylistId = `discover_lb_${identifier}`;
|
const virtualPlaylistId = `discover_lb_${identifier}`;
|
||||||
console.log(`🆔 Virtual playlist ID: ${virtualPlaylistId}`);
|
console.log(`🆔 Virtual playlist ID: ${virtualPlaylistId}`);
|
||||||
|
|
||||||
// Start sync
|
// Store in cache for sync function (CRITICAL - same as other discover playlists)
|
||||||
console.log('📤 Sending sync request...');
|
playlistTrackCache[virtualPlaylistId] = spotifyTracks;
|
||||||
const response = await fetch('/api/sync/start', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({
|
|
||||||
virtual_playlist_id: virtualPlaylistId,
|
|
||||||
playlist_name: title,
|
|
||||||
tracks: tracksForSync
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`📡 Response status: ${response.status}`);
|
// Create virtual playlist object (CRITICAL - same as other discover playlists)
|
||||||
const result = await response.json();
|
const virtualPlaylist = {
|
||||||
console.log('📋 Response data:', result);
|
id: virtualPlaylistId,
|
||||||
|
name: title,
|
||||||
|
track_count: spotifyTracks.length
|
||||||
|
};
|
||||||
|
|
||||||
if (!result.success) {
|
// Add to spotify playlists array if not already there (CRITICAL)
|
||||||
throw new Error(result.error || 'Failed to start sync');
|
if (!spotifyPlaylists.find(p => p.id === virtualPlaylistId)) {
|
||||||
|
spotifyPlaylists.push(virtualPlaylist);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`✅ Sync started successfully for ${title}`);
|
// Show sync status display
|
||||||
showToast(`Syncing "${title}" to media server`, 'success');
|
const statusDisplay = document.getElementById(`${playlistId}-sync-status`);
|
||||||
|
const syncButton = document.getElementById(`${playlistId}-sync-btn`);
|
||||||
|
|
||||||
// Start polling for status
|
if (statusDisplay) statusDisplay.style.display = 'block';
|
||||||
|
if (syncButton) {
|
||||||
|
syncButton.disabled = true;
|
||||||
|
syncButton.style.opacity = '0.5';
|
||||||
|
syncButton.style.cursor = 'not-allowed';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the same sync function as all other discover playlists
|
||||||
|
await startPlaylistSync(virtualPlaylistId);
|
||||||
|
|
||||||
|
// Start polling for progress updates (using discover playlist pattern)
|
||||||
startListenBrainzSyncPolling(playlistId, virtualPlaylistId);
|
startListenBrainzSyncPolling(playlistId, virtualPlaylistId);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -26647,7 +26645,11 @@ async function startListenBrainzPlaylistSync(identifier, title, playlistId) {
|
||||||
const statusDisplay = document.getElementById(`${playlistId}-sync-status`);
|
const statusDisplay = document.getElementById(`${playlistId}-sync-status`);
|
||||||
const syncButton = document.getElementById(`${playlistId}-sync-btn`);
|
const syncButton = document.getElementById(`${playlistId}-sync-btn`);
|
||||||
if (statusDisplay) statusDisplay.style.display = 'none';
|
if (statusDisplay) statusDisplay.style.display = 'none';
|
||||||
if (syncButton) syncButton.disabled = false;
|
if (syncButton) {
|
||||||
|
syncButton.disabled = false;
|
||||||
|
syncButton.style.opacity = '1';
|
||||||
|
syncButton.style.cursor = 'pointer';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue