mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 15:25:46 +00:00
fix(windows): transcode WebP cover to PNG for the media controls (#1102)
Windows SMTC could not render our cached WebP album covers: souvlaki loads the file and SetThumbnail/set_metadata succeed, but the lock screen and Quick Settings media tile showed a blank cover, because the OS thumbnail decoder does not handle WebP even with the Store WebP extension installed. Transcode local file:// WebP covers to PNG (libwebp decode then image PNG encode, into a single reusable temp file) before handing them to the OS media controls, gated to Windows. macOS (ImageIO) and Linux pass through unchanged.
This commit is contained in:
@@ -84,6 +84,15 @@ pub(crate) fn mpris_set_metadata(
|
||||
let duration = duration_secs.map(Duration::from_secs_f64);
|
||||
let mut guard = controls.lock().unwrap();
|
||||
let Some(ctrl) = guard.as_mut() else { return Ok(()); };
|
||||
|
||||
// #1102: Windows SMTC cannot render our cached WebP covers. souvlaki loads
|
||||
// the file and SetThumbnail/set_metadata succeed, but the lock screen and
|
||||
// Quick-Settings media tile show a blank cover (the OS thumbnail decoder
|
||||
// does not handle WebP, even with the Store WebP extension installed).
|
||||
// Transcode local WebP covers to PNG for the OS media controls; macOS
|
||||
// (ImageIO) decodes WebP fine, so other platforms pass through unchanged.
|
||||
let cover_url = smtc_cover_url(cover_url);
|
||||
|
||||
ctrl.set_metadata(MediaMetadata {
|
||||
title: title.as_deref(),
|
||||
artist: artist.as_deref(),
|
||||
@@ -94,6 +103,48 @@ pub(crate) fn mpris_set_metadata(
|
||||
.map_err(|e| format!("MPRIS set_metadata failed: {e:?}"))
|
||||
}
|
||||
|
||||
/// Rewrite a cached WebP cover URL to a PNG the OS media controls can render.
|
||||
/// Windows SMTC cannot decode WebP thumbnails (#1102); other platforms and any
|
||||
/// non-`file://`/non-WebP URL pass through unchanged.
|
||||
fn smtc_cover_url(cover_url: Option<String>) -> Option<String> {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
if let Some(url) = cover_url.as_deref() {
|
||||
if let Some(path) = url.strip_prefix("file://") {
|
||||
let is_webp = std::path::Path::new(path)
|
||||
.extension()
|
||||
.is_some_and(|e| e.eq_ignore_ascii_case("webp"));
|
||||
if is_webp {
|
||||
match webp_file_to_temp_png(path) {
|
||||
Ok(png) => return Some(format!("file://{png}")),
|
||||
Err(e) => {
|
||||
crate::app_eprintln!("[mpris] cover WebP->PNG transcode failed: {e}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cover_url
|
||||
}
|
||||
|
||||
/// Decode a WebP file (libwebp, the same codec that wrote the cover cache) and
|
||||
/// re-encode it as a PNG in the temp dir, returning the native path. A single
|
||||
/// reusable file is fine: souvlaki reads it synchronously inside `set_metadata`,
|
||||
/// and the controls mutex serializes calls so it is never written concurrently.
|
||||
#[cfg(target_os = "windows")]
|
||||
fn webp_file_to_temp_png(webp_path: &str) -> Result<String, String> {
|
||||
let bytes = std::fs::read(webp_path).map_err(|e| e.to_string())?;
|
||||
let decoded = webp::Decoder::new(&bytes)
|
||||
.decode()
|
||||
.ok_or_else(|| "WebP decode returned None".to_string())?;
|
||||
let img = decoded.to_image();
|
||||
let out = std::env::temp_dir().join("psysonic-smtc-cover.png");
|
||||
img.save_with_format(&out, image::ImageFormat::Png)
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(out.to_string_lossy().into_owned())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) fn mpris_set_playback(
|
||||
controls: tauri::State<MprisControls>,
|
||||
|
||||
Reference in New Issue
Block a user