Improve track notifications with artwork and quality info

This commit is contained in:
vicrodh committed 2026-01-11 12:38:21 -06:00
1 parent 2d908c7b12
commit bc7e13d756
3 files changed
+123 -14

No files matched your search

+1 -1
View File
@@ -22,7 +22,7 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
# HTTP Client
reqwest = { version = "0.12", features = ["json", "cookies", "rustls-tls", "stream"], default-features = false }
reqwest = { version = "0.12", features = ["json", "cookies", "rustls-tls", "stream", "blocking"], default-features = false }
# Async Runtime
tokio = { version = "1", features = ["full"] }
+107 -9
View File
@@ -1,28 +1,126 @@
//! System notification commands
use notify_rust::Notification;
use md5::{Digest, Md5};
use std::path::PathBuf;
use std::fs;
use std::io::Write;
/// Show a track change notification
/// Cache directory for notification artwork
fn get_artwork_cache_dir() -> Result<PathBuf, String> {
let cache_dir = dirs::cache_dir()
.ok_or_else(|| "Could not find cache directory".to_string())?
.join("qbz")
.join("artwork");
fs::create_dir_all(&cache_dir)
.map_err(|e| format!("Failed to create artwork cache dir: {}", e))?;
Ok(cache_dir)
}
/// Download artwork to cache and return the path
fn cache_artwork(url: &str) -> Result<PathBuf, String> {
// Create a hash of the URL for the filename
let mut hasher = Md5::new();
hasher.update(url.as_bytes());
let hash = format!("{:x}", hasher.finalize());
let cache_dir = get_artwork_cache_dir()?;
let cache_path = cache_dir.join(format!("{}.jpg", hash));
// Return cached file if exists
if cache_path.exists() {
return Ok(cache_path);
}
// Download the image
let response = reqwest::blocking::Client::new()
.get(url)
.timeout(std::time::Duration::from_secs(5))
.send()
.map_err(|e| format!("Failed to download artwork: {}", e))?;
if !response.status().is_success() {
return Err(format!("Failed to download artwork: HTTP {}", response.status()));
}
let bytes = response.bytes()
.map_err(|e| format!("Failed to read artwork bytes: {}", e))?;
// Write to cache
let mut file = fs::File::create(&cache_path)
.map_err(|e| format!("Failed to create artwork cache file: {}", e))?;
file.write_all(&bytes)
.map_err(|e| format!("Failed to write artwork cache: {}", e))?;
Ok(cache_path)
}
/// Format quality info for notification
fn format_quality(bit_depth: Option<u32>, sample_rate: Option<u32>) -> String {
match (bit_depth, sample_rate) {
(Some(bits), Some(rate)) if bits >= 24 || rate > 48 => {
format!("Hi-Res • {}-bit/{}kHz", bits, rate)
}
(Some(bits), Some(rate)) => {
format!("CD Quality • {}-bit/{}kHz", bits, rate)
}
_ => String::new()
}
}
/// Show a track change notification with artwork
#[tauri::command]
pub fn show_track_notification(
title: String,
artist: String,
album: String,
_artwork_url: Option<String>,
artwork_url: Option<String>,
bit_depth: Option<u32>,
sample_rate: Option<u32>,
) -> Result<(), String> {
log::info!("Command: show_track_notification - {} by {}", title, artist);
let body = if album.is_empty() {
artist.clone()
} else {
format!("{} • {}", artist, album)
};
// Build body with artist, album, and quality
let mut body_parts = Vec::new();
Notification::new()
if !artist.is_empty() {
body_parts.push(artist.clone());
}
if !album.is_empty() {
body_parts.push(album.clone());
}
let quality = format_quality(bit_depth, sample_rate);
if !quality.is_empty() {
body_parts.push(quality);
}
let body = body_parts.join(" • ");
let mut notification = Notification::new();
notification
.summary(&title)
.body(&body)
.appname("QBZ")
.timeout(3000) // 3 seconds
.timeout(4000); // 4 seconds
// Try to add artwork
if let Some(url) = artwork_url {
match cache_artwork(&url) {
Ok(path) => {
if let Some(path_str) = path.to_str() {
notification.image_path(path_str);
log::debug!("Notification artwork set: {}", path_str);
}
}
Err(e) => {
log::warn!("Could not cache artwork for notification: {}", e);
}
}
}
notification
.show()
.map_err(|e| format!("Failed to show notification: {}", e))?;
+15 -4
View File
@@ -98,8 +98,15 @@ export async function playTrack(
coverUrl: track.artwork || null
});
// Show system notification
await showTrackNotification(track.title, track.artist, track.album, track.artwork);
// Show system notification with artwork and quality info
await showTrackNotification(
track.title,
track.artist,
track.album,
track.artwork,
track.bitDepth,
track.samplingRate
);
// Update Last.fm
await updateLastfmNowPlaying(track.title, track.artist, track.album, track.duration, track.id);
@@ -152,14 +159,18 @@ export async function showTrackNotification(
title: string,
artist: string,
album: string,
artworkUrl?: string
artworkUrl?: string,
bitDepth?: number,
sampleRate?: number
): Promise<void> {
try {
await invoke('show_track_notification', {
title,
artist,
album,
artworkUrl: artworkUrl || null
artworkUrl: artworkUrl || null,
bitDepth: bitDepth || null,
sampleRate: sampleRate || null
});
} catch (err) {
console.error('Failed to show track notification:', err);