fix: cover art loss, sidebar badge, analysis UI, and phase text/color
- Recover image_url into album.images across all sync paths (ensure_spotify_track_format, cancel V1, sync_service fallback, discover.js wishlist add) - Include analysis, downloading, and queued phases in sidebar badge count - Add analysis_processed to batch summary API so downloads page shows analysis progress - Show Analyzing X/Y with correct progress bar in batch cards (pages-extra.js) - Add analysis and queued cases to getPhaseText/getPhaseColor (sync-spotify.js)
This commit is contained in:
parent
4317235a24
commit
0ec8b25a17
5 changed files with 75 additions and 13 deletions
|
|
@ -339,11 +339,15 @@ class PlaylistSyncService:
|
|||
if original_track_data:
|
||||
spotify_track_data = original_track_data
|
||||
else:
|
||||
_img_url = getattr(spotify_track, 'image_url', None) or ''
|
||||
spotify_track_data = {
|
||||
'id': spotify_track.id,
|
||||
'name': spotify_track.name,
|
||||
'artists': [{'name': a} if isinstance(a, str) else a for a in spotify_track.artists],
|
||||
'album': {'name': spotify_track.album},
|
||||
'album': {
|
||||
'name': spotify_track.album,
|
||||
'images': [{'url': _img_url}] if _img_url else []
|
||||
},
|
||||
'duration_ms': spotify_track.duration_ms,
|
||||
'popularity': getattr(spotify_track, 'popularity', 0),
|
||||
'preview_url': getattr(spotify_track, 'preview_url', None),
|
||||
|
|
|
|||
|
|
@ -5950,7 +5950,7 @@ def get_debug_info():
|
|||
|
||||
# Active downloads & syncs (use list() snapshots to avoid RuntimeError from concurrent mutation)
|
||||
try:
|
||||
active_downloads = len([bid for bid, bd in list(download_batches.items()) if bd.get('phase') == 'downloading'])
|
||||
active_downloads = len([bid for bid, bd in list(download_batches.items()) if bd.get('phase') in ('analysis', 'downloading', 'queued')])
|
||||
except Exception:
|
||||
active_downloads = 0
|
||||
active_syncs = 0
|
||||
|
|
@ -28395,6 +28395,13 @@ def _ensure_spotify_track_format(track_info):
|
|||
album.setdefault('album_type', 'album')
|
||||
album.setdefault('total_tracks', 0)
|
||||
|
||||
# Recover cover art from top-level image_url / album_cover_url when album
|
||||
# was a plain string (discover fallback path) and images ended up empty
|
||||
if not album.get('images'):
|
||||
_img_url = track_info.get('image_url') or track_info.get('album_cover_url') or ''
|
||||
if _img_url:
|
||||
album['images'] = [{'url': _img_url}]
|
||||
|
||||
# Build proper Spotify track structure
|
||||
spotify_track = {
|
||||
'id': track_info.get('id', f"webui_{hash(str(track_info))}"),
|
||||
|
|
@ -31637,6 +31644,7 @@ def get_all_downloads_unified():
|
|||
'phase': batch.get('phase', 'unknown'),
|
||||
'total': len(queue),
|
||||
'analysis_total': batch.get('analysis_total', len(queue)),
|
||||
'analysis_processed': batch.get('analysis_processed', 0),
|
||||
'completed': sum(1 for s in statuses if s in ('completed', 'skipped', 'already_owned')),
|
||||
'failed': sum(1 for s in statuses if s in ('failed', 'not_found', 'cancelled')),
|
||||
'active': sum(1 for s in statuses if s in ('downloading', 'searching', 'post_processing')),
|
||||
|
|
@ -31822,10 +31830,16 @@ def cancel_download_task():
|
|||
album_data = dict(album_raw) # Copy all fields including artists
|
||||
album_data.setdefault('name', 'Unknown Album')
|
||||
album_data.setdefault('album_type', track_info.get('album_type', 'album'))
|
||||
# Ensure images are present (recover from top-level image_url if needed)
|
||||
if 'images' not in album_data:
|
||||
_img = track_info.get('image_url') or track_info.get('album_image_url') or ''
|
||||
album_data['images'] = [{'url': _img}] if _img else []
|
||||
else:
|
||||
_img = track_info.get('image_url') or track_info.get('album_image_url') or ''
|
||||
album_data = {
|
||||
'name': str(album_raw) if album_raw else 'Unknown Album',
|
||||
'album_type': track_info.get('album_type', 'album')
|
||||
'album_type': track_info.get('album_type', 'album'),
|
||||
'images': [{'url': _img}] if _img else []
|
||||
}
|
||||
|
||||
spotify_track_data = {
|
||||
|
|
|
|||
|
|
@ -7901,13 +7901,17 @@ async function startDiscoverPlaylistSync(playlistType, playlistName) {
|
|||
}
|
||||
return t;
|
||||
}
|
||||
const _coverUrl = track.album_cover_url || track.image_url || '';
|
||||
return {
|
||||
id: track.spotify_track_id || track.track_id || '',
|
||||
name: track.track_name || track.name || '',
|
||||
artists: [track.artist_name || 'Unknown Artist'],
|
||||
album: track.album_name || '',
|
||||
album: {
|
||||
name: track.album_name || '',
|
||||
images: _coverUrl ? [{ url: _coverUrl }] : []
|
||||
},
|
||||
duration_ms: track.duration_ms || 0,
|
||||
image_url: track.album_cover_url || track.image_url || ''
|
||||
image_url: _coverUrl
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -9260,13 +9264,17 @@ async function _doSyncDiscoverPlaylist(playlistType, playlistName) {
|
|||
}
|
||||
return t;
|
||||
}
|
||||
const _coverUrl = track.album_cover_url || track.image_url || '';
|
||||
return {
|
||||
id: track.spotify_track_id || track.track_id || '',
|
||||
name: track.track_name || track.name || '',
|
||||
artists: [track.artist_name || 'Unknown Artist'],
|
||||
album: track.album_name || '',
|
||||
album: {
|
||||
name: track.album_name || '',
|
||||
images: _coverUrl ? [{ url: _coverUrl }] : []
|
||||
},
|
||||
duration_ms: track.duration_ms || 0,
|
||||
image_url: track.album_cover_url || track.image_url || ''
|
||||
image_url: _coverUrl
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -9287,13 +9295,13 @@ async function _doSyncDiscoverPlaylist(playlistType, playlistName) {
|
|||
|
||||
const result = await batchResponse.json();
|
||||
if (result.success) {
|
||||
showToast(`Downloading ${playlistName} (${syncTracks.length} tracks)...`, 'info');
|
||||
showToast(`${playlistName}: analyzing ${syncTracks.length} tracks...`, 'info');
|
||||
const card = document.getElementById(`discover-sync-card-${playlistType}`);
|
||||
if (card) {
|
||||
const statusEl = card.querySelector('.discover-sync-status');
|
||||
if (statusEl) {
|
||||
statusEl.className = 'discover-sync-status syncing';
|
||||
statusEl.textContent = 'Downloading...';
|
||||
statusEl.textContent = 'Analyzing...';
|
||||
}
|
||||
}
|
||||
// Poll the download batch status
|
||||
|
|
@ -9378,6 +9386,28 @@ function pollDiscoverBatchFromTab(playlistType, batchId, playlistName) {
|
|||
const data = await resp.json();
|
||||
const phase = data.phase || data.status;
|
||||
|
||||
// Update card status text during intermediate phases
|
||||
const card = document.getElementById(`discover-sync-card-${playlistType}`);
|
||||
if (card && phase !== 'complete' && phase !== 'error' && phase !== 'cancelled') {
|
||||
const statusEl = card.querySelector('.discover-sync-status');
|
||||
if (statusEl) {
|
||||
statusEl.className = 'discover-sync-status syncing';
|
||||
if (phase === 'analyzing' || phase === 'analysis') {
|
||||
const analysisResults = data.analysis_results || [];
|
||||
const total = data.total || data.track_count || 0;
|
||||
const analyzed = analysisResults.length;
|
||||
statusEl.textContent = total > 0 ? `Analyzing ${analyzed}/${total}` : 'Analyzing...';
|
||||
} else if (phase === 'downloading') {
|
||||
const tasks = data.tasks || [];
|
||||
const completed = tasks.filter(t => t.status === 'completed').length;
|
||||
const total = tasks.length;
|
||||
statusEl.textContent = total > 0 ? `Downloading ${completed}/${total}` : 'Downloading...';
|
||||
} else {
|
||||
statusEl.textContent = phase === 'idle' ? 'Starting...' : `${phase.charAt(0).toUpperCase() + phase.slice(1)}...`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (phase === 'complete' || phase === 'error' || phase === 'cancelled') {
|
||||
clearInterval(pollInterval);
|
||||
delete discoverSyncPollers[playlistType];
|
||||
|
|
|
|||
|
|
@ -2484,10 +2484,16 @@ function _adlRenderBatchPanel() {
|
|||
const isFiltered = _adlFilterBatchId === batch.batch_id;
|
||||
const total = batch.total || 1;
|
||||
const done = batch.completed + batch.failed;
|
||||
const pct = Math.round((done / total) * 100);
|
||||
let pct;
|
||||
if (batch.phase === 'analysis') {
|
||||
const at = batch.analysis_total || 1;
|
||||
pct = Math.round(((batch.analysis_processed || 0) / at) * 100);
|
||||
} else {
|
||||
pct = Math.round((done / total) * 100);
|
||||
}
|
||||
const hasFailed = batch.failed > 0;
|
||||
const isTerminal = batch.phase === 'complete' || batch.phase === 'cancelled' || batch.phase === 'error';
|
||||
const isActive = batch.phase === 'downloading' && batch.active > 0;
|
||||
const isActive = (batch.phase === 'downloading' && batch.active > 0) || batch.phase === 'analysis';
|
||||
|
||||
// Fade progress for completing batches
|
||||
let fadeStyle = '';
|
||||
|
|
@ -2508,8 +2514,13 @@ function _adlRenderBatchPanel() {
|
|||
let phaseText = '';
|
||||
let phaseIcon = '';
|
||||
if (batch.phase === 'analysis') {
|
||||
phaseText = 'Analyzing...';
|
||||
const ap = batch.analysis_processed || 0;
|
||||
const at = batch.analysis_total || 0;
|
||||
phaseText = at > 0 ? `Analyzing ${ap}/${at}` : 'Analyzing...';
|
||||
phaseIcon = '<span class="adl-spinner" style="margin-right:4px"></span>';
|
||||
} else if (batch.phase === 'queued') {
|
||||
phaseText = 'Queued';
|
||||
phaseIcon = '<span style="margin-right:4px">⏳</span>';
|
||||
} else if (batch.phase === 'downloading') {
|
||||
phaseText = `${batch.completed}/${total} tracks`;
|
||||
if (batch.active > 0) phaseIcon = '<span class="adl-spinner" style="margin-right:4px"></span>';
|
||||
|
|
|
|||
|
|
@ -1413,6 +1413,8 @@ function getPhaseText(phase) {
|
|||
case 'discovered': return 'Discovery Complete';
|
||||
case 'syncing': return 'Syncing...';
|
||||
case 'sync_complete': return 'Sync Complete';
|
||||
case 'analysis': return 'Analyzing...';
|
||||
case 'queued': return 'Queued';
|
||||
case 'downloading': return 'Downloading...';
|
||||
case 'download_complete': return 'Download Complete';
|
||||
default: return phase;
|
||||
|
|
@ -1422,7 +1424,8 @@ function getPhaseText(phase) {
|
|||
function getPhaseColor(phase) {
|
||||
switch (phase) {
|
||||
case 'fresh': return '#999';
|
||||
case 'discovering': case 'syncing': case 'downloading': return '#ffa500';
|
||||
case 'discovering': case 'syncing': case 'downloading': case 'analysis': return '#ffa500';
|
||||
case 'queued': return '#6b9fff';
|
||||
case 'discovered': case 'sync_complete': case 'download_complete': return 'rgb(var(--accent-rgb))';
|
||||
default: return '#999';
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue