noise-rs/src/main.rs

260 lines
7.3 KiB
Rust
Raw Normal View History

2025-06-13 13:09:18 +02:00
extern crate libnotify;
2025-06-07 11:24:20 +02:00
extern crate mpd;
2025-06-07 11:43:42 +02:00
use clap::{Parser, Subcommand};
2025-06-18 09:30:08 +02:00
use libnotify::Notification;
use mpd::{Client, Query, Song, State, Term};
2025-06-18 09:30:08 +02:00
use std::time::Duration;
2025-06-07 11:24:20 +02:00
2025-06-07 11:43:42 +02:00
#[derive(Parser)]
2025-06-08 22:53:08 +02:00
#[command(author, version, about, long_about = "Banaan")]
2025-06-07 11:43:42 +02:00
#[command(propagate_version = true)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
2025-06-08 22:53:08 +02:00
#[arg(short, long, global = true)]
verbose: bool,
2025-06-13 13:09:18 +02:00
///hostname where MPD listens at
2025-06-08 22:53:08 +02:00
#[arg(short = 'H', long, global = true)]
host: Option<String>,
2025-06-07 11:43:42 +02:00
}
#[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
2025-06-08 22:53:08 +02:00
Play { track: Option<u32> },
/// Set or get crossfade
2025-06-08 22:53:08 +02:00
Crossfade { seconds: Option<i64> },
///Update still needs some work
Update,
/// Return currently playing song
Current,
2025-06-19 17:21:13 +02:00
/// Clear current queueueu
Clear,
/// Query database
Search {
2025-06-08 22:53:08 +02:00
///Search query
#[arg(trailing_var_arg = true)]
query: Vec<String>,
2025-06-08 22:53:08 +02:00
///Only return the first n results
#[arg(short, long)]
max: Option<u32>,
2025-06-18 09:30:08 +02:00
// #[arg(short, long)]
// append: bool,
// #[arg(short, long)]
// insert: Option<u32>,
2025-06-13 13:09:18 +02:00
},
/// Query database differently
Find {
///Search query
#[arg(trailing_var_arg = true)]
query: Vec<String>,
///Only return the first n results
#[arg(short, long)]
max: Option<u32>,
2025-06-18 09:30:08 +02:00
#[arg(short, long)]
append: bool,
},
2025-06-13 13:09:18 +02:00
/// List items in the current queueueu
List {
#[arg(short, long)]
file: bool,
},
2025-06-18 09:30:08 +02:00
// Add {
// #[arg(short, long)]
// position: Option<u32>,
// },
/// Shuffles the current queueue
Shuffle,
2025-06-07 11:24:20 +02:00
}
fn main() {
2025-06-18 09:30:08 +02:00
libnotify::init("noise").unwrap();
let n = libnotify::Notification::new("Noise", None, None);
2025-06-07 11:43:42 +02:00
let cli = Cli::parse();
2025-06-21 21:16:26 +02:00
let host = cli.host.unwrap_or("localhost:6600".into());
2025-06-08 22:53:08 +02:00
let mut conn = Client::connect(host).expect("Connection failed");
2025-06-13 13:09:18 +02:00
if let Some(cmd) = &cli.command {
match cmd {
Commands::Play { track } => {
if let Some(i) = track {
conn.switch(*i).unwrap();
} else {
conn.play().unwrap();
}
}
Commands::Stop => {
conn.stop().unwrap();
}
Commands::Toggle => {
if conn.status().unwrap().state == State::Stop {
conn.play().unwrap();
} else {
conn.toggle_pause().unwrap();
}
2025-06-18 09:30:08 +02:00
}
Commands::Next => conn.next().unwrap(),
Commands::Prev => conn.prev().unwrap(),
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();
2025-06-13 13:09:18 +02:00
println!("{thing}")
}
Commands::Current => (), // this is basically the same as having no command
Commands::Clear => conn.clear().unwrap(),
Commands::Search { query, max } => {
let query = query.join(" ");
let max = max.unwrap_or(conn.stats().unwrap().songs);
2025-06-18 09:30:08 +02:00
conn.search(&Query::new().and(Term::Any, query), (0, max))
2025-06-18 09:30:08 +02:00
.unwrap()
.iter()
.for_each(|x| println!("{}", x.file));
2025-06-13 13:09:18 +02:00
}
2025-06-18 09:30:08 +02:00
Commands::Find { query, max, append } => {
2025-06-13 13:09:18 +02:00
let query = query.join(" ");
let max = max.unwrap_or(conn.stats().unwrap().songs);
2025-06-18 09:30:08 +02:00
if *append {
conn.findadd(&Query::new().and(Term::Any, query)).unwrap();
} else {
conn.find(&Query::new().and(Term::Any, query), (0, max))
2025-06-18 09:30:08 +02:00
.unwrap()
.iter()
.for_each(|x| println!("{}", x.file));
2025-06-18 09:30:08 +02:00
}
}
Commands::Crossfade { seconds } => {
if let Some(crossfade) = seconds {
conn.crossfade(*crossfade).unwrap();
} else {
println!(
"Crossfade: {}",
conn.status()
.unwrap()
.crossfade
.map_or("Disabled".into(), |d| d.as_secs().to_string())
);
}
}
Commands::Shuffle => {
send_notification("Shuffling queueueu...", &n);
println!("Shuffling queueueu...");
conn.shuffle(..).unwrap();
}
2025-06-07 11:43:42 +02:00
}
if let Commands::Stop
| Commands::List { .. }
| Commands::Update
| Commands::Clear
| Commands::Search { .. }
| Commands::Find { .. }
| Commands::Crossfade { .. }
| Commands::Shuffle = cmd
{
return;
}
2025-06-07 11:43:42 +02:00
}
println!("{}", get_status(conn, cli.verbose, n));
2025-06-07 11:24:20 +02:00
}
fn format_song(song: Song) -> String {
format!("{} - {}", song.artist.unwrap(), song.title.unwrap())
}
2025-06-08 22:53:08 +02:00
trait QuickFmt {
fn quick_fmt(&self) -> &'static str;
2025-06-08 22:53:08 +02:00
}
impl QuickFmt for bool {
fn quick_fmt(&self) -> &'static str {
if *self {
"on"
} else {
"off"
2025-06-08 22:53:08 +02:00
}
}
}
2025-06-08 22:53:08 +02:00
fn get_status(mut client: Client, verbose: bool, notify: Notification) -> String {
let current_song = if let Some(song) = client.currentsong().unwrap() {
format_song(song)
2025-06-08 22:53:08 +02:00
} else {
"No song playing".into()
};
send_notification(&current_song, &notify);
if !verbose {
return current_song;
2025-06-08 22:53:08 +02:00
}
let mut output = Vec::with_capacity(3);
output.push(current_song);
2025-06-18 09:30:08 +02:00
let status = client.status().unwrap();
2025-06-08 22:53:08 +02:00
output.push(format!(
"volume: {vol:<6} repeat: {repeat:<6} random: {rnd:<6} single: {single:<6} consume: {consume:<6}",
vol = format!("{}%", status.volume),
repeat = status.repeat.quick_fmt(),
rnd = status.random.quick_fmt(),
single = status.single.quick_fmt(),
consume = status.consume.quick_fmt()
));
let state = match status.state {
State::Stop => "stopped",
State::Pause => "paused",
State::Play => "playing",
};
let duration = status.time.map_or("".into(), |(stamp, total)| {
let stamp = to_mins_string(stamp);
let total = to_mins_string(total);
format!("{stamp}/{total}")
});
output.push(format!("[{state}] {duration}"));
2025-06-08 22:53:08 +02:00
output.join("\n")
}
fn send_notification(body: &str, notify: &Notification) {
let _ = notify.update("Noise", body, None);
2025-06-18 09:30:08 +02:00
notify.show().unwrap();
}
2025-06-08 22:53:08 +02:00
fn to_mins_string(dur: Duration) -> String {
let seconds = dur.as_secs();
let minutes = seconds / 60;
let rem_seconds = seconds % 60;
2025-06-13 13:09:18 +02:00
format!("{minutes:02}:{rem_seconds:02}")
2025-06-08 22:53:08 +02:00
}