feat(playlist): use first album cover art for playlists

Add logic to find and use album cover art as a fallback when playlist
cover art is missing, both for local and online playlists. Update
database schema and upsert logic to store album picture URLs for
online tracks. Improve UI to display fallback cover art when needed.
This commit is contained in:
2025-10-02 22:20:06 -04:00
parent 8e8afb0f66
commit 0bc0e70274
6 changed files with 96 additions and 3 deletions

View File

@@ -3,7 +3,7 @@
import { page } from '$app/stores';
import { settings, loadSettings } from '$lib/stores/settings';
import { scanPlaylists } from '$lib/library/scanner';
import { loadPlaylistTracks, findPlaylistArt } from '$lib/library/playlist';
import { loadPlaylistTracks, findPlaylistArt, findPlaylistCoverFallback } from '$lib/library/playlist';
import CollectionView from '$lib/components/CollectionView.svelte';
import type { Track, PlaylistWithTracks } from '$lib/types/track';
@@ -58,6 +58,11 @@
playlistData = tracksData;
coverArtPath = coverPath;
// If no cover art found, try fallback from first track's album
if (!coverArtPath && playlistData.tracks.length > 0) {
coverArtPath = await findPlaylistCoverFallback(playlistData.tracks, $settings.musicFolder);
}
loading = false;
} catch (e) {
error = 'Error loading playlist: ' + (e as Error).message;

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { onMount } from 'svelte';
import { page } from '$app/stores';
import { convertFileSrc } from '@tauri-apps/api/core';
import { deezerAuth } from '$lib/stores/deezer';
import { deezerAPI } from '$lib/services/deezer';
import { settings } from '$lib/stores/settings';
@@ -14,6 +15,7 @@
import { TrackExistenceCache } from '$lib/library/trackMatcher';
import { addDeezerTrackToQueue } from '$lib/services/deezer/addToQueue';
import { downloadDeezerPlaylist } from '$lib/services/deezer/playlistDownloader';
import { findPlaylistCoverFallback } from '$lib/library/playlist';
import DeezerCollectionView from '$lib/components/DeezerCollectionView.svelte';
import type { Track } from '$lib/types/track';
import type { DeezerTrack } from '$lib/types/deezer';
@@ -85,6 +87,47 @@
// Check track existence after loading tracks
await checkTrackExistence();
// If no valid cover art from Deezer playlist, fetch and use first track's album cover
if ((!playlistPicture || !playlistPicture.startsWith('http')) && tracks.length > 0) {
const cachedTracks = isFavoriteTracks
? await getCachedTracks()
: await getCachedPlaylistTracks(playlistId);
if (cachedTracks.length > 0) {
// If we have cached album picture, use it
if (cachedTracks[0].album_picture) {
playlistPicture = cachedTracks[0].album_picture;
} else if ($deezerAuth.arl && cachedTracks[0].track_id) {
// Fetch album data from API to get cover
try {
deezerAPI.setArl($deezerAuth.arl);
const trackData = await deezerAPI.getTrackData(cachedTracks[0].track_id);
if (trackData && trackData.ALB_PICTURE) {
const albumCoverUrl = `https://e-cdns-images.dzcdn.net/images/cover/${trackData.ALB_PICTURE}/500x500-000000-80-0-0.jpg`;
playlistPicture = albumCoverUrl;
// Update cache with the album picture
const database = await import('$lib/library/deezer-database').then(m => m.initDeezerDatabase());
await database.execute(
'UPDATE deezer_playlist_tracks SET album_picture = $1 WHERE track_id = $2',
[albumCoverUrl, cachedTracks[0].track_id]
);
}
} catch (err) {
console.error('Failed to fetch album cover:', err);
}
}
}
// Final fallback to local files
if ((!playlistPicture || !playlistPicture.startsWith('http')) && $settings.musicFolder) {
const fallbackPath = await findPlaylistCoverFallback(tracks, $settings.musicFolder);
if (fallbackPath) {
playlistPicture = convertFileSrc(fallbackPath);
}
}
}
} catch (e) {
error = 'Error loading playlist: ' + (e instanceof Error ? e.message : String(e));
} finally {