diff --git a/src/lib.rs b/src/lib.rs index de5f21a2..2ee6d79a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,7 +146,8 @@ impl MpvCommand { /// /// # Returns /// A string slice representing the command. - #[must_use] pub const fn as_str(&self) -> &str { + #[must_use] + pub const fn as_str(&self) -> &str { match self { Self::SetProperty => "set_property", Self::PlaylistNext => "playlist-next", diff --git a/src/main.rs b/src/main.rs index 29d42c1a..1b4cc022 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,14 @@ use clap::{Parser, Subcommand}; -use serde_json::json; -use std::io::{self}; -use std::path::PathBuf; -use tracing::{debug, error, info}; - use mrc::set_property; use mrc::SOCKET_PATH; use mrc::{ get_property, loadfile, playlist_clear, playlist_move, playlist_next, playlist_prev, playlist_remove, quit, seek, }; +use serde_json::json; +use std::io::{self, Write}; +use std::path::PathBuf; +use tracing::{debug, error, info}; #[derive(Parser)] #[command(author, version, about)] @@ -51,6 +50,7 @@ enum CommandOptions { Move { /// The index of the item to move index1: usize, + /// The index to move the item to index2: usize, }, @@ -89,6 +89,9 @@ enum CommandOptions { /// The properties to fetch properties: Vec, }, + + /// Enter interactive mode to send commands to MPV IPC + Interactive, } #[tokio::main] @@ -108,7 +111,6 @@ async fn main() -> io::Result<()> { info!("Playing media at index: {}", idx); set_property("playlist-pos", &json!(idx), None).await?; } - info!("Unpausing playback"); set_property("pause", &json!(false), None).await?; } @@ -171,6 +173,7 @@ async fn main() -> io::Result<()> { error!("{}", e); return Err(io::Error::new(io::ErrorKind::InvalidInput, e)); } + info!("Adding {} files to the playlist", filenames.len()); for filename in filenames { loadfile(&filename, true, None).await?; @@ -195,6 +198,145 @@ async fn main() -> io::Result<()> { } } } + + CommandOptions::Interactive => { + println!("Entering interactive mode. Type 'exit' to quit."); + let stdin = io::stdin(); + let mut stdout = io::stdout(); + + loop { + print!("mpv> "); + stdout.flush()?; + let mut input = String::new(); + stdin.read_line(&mut input)?; + let trimmed = input.trim(); + + if trimmed.eq_ignore_ascii_case("exit") { + println!("Exiting interactive mode."); + break; + } + + + // I don't like this either, but it looks cleaner than a multi-line + // print macro just cramped in here. + let commands = vec![ + ( + "play [index]", + "Play or unpause playback, optionally at the specified index", + ), + ("pause", "Pause playback"), + ("stop", "Stop playback and quit MPV"), + ("next", "Skip to the next item in the playlist"), + ("prev", "Skip to the previous item in the playlist"), + ("seek ", "Seek to the specified position"), + ("clear", "Clear the playlist"), + ("list", "List all items in the playlist"), + ("add ", "Add files to the playlist"), + ("get ", "Get the specified property"), + ( + "set ", + "Set the specified property to a value", + ), + ("exit", "Quit interactive mode"), + ]; + + if trimmed.eq_ignore_ascii_case("help") { + println!("Valid commands:"); + for (command, description) in commands { + println!(" {} - {}", command, description); + } + continue; + } + + let parts: Vec<&str> = trimmed.split_whitespace().collect(); + match parts.as_slice() { + ["play"] => { + info!("Unpausing playback"); + set_property("pause", &json!(false), None).await?; + } + + ["play", index] => { + if let Ok(idx) = index.parse::() { + info!("Playing media at index: {}", idx); + set_property("playlist-pos", &json!(idx), None).await?; + set_property("pause", &json!(false), None).await?; + } else { + println!("Invalid index: {}", index); + } + } + + ["pause"] => { + info!("Pausing playback"); + set_property("pause", &json!(true), None).await?; + } + + ["stop"] => { + info!("Pausing playback"); + quit(None).await?; + } + + ["next"] => { + info!("Skipping to next item in the playlist"); + playlist_next(None).await?; + } + + ["prev"] => { + info!("Skipping to previous item in the playlist"); + playlist_prev(None).await?; + } + + ["seek", seconds] => { + if let Ok(sec) = seconds.parse::() { + info!("Seeking to {} seconds", sec); + seek(sec.into(), None).await?; + } else { + println!("Invalid seconds: {}", seconds); + } + } + + ["clear"] => { + info!("Clearing the playlist"); + playlist_clear(None).await?; + } + + ["list"] => { + info!("Listing playlist items"); + if let Some(data) = get_property("playlist", None).await? { + println!("{}", serde_json::to_string_pretty(&data)?); + } + } + + ["add", files @ ..] => { + if files.is_empty() { + println!("No files provided to add to the playlist"); + } else { + info!("Adding {} files to the playlist", files.len()); + for file in files { + loadfile(file, true, None).await?; + } + } + } + + ["get", property] => { + if let Some(data) = get_property(property, None).await? { + println!("{property}: {data}"); + } + } + + ["set", property, value] => { + let json_value = serde_json::from_str::(value) + .unwrap_or_else(|_| json!(value)); + set_property(property, &json_value, None).await?; + println!("Set {property} to {value}"); + } + + _ => { + println!("Unknown command: {}", trimmed); + println!("Valid commands: play , pause, stop, next, prev, seek , clear, list, add , get , set , exit"); + } + } + } + } } Ok(())