From 0bc0e702748dfae67bf461131e1a914f2f484f36 Mon Sep 17 00:00:00 2001 From: Markury Date: Thu, 2 Oct 2025 22:20:06 -0400 Subject: [PATCH] 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. --- src-tauri/src/lib.rs | 1 + src/lib/library/deezer-database.ts | 12 +++++- src/lib/library/playlist.ts | 35 +++++++++++++++ src/routes/playlists/[name]/+page.svelte | 7 ++- .../deezer/playlists/[id]/+page.svelte | 43 +++++++++++++++++++ static/98.css.map | 1 + 6 files changed, 96 insertions(+), 3 deletions(-) create mode 100644 static/98.css.map diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index e2c4df6..f4882f7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -121,6 +121,7 @@ pub fn run() { title TEXT NOT NULL, artist_name TEXT NOT NULL, album_title TEXT, + album_picture TEXT, duration INTEGER DEFAULT 0, track_number INTEGER, cached_at INTEGER NOT NULL, diff --git a/src/lib/library/deezer-database.ts b/src/lib/library/deezer-database.ts index 8f880ae..f274537 100644 --- a/src/lib/library/deezer-database.ts +++ b/src/lib/library/deezer-database.ts @@ -36,6 +36,7 @@ export interface DeezerTrack { title: string; artist_name: string; album_title: string; + album_picture?: string | null; duration: number; cached_at: number; } @@ -47,6 +48,7 @@ export interface DeezerPlaylistTrack { title: string; artist_name: string; album_title: string; + album_picture: string | null; duration: number; track_number: number | null; cached_at: number; @@ -292,15 +294,21 @@ export async function upsertPlaylistTracks(playlistId: string, tracks: any[]): P // Insert new tracks for (let i = 0; i < tracks.length; i++) { const track = tracks[i]; + // Convert ALB_PICTURE hash to full URL + const albumPictureUrl = track.ALB_PICTURE + ? `https://e-cdns-images.dzcdn.net/images/cover/${track.ALB_PICTURE}/500x500-000000-80-0-0.jpg` + : null; + await database.execute( - `INSERT INTO deezer_playlist_tracks (playlist_id, track_id, title, artist_name, album_title, duration, track_number, cached_at) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, + `INSERT INTO deezer_playlist_tracks (playlist_id, track_id, title, artist_name, album_title, album_picture, duration, track_number, cached_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [ playlistId, String(track.SNG_ID), track.SNG_TITLE || '', track.ART_NAME || 'Unknown', track.ALB_TITLE || '', + albumPictureUrl, track.DURATION || 0, track.TRACK_NUMBER || i + 1, now diff --git a/src/lib/library/playlist.ts b/src/lib/library/playlist.ts index 7ad45c1..6ea593d 100644 --- a/src/lib/library/playlist.ts +++ b/src/lib/library/playlist.ts @@ -1,6 +1,7 @@ import { readTextFile, exists, readDir } from '@tauri-apps/plugin-fs'; import { invoke } from '@tauri-apps/api/core'; import type { Track, AudioFormat, PlaylistWithTracks, TrackMetadata } from '$lib/types/track'; +import { findAlbumArt } from './album'; /** * Get audio format from file extension @@ -210,6 +211,40 @@ export async function findPlaylistArt(playlistPath: string): Promise { + for (const track of tracks) { + const album = track.metadata.album; + const albumArtist = track.metadata.albumArtist || track.metadata.artist; + + if (!album || !albumArtist) { + continue; // Skip tracks without album metadata + } + + // Construct album folder path following the same structure as downloader + const albumPath = `${musicFolder}/${albumArtist}/${album}`; + + try { + // Check if album folder exists and has cover art + const coverArtPath = await findAlbumArt(albumPath); + if (coverArtPath) { + return coverArtPath; + } + } catch (error) { + // Album folder doesn't exist or can't be read, continue to next track + continue; + } + } + + return undefined; +} + /** * Load playlist with track information */ diff --git a/src/routes/playlists/[name]/+page.svelte b/src/routes/playlists/[name]/+page.svelte index ed0b299..781c977 100644 --- a/src/routes/playlists/[name]/+page.svelte +++ b/src/routes/playlists/[name]/+page.svelte @@ -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; diff --git a/src/routes/services/deezer/playlists/[id]/+page.svelte b/src/routes/services/deezer/playlists/[id]/+page.svelte index cce0774..963657c 100644 --- a/src/routes/services/deezer/playlists/[id]/+page.svelte +++ b/src/routes/services/deezer/playlists/[id]/+page.svelte @@ -1,6 +1,7 @@