Show the first notifiication finaaaaaly :D

This commit is contained in:
Anas Elgarhy 2023-02-17 08:09:52 +02:00
parent e7c080abb6
commit b75d56485c
No known key found for this signature in database
GPG key ID: 0501802A1D496528
9 changed files with 110 additions and 43 deletions

8
Cargo.lock generated
View file

@ -219,13 +219,13 @@ dependencies = [
"clap",
"confy",
"id3",
"image",
"log",
"lrc",
"notify-rust",
"pretty_env_logger",
"regex",
"serde",
"temp-file",
"test-context",
"thiserror",
"typed-builder",
@ -1557,12 +1557,6 @@ dependencies = [
"windows",
]
[[package]]
name = "temp-file"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4479870ee948e4c5c3fc4774b46aa73eedf97d84503a9b90922c1bb19a73a8d"
[[package]]
name = "tempfile"
version = "3.3.0"

View file

@ -10,8 +10,8 @@ serde = "1.0.152"
id3 = "1.6.0"
lrc = { version = "0.1.7", optional = true }
notify-rust = { version = "4.7.0", features = ["images"] }
image = "0.24.5"
regex = "1.7.1"
temp-file = "0.1.7"
typed-builder = "0.12.0"
log = { version = "0.4.17", optional = true }
pretty_env_logger = { version = "0.4.0", optional = true }

View file

@ -4,6 +4,7 @@ use cmus_notify::{
settings::Settings,
TrackCover,
};
#[cfg(feature = "debug")]
extern crate pretty_env_logger;
#[cfg(feature = "debug")]
@ -12,6 +13,8 @@ extern crate log;
macro_rules! sleep {
($time: expr) => {
#[cfg(feature = "debug")]
info!("sleeping for {} ms...", $time);
std::thread::sleep(std::time::Duration::from_millis($time));
};
}
@ -49,6 +52,8 @@ fn main() {
info!("Query command built: {:?}", query_command);
}
let mut notification = notify_rust::Notification::new();
// Initialize the buffer to store the response from cmus, to compare it with the next one.
let mut previous_response = CmusQueryResponse::default();
// Initialize the buffer to store the cover path, to compare it with the next one.
@ -63,9 +68,6 @@ fn main() {
std::process::exit(0)
} else {
// If the track info is the same as the previous one, just sleep for a while and try again.
#[cfg(feature = "debug")] {
info!("Cmus is not running, sleeping for {} ms...", settings.interval);
}
sleep!(settings.interval);
continue;
}
@ -78,7 +80,12 @@ fn main() {
// Update the previous response.
previous_response = response;
notification::show_notification(events, &settings, &mut previous_cover);
notification::show_notification(
events,
&settings,
&mut notification,
&mut previous_cover,
);
// TODO: Handle the error.
}
}

View file

@ -3,7 +3,7 @@ use crate::cmus::{Track, TrackStatus};
#[derive(Debug)]
pub enum CmusEvent {
StatusChanged(TrackStatus, Track),
StatusChanged(Track),
TrackChanged(Track),
VolumeChanged { left: u8, right: u8 },
PositionChanged(u32),

View file

@ -3,14 +3,14 @@ pub mod player_settings;
pub mod query;
use crate::cmus::query::CmusQueryResponse;
#[cfg(feature = "debug")]
use log::{debug, info};
use std::collections::HashMap;
use std::fmt::Debug;
use std::num::ParseIntError;
use std::str::FromStr;
use thiserror::Error;
use typed_builder::TypedBuilder;
#[cfg(feature = "debug")]
use log::{debug, info};
#[derive(Debug, PartialEq, Default)]
pub struct TrackMetadata {

View file

@ -73,7 +73,7 @@ impl CmusQueryResponse {
"Status changed: {:?} -> {:?}",
other_track.status, track.status
);
events.push(CmusEvent::StatusChanged(other_track.status, track));
events.push(CmusEvent::StatusChanged(track));
} else if track.position != other_track.position {
#[cfg(feature = "debug")]
debug!(

View file

@ -34,12 +34,11 @@ pub mod settings;
/// Err(error) => println!("Error: {}", error),
/// }
/// ```
pub fn get_embedded_art(track_path: &str) -> std::io::Result<Option<temp_file::TempFile>> {
pub fn get_embedded_art(track_path: &str) -> std::io::Result<Option<image::DynamicImage>> {
let tags = id3::Tag::read_from_path(track_path)
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
let Some(picture) = tags.pictures().next() else { return Ok(None); };
let temp_file = temp_file::TempFile::new()?;
Ok(Some(temp_file.with_contents(&*picture.data).map_err(
Ok(Some(image::load_from_memory(&picture.data).map_err(
|e| std::io::Error::new(std::io::ErrorKind::Other, e),
)?))
}
@ -105,10 +104,11 @@ pub fn search_for(
}
/// The cover of a track.
#[derive(Debug)]
pub enum TrackCover {
/// The cover is embedded in the track.
/// The `TempFile` object contains the contents of the embedded picture.
Embedded(temp_file::TempFile),
Embedded(image::DynamicImage),
/// The cover is an external file.
/// The `String` contains the absolute path of the external file.
External(String),
@ -116,6 +116,29 @@ pub enum TrackCover {
None,
}
impl TrackCover {
pub fn set_notification_image(&self, notification: &mut notify_rust::Notification) {
use TrackCover::*;
match self {
Embedded(cover) => {
#[cfg(feature = "debug")]
debug!("Setting the embedded cover as the notification image.");
let Ok(image) = notify_rust::Image::try_from(cover.clone()) else { return; };
notification.image_data(image);
}
External(cover_path) => {
#[cfg(feature = "debug")]
debug!("Setting the external cover as the notification image.");
let _ = notification.image(cover_path);
}
None => {
#[cfg(feature = "debug")]
debug!("The track does not have a cover.");
}
}
}
}
/// Returns the cover of a track.
/// If the track has an embedded cover, and `force_use_external_cover` is `false`, the embedded cover will be returned.
/// If the track does not have an embedded cover, and `no_use_external_cover` is `false`, the function will search for an external cover.
@ -172,7 +195,6 @@ fn search(search_directory: &str, matcher: &regex::Regex) -> std::io::Result<Opt
}
/// Replace all the placeholders in the template with their matching value.
#[inline]
pub fn process_template_placeholders(template: &String, track: &cmus::Track) -> String {
#[cfg(feature = "debug")]
{

View file

@ -1,13 +1,15 @@
use crate::cmus::events::CmusEvent;
use crate::cmus::{Track, TrackStatus};
use crate::settings::Settings;
use crate::TrackCover;
use crate::{process_template_placeholders, track_cover, TrackCover};
#[cfg(feature = "debug")]
use log::info;
use log::{info,debug};
#[inline(always)]
pub fn show_notification(
events: Vec<CmusEvent>,
settings: &Settings,
notification: &mut notify_rust::Notification,
previous_cover: &mut TrackCover,
) -> Result<(), notify_rust::error::Error> {
if events.is_empty() {
@ -16,36 +18,57 @@ pub fn show_notification(
return Ok(()); // No events to process.
}
let mut notification = notify_rust::Notification::new();
// Set the image of the notification.
previous_cover.set_notification_image(notification);
for event in events {
#[cfg(feature = "debug")]
info!("event: {:?}", event);
match event {
CmusEvent::StatusChanged(status) => {
println!("{:?}", status);
}
CmusEvent::TrackChanged(track) => {
println!("track change {:?}", track);
*previous_cover = track_cover(track.path.as_str(), settings.depth, settings.force_use_external_cover, settings.no_use_external_cover);
}
CmusEvent::VolumeChanged { left, right } if settings.show_player_notifications => {
println!("left: {}, right: {}", left, right);
}
CmusEvent::PositionChanged(position) => {
println!("position: {}", position);
}
CmusEvent::ShuffleChanged(shuffle) if settings.show_player_notifications => {
println!("shuffle: {:?}", shuffle);
}
CmusEvent::RepeatChanged(repeat) if settings.show_player_notifications => {
println!("repeat: {}", repeat);
}
CmusEvent::AAAMode(aaa_mode) if settings.show_player_notifications => {
println!("aaa_mode: {:?}", aaa_mode);
CmusEvent::StatusChanged(track) => {
#[cfg(feature = "debug")]
debug!("Status changed: {:?}", track.status);
build_status_notification(track, settings, notification)?;
notification.show()?;
}
/* CmusEvent::TrackChanged(track) => {
bulid_track_notification(track, settings, notification, previous_cover)?
}
CmusEvent::VolumeChanged { left, right } if settings.show_player_notifications => {
build_volume_notification(left, right, settings, notification)?
}
CmusEvent::PositionChanged(position) => todo!(),
CmusEvent::ShuffleChanged(shuffle) if settings.show_player_notifications => {
build_shuffle_notification(shuffle, settings, notification)?
}
CmusEvent::RepeatChanged(repeat) if settings.show_player_notifications => {
build_repeat_notification(repeat, settings, notification)?
}
CmusEvent::AAAMode(aaa_mode) if settings.show_player_notifications => {
build_aaa_mode_notification(aaa_mode, settings, notification)?
}
*/
_ => {}
}
notification.show()?;
}
Ok(())
}
#[inline(always)]
fn build_status_notification(
track: Track,
settings: &Settings,
notification: &mut notify_rust::Notification,
) -> Result<(), notify_rust::error::Error> {
// Set the summary and body of the notification.
notification
.summary(
process_template_placeholders(&settings.status_notification_summary, &track).as_str(),
)
.body(process_template_placeholders(&settings.status_notification_body, &track).as_str());
Ok(())
}

View file

@ -10,6 +10,9 @@ const NOTIFICATION_SUMMARY: &str = "{artist} - {title}";
const NOTIFICATION_APP_NAME: &str = "C* Music Player";
const DEFAULT_MAX_DEPTH: u8 = 3;
const DEFAULT_INTERVAL_TIME: u64 = 1000; // 1000 ms
const DEFAULT_STATUS_CHANGE_NOTIFICATION_BODY: &str = "<b>{status}</b>";
const DEFAULT_STATUS_CHANGE_NOTIFICATION_SUMMARY: &str = "Status changed";
const DEFAULT_STATUS_CHANGE_NOTIFICATION_TIMEOUT: u8 = 1;
const DEFAULT_VOLUME_CHANGE_NOTIFICATION_BODY: &str = "Volume changed to {volume}%";
const DEFAULT_VOLUME_CHANGE_NOTIFICATION_SUMMARY: &str = "Volume changed";
const DEFAULT_VOLUME_CHANGE_NOTIFICATION_TIMEOUT: u8 = 1;
@ -232,6 +235,19 @@ pub struct Settings {
/// you can use the placeholders like "{lyrics}" in the summary, it will be replaced with the lyrics.
#[arg(short = 'M', long, default_value = DEFAULT_LYRICS_NOTIFICATION_SUMMARY)]
pub lyrics_notification_summary: String,
/// The status change notification body.
/// you can use the placeholders like "{status}" in the body, it will be replaced with the aaa mode.
///
/// If you leave it empty, the notification will not be shown.
#[arg(short = 'O', long, default_value = DEFAULT_STATUS_CHANGE_NOTIFICATION_BODY)]
pub status_notification_body: String,
/// The status change notification summary.
/// you can use the placeholders like "{status}" in the summary, it will be replaced with the aaa mode.
#[arg(short = 'P', long, default_value = DEFAULT_STATUS_CHANGE_NOTIFICATION_SUMMARY)]
pub status_notification_summary: String,
/// The time out of the status change notification, in seconds.
#[arg(short = 'Q', long, default_value_t = DEFAULT_STATUS_CHANGE_NOTIFICATION_TIMEOUT)]
pub status_notification_timeout: u8,
}
impl Default for Settings {
@ -242,6 +258,7 @@ impl Default for Settings {
show_track_cover: true,
notification_static_icon: None,
cover_path: None,
#[cfg(feature = "lyrics")]
lyrics_path: None,
depth: DEFAULT_MAX_DEPTH,
app_name: NOTIFICATION_APP_NAME.to_string(),
@ -275,6 +292,9 @@ impl Default for Settings {
lyrics_notification_body: DEFAULT_LYRICS_NOTIFICATION_BODY.to_string(),
#[cfg(feature = "lyrics")]
lyrics_notification_summary: DEFAULT_LYRICS_NOTIFICATION_SUMMARY.to_string(),
status_notification_body: DEFAULT_STATUS_CHANGE_NOTIFICATION_BODY.to_string(),
status_notification_summary: DEFAULT_STATUS_CHANGE_NOTIFICATION_SUMMARY.to_string(),
status_notification_timeout: DEFAULT_STATUS_CHANGE_NOTIFICATION_TIMEOUT,
}
}
}
@ -285,6 +305,7 @@ impl Settings {
/// The args will override the config.
/// If the config file is not found, create a new one, and use the default values.
/// If the config file is found, but the config is invalid, use the default values.
#[inline(always)]
pub fn load_config_and_parse_args() -> Self {
#[cfg(feature = "debug")]
info!("Loading config and parsing args...");