mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 14:55:43 +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.
(cherry picked from commit 76d028127d)
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