From 3d7d3ded1cf9fd7f737a1bac54486d34ddaefd06 Mon Sep 17 00:00:00 2001 From: Markury Date: Wed, 3 Jun 2026 20:39:20 -0400 Subject: [PATCH] feat: re-encode cover images for rockbox compatibility --- src-tauri/src/lib.rs | 9 +++++++++ src-tauri/src/tagger.rs | 19 +++++++++++++++++++ src/lib/services/deezer/downloader.ts | 12 +++++++++--- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c38b356..0a13ba7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -37,6 +37,14 @@ fn read_audio_metadata(path: String) -> Result metadata::read_audio_metadata(&path) } +/// Re-encode cover image as baseline JPEG for broad player compatibility +#[tauri::command] +async fn reencode_cover_image(data: Vec, quality: u8) -> Result, String> { + tauri::async_runtime::spawn_blocking(move || tagger::reencode_cover_image(&data, quality)) + .await + .map_err(|e| format!("Re-encode task failed: {}", e))? +} + /// Decrypt Deezer track data (legacy - kept for backwards compatibility) #[tauri::command] async fn decrypt_deezer_track(data: Vec, track_id: String) -> Result, String> { @@ -405,6 +413,7 @@ pub fn run() { greet, tag_audio_file, read_audio_metadata, + reencode_cover_image, decrypt_deezer_track, download_and_decrypt_track, device_sync::index_and_compare, diff --git a/src-tauri/src/tagger.rs b/src-tauri/src/tagger.rs index a005619..9b3e575 100644 --- a/src-tauri/src/tagger.rs +++ b/src-tauri/src/tagger.rs @@ -2,8 +2,10 @@ use id3::{ frame::{Picture, PictureType}, Tag as ID3Tag, TagLike, Version, }; +use image::codecs::jpeg::JpegEncoder; use metaflac::Tag as FlacTag; use serde::{Deserialize, Serialize}; +use std::io::Cursor; use std::path::Path; /// Metadata structure for audio file tagging @@ -356,3 +358,20 @@ fn detect_mime_type_str(data: &[u8]) -> &'static str { "image/jpeg" } } + +/// Re-encode image data as a baseline (non-progressive) JPEG in sRGB. +/// This ensures compatibility with players like Rockbox that can't +/// decode progressive JPEGs or non-sRGB colorspaces. +pub fn reencode_cover_image(data: &[u8], quality: u8) -> Result, String> { + let img = image::load_from_memory(data) + .map_err(|e| format!("Failed to decode image: {}", e))?; + + let rgb = img.to_rgb8(); + + let mut buf = Cursor::new(Vec::new()); + let encoder = JpegEncoder::new_with_quality(&mut buf, quality); + rgb.write_with_encoder(encoder) + .map_err(|e| format!("Failed to encode JPEG: {}", e))?; + + Ok(buf.into_inner()) +} diff --git a/src/lib/services/deezer/downloader.ts b/src/lib/services/deezer/downloader.ts index e9418fb..0559d48 100644 --- a/src/lib/services/deezer/downloader.ts +++ b/src/lib/services/deezer/downloader.ts @@ -87,14 +87,20 @@ export async function downloadTrack( // Get user settings const appSettings = get(settings); - // Download cover art if enabled + // Download cover art if enabled, re-encode as baseline JPEG for player compatibility let coverData: Uint8Array | undefined; if ((appSettings.embedCoverArt || appSettings.saveCoverToFolder) && track.albumCoverUrl) { try { console.log('Downloading cover art...'); - coverData = await downloadCover(track.albumCoverUrl); + const rawCover = await downloadCover(track.albumCoverUrl); + const reencoded = await invoke('reencode_cover_image', { + data: Array.from(rawCover), + quality: appSettings.coverImageQuality + }); + coverData = new Uint8Array(reencoded); + console.log(`[ImageDownload] Re-encoded cover: ${rawCover.length} -> ${coverData.length} bytes (q=${appSettings.coverImageQuality})`); } catch (error) { - console.warn('Failed to download cover art:', error); + console.warn('Failed to download/re-encode cover art:', error); } }