feat(library): extract and display album artwork

- Extract embedded artwork from audio files during scan
- Save extracted artwork to cache directory (~/.cache/qbz/artwork)
- Fallback to folder artwork (cover.jpg, folder.jpg, front.jpg, etc.)
- Add get_artwork_cache_dir helper function
- Artwork now displays in album cards and detail views
This commit is contained in:
vicrodh committed 2026-01-09 14:07:09 -06:00
1 parent e8a61a45ce
commit f6dbf2ee2a
3 files changed
+45 -2

No files matched your search

+10 -2
View File
@@ -6,7 +6,7 @@ use tauri::State;
use tokio::sync::Mutex;
use crate::library::{
cue_to_tracks, CueParser, LibraryDatabase, LibraryError, LibraryScanner, LibraryStats,
cue_to_tracks, get_artwork_cache_dir, CueParser, LibraryDatabase, LibraryScanner, LibraryStats,
LocalAlbum, LocalArtist, LocalTrack, MetadataExtractor, ScanError, ScanProgress, ScanStatus,
};
@@ -158,7 +158,15 @@ pub async fn library_scan(state: State<'_, LibraryState>) -> Result<(), String>
}
match MetadataExtractor::extract(audio_path) {
Ok(track) => {
Ok(mut track) => {
// Try to extract embedded artwork, fallback to folder artwork
let artwork_cache = get_artwork_cache_dir();
if let Some(artwork_path) = MetadataExtractor::extract_artwork(audio_path, &artwork_cache) {
track.artwork_path = Some(artwork_path);
} else if let Some(folder_art) = MetadataExtractor::find_folder_artwork(audio_path) {
track.artwork_path = Some(folder_art);
}
let db = state.db.lock().await;
if let Err(e) = db.insert_track(&track) {
all_errors.push(ScanError {
+25
View File
@@ -197,6 +197,31 @@ impl MetadataExtractor {
}
hash
}
/// Look for folder artwork (cover.jpg, folder.jpg, etc.)
/// Returns the path if found
pub fn find_folder_artwork(audio_file_path: &Path) -> Option<String> {
let parent_dir = audio_file_path.parent()?;
// Common artwork filenames in order of preference
const ARTWORK_NAMES: &[&str] = &[
"cover.jpg", "cover.jpeg", "cover.png",
"folder.jpg", "folder.jpeg", "folder.png",
"front.jpg", "front.jpeg", "front.png",
"album.jpg", "album.jpeg", "album.png",
"Cover.jpg", "Cover.jpeg", "Cover.png",
"Folder.jpg", "Folder.jpeg", "Folder.png",
];
for name in ARTWORK_NAMES {
let artwork_path = parent_dir.join(name);
if artwork_path.exists() {
return Some(artwork_path.to_string_lossy().to_string());
}
}
None
}
}
#[cfg(test)]
+10
View File
@@ -32,6 +32,16 @@ pub fn get_db_path() -> PathBuf {
data_dir.join("library.db")
}
/// Get artwork cache directory
pub fn get_artwork_cache_dir() -> PathBuf {
let cache_dir = dirs::cache_dir()
.unwrap_or_else(|| PathBuf::from("."))
.join("qbz")
.join("artwork");
std::fs::create_dir_all(&cache_dir).ok();
cache_dir
}
/// Initialize library state
pub fn init_library_state() -> Result<LibraryState, LibraryError> {
let db_path = get_db_path();