Show the first notifiication finaaaaaly :D
This commit is contained in:
parent
e7c080abb6
commit
b75d56485c
9 changed files with 110 additions and 43 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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!(
|
||||
|
|
32
src/lib.rs
32
src/lib.rs
|
@ -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: ®ex::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")]
|
||||
{
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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...");
|
||||
|
|
Loading…
Reference in a new issue