extern crate libnotify; extern crate mpd; use clap::{Parser, Subcommand}; use libnotify::Notification; use mpd::{Client, Query, Song, State, Term}; use std::time::Duration; #[derive(Parser)] #[command(author, version, about, long_about = "Banaan")] #[command(propagate_version = true)] struct Cli { #[command(subcommand)] command: Option, #[arg(short, long, global = true)] verbose: bool, ///hostname where MPD listens at #[arg(short = 'H', long, global = true)] host: Option, } #[derive(Subcommand)] enum Commands { /// Toggle MPD stream Toggle {}, /// Skip to the next track Next {}, /// Revert to the previous track Prev {}, /// Stops playing Stop {}, /// Play queueueu Play { track: Option }, /// Set or get crossfade Crossfade { seconds: Option }, ///Update still needs some work Update {}, /// Return currently playing song Current {}, /// Query database Search { ///Search query #[arg(trailing_var_arg = true)] query: Vec, ///Only return the first n results #[arg(short, long)] max: Option, // #[arg(short, long)] // append: bool, // #[arg(short, long)] // insert: Option, }, /// Query database differently Find { ///Search query #[arg(trailing_var_arg = true)] query: Vec, ///Only return the first n results #[arg(short, long)] max: Option, #[arg(short, long)] append: bool, }, /// List items in the current queueueu List { #[arg(short, long)] file: bool, }, // Add { // #[arg(short, long)] // position: Option, // }, /// Shuffles the current queueue Shuffle {}, } fn main() { libnotify::init("noise").unwrap(); let n = libnotify::Notification::new("Noise", None, None); let cli = Cli::parse(); let host = match cli.host { Some(host) => host, None => String::from("localhost:6600"), }; // let mut conn = Client::connect(host).unwrap(); let mut conn = Client::connect(host).expect("Connection failed"); // let mut conn = match Client::connect(host) { // Ok(conn) => conn, // Err(error) => panic!("yo fucked up: {}", error.to_string()), // }; if let Some(cmd) = &cli.command { match cmd { Commands::Play { track } => { if let Some(i) = track { conn.switch(*i).unwrap(); } else { conn.play().unwrap(); } let status = get_status(conn, cli.verbose, n); println!("{status}"); } Commands::Stop {} => { conn.stop().unwrap(); } Commands::Toggle {} => { if conn.status().unwrap().state == State::Stop { conn.play().unwrap(); } else { conn.toggle_pause().unwrap(); } let status = get_status(conn, cli.verbose, n); println!("{status}"); } Commands::Next {} => { conn.next().unwrap(); let status = get_status(conn, cli.verbose, n); println!("{status}"); } Commands::Prev {} => { conn.prev().unwrap(); let status = get_status(conn, cli.verbose, n); println!("{status}"); } Commands::List { file } => conn.queue().unwrap().iter().for_each(|x| { println!( "{:<02} {}", x.place.unwrap().pos, if *file { x.file.clone() } else { format_song(x.clone()) } ) }), Commands::Update {} => { let thing = conn.update().unwrap(); println!("{thing}") } Commands::Current {} => { let status = get_status(conn, cli.verbose, n); println!("{status}"); } Commands::Search { query, max, // append, // insert, } => { let mut songs: Vec = vec![]; let query = query.join(" "); // I want to return all results by default let window = ( 0, if let Some(n) = max { *n } else { conn.stats().unwrap().songs }, ); // let queue_length = conn.status().unwrap().queue_len; // conn.search(&Query::new().and(Term::Any, query), window) // .unwrap() // .iter() // .for_each(|x| songs.push(x.clone())); // songs.iter().rev().for_each(|x| { // conn.insert(x, usize::try_from(queue_length).unwrap()) // .unwrap(); // }); //conn.insert(, usize::try_from(queue_length).unwrap()) conn.search(&Query::new().and(Term::Any, query), window) .unwrap() .iter() .for_each(|x| songs.push(x.clone())); songs.iter().for_each(|x| println!("{}", x.file)) } Commands::Find { query, max, append } => { let mut songs: Vec = vec![]; let query = query.join(" "); let window = ( 0, if let Some(n) = max { *n } else { conn.stats().unwrap().songs }, ); if *append { conn.findadd(&Query::new().and(Term::Any, query)).unwrap(); } else { conn.find(&Query::new().and(Term::Any, query), window) .unwrap() .iter() .for_each(|x| songs.push(x.clone())); songs.iter().for_each(|x| println!("{}", x.file)) } } Commands::Crossfade { seconds } => { if let Some(crossfade) = seconds { conn.crossfade(*crossfade).unwrap() } else { if let Some(crossfade) = conn.status().unwrap().crossfade { println!("Crossfade: {}", crossfade.as_secs()); } else { println!("Crossfade disabled") } } } Commands::Shuffle {} => { send_notification("Shuffling queueueu...".to_string(), n); println!("Shuffling queueueu..."); conn.shuffle(..).unwrap(); } } } else { let status = get_status(conn, cli.verbose, n); println!("{}", &status); libnotify::uninit(); } } fn format_song(song: Song) -> String { format!("{} - {}", song.artist.unwrap(), song.title.unwrap()) } ///Bool to String fn bts(b: bool) -> &'static str { b.then(|| "on").unwrap_or("off") } fn get_status(mut client: Client, verbose: bool, notify: Notification) -> String { let status = client.status().unwrap(); let repeat = bts(status.repeat); let single = bts(status.single); let random = bts(status.random); let consume = bts(status.consume); let duration = match status.time { Some((stamp, total)) => { let stamp = to_mins_string(stamp); let total = to_mins_string(total); format!("{stamp}/{total}") } None => "".to_string(), }; let mut output = vec![]; if let Some(song) = client.currentsong().unwrap() { output.push(format_song(song)) } else { output.push("No song playing".to_string()) } send_notification(output.first().unwrap().clone(), notify); if verbose { output.push(format!( "volume: {vol:<6} repeat: {repeat:<6} random: {rnd:<6} single: {single:<6} consume: {consume:<6}", vol = {format!("{}%", status.volume)}, repeat = repeat, rnd = random, single = single, consume = consume )); output.push(format!( "[{status}] {duration}", status = { match client.status().unwrap().state { State::Stop => "stopped", State::Pause => "paused", State::Play => "playing", } } )); } output.join("\n") } fn send_notification(body: String, notify: Notification) -> String { let _ = notify.update("Noise", body.as_str(), None); notify.show().unwrap(); body } fn to_mins_string(dur: Duration) -> String { let seconds = dur.as_secs(); let minutes = seconds / 60; let rem_seconds = seconds % 60; format!("{minutes:02}:{rem_seconds:02}") }