Misc: refactor + serde serialization derives

- Split content into more files
- Refactor TypeHandler impls
- Add serialization traits to public structs/enums
This commit is contained in:
Oystein Kristoffer Tveit 2024-04-16 22:48:27 +02:00
parent 6d2575b940
commit f5c9674b78
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
5 changed files with 1014 additions and 1097 deletions

View File

@ -12,6 +12,7 @@ edition = "2021"
[dependencies] [dependencies]
serde_json = "1.0.104" serde_json = "1.0.104"
log = "0.4.19" log = "0.4.19"
serde = { version = "1.0.197", features = ["derive"] }
[dev-dependencies] [dev-dependencies]
env_logger = "0.10.0" env_logger = "0.10.0"

749
src/api.rs Normal file
View File

@ -0,0 +1,749 @@
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::collections::HashMap;
use std::fmt::{self, Display};
use std::io::{BufReader, Read};
use std::os::unix::net::UnixStream;
use crate::ipc::{
get_mpv_property, get_mpv_property_string, listen, listen_raw, observe_mpv_property,
run_mpv_command, set_mpv_property, unobserve_mpv_property,
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Event {
Shutdown,
StartFile,
EndFile,
FileLoaded,
TracksChanged,
TrackSwitched,
Idle,
Pause,
Unpause,
Tick,
VideoReconfig,
AudioReconfig,
MetadataUpdate,
Seek,
PlaybackRestart,
PropertyChange { id: usize, property: Property },
ChapterChange,
ClientMessage { args: Vec<String> },
Unimplemented,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Property {
Path(Option<String>),
Pause(bool),
PlaybackTime(Option<f64>),
Duration(Option<f64>),
Metadata(Option<HashMap<String, MpvDataType>>),
Unknown { name: String, data: MpvDataType },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MpvCommand {
LoadFile {
file: String,
option: PlaylistAddOptions,
},
LoadList {
file: String,
option: PlaylistAddOptions,
},
PlaylistClear,
PlaylistMove {
from: usize,
to: usize,
},
Observe {
id: isize,
property: String,
},
PlaylistNext,
PlaylistPrev,
PlaylistRemove(usize),
PlaylistShuffle,
Quit,
ScriptMessage(Vec<String>),
ScriptMessageTo {
target: String,
args: Vec<String>,
},
Seek {
seconds: f64,
option: SeekOptions,
},
Stop,
Unobserve(isize),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum MpvDataType {
Array(Vec<MpvDataType>),
Bool(bool),
Double(f64),
HashMap(HashMap<String, MpvDataType>),
Null,
Playlist(Playlist),
String(String),
Usize(usize),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum NumberChangeOptions {
Absolute,
Increase,
Decrease,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum PlaylistAddOptions {
Replace,
Append,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum PlaylistAddTypeOptions {
File,
Playlist,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum SeekOptions {
Relative,
Absolute,
RelativePercent,
AbsolutePercent,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Switch {
On,
Off,
Toggle,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ErrorCode {
MpvError(String),
JsonParseError(String),
ConnectError(String),
JsonContainsUnexptectedType,
UnexpectedResult,
UnexpectedValue,
MissingValue,
UnsupportedType,
ValueDoesNotContainBool,
ValueDoesNotContainF64,
ValueDoesNotContainHashMap,
ValueDoesNotContainPlaylist,
ValueDoesNotContainString,
ValueDoesNotContainUsize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlaylistEntry {
pub id: usize,
pub filename: String,
pub title: String,
pub current: bool,
}
pub struct Mpv {
pub(crate) stream: UnixStream,
pub(crate) reader: BufReader<UnixStream>,
pub(crate) name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Playlist(pub Vec<PlaylistEntry>);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Error(pub ErrorCode);
impl Drop for Mpv {
fn drop(&mut self) {
self.disconnect();
}
}
impl fmt::Debug for Mpv {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_tuple("Mpv").field(&self.name).finish()
}
}
impl Clone for Mpv {
fn clone(&self) -> Self {
let stream = self.stream.try_clone().expect("cloning UnixStream");
let cloned_stream = stream.try_clone().expect("cloning UnixStream");
Mpv {
stream,
reader: BufReader::new(cloned_stream),
name: self.name.clone(),
}
}
fn clone_from(&mut self, source: &Self) {
let stream = source.stream.try_clone().expect("cloning UnixStream");
let cloned_stream = stream.try_clone().expect("cloning UnixStream");
*self = Mpv {
stream,
reader: BufReader::new(cloned_stream),
name: source.name.clone(),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl std::error::Error for Error {}
impl Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ErrorCode::ConnectError(ref msg) => f.write_str(&format!("ConnectError: {}", msg)),
ErrorCode::JsonParseError(ref msg) => f.write_str(&format!("JsonParseError: {}", msg)),
ErrorCode::MpvError(ref msg) => f.write_str(&format!("MpvError: {}", msg)),
ErrorCode::JsonContainsUnexptectedType => {
f.write_str("Mpv sent a value with an unexpected type")
}
ErrorCode::UnexpectedResult => f.write_str("Unexpected result received"),
ErrorCode::UnexpectedValue => f.write_str("Unexpected value received"),
ErrorCode::MissingValue => f.write_str("Missing value"),
ErrorCode::UnsupportedType => f.write_str("Unsupported type received"),
ErrorCode::ValueDoesNotContainBool => {
f.write_str("The received value is not of type \'std::bool\'")
}
ErrorCode::ValueDoesNotContainF64 => {
f.write_str("The received value is not of type \'std::f64\'")
}
ErrorCode::ValueDoesNotContainHashMap => {
f.write_str("The received value is not of type \'std::collections::HashMap\'")
}
ErrorCode::ValueDoesNotContainPlaylist => {
f.write_str("The received value is not of type \'mpvipc::Playlist\'")
}
ErrorCode::ValueDoesNotContainString => {
f.write_str("The received value is not of type \'std::string::String\'")
}
ErrorCode::ValueDoesNotContainUsize => {
f.write_str("The received value is not of type \'std::usize\'")
}
}
}
}
pub trait GetPropertyTypeHandler: Sized {
fn get_property_generic(instance: &Mpv, property: &str) -> Result<Self, Error>;
}
impl GetPropertyTypeHandler for bool {
fn get_property_generic(instance: &Mpv, property: &str) -> Result<bool, Error> {
get_mpv_property::<bool>(instance, property)
}
}
impl GetPropertyTypeHandler for String {
fn get_property_generic(instance: &Mpv, property: &str) -> Result<String, Error> {
get_mpv_property::<String>(instance, property)
}
}
impl GetPropertyTypeHandler for f64 {
fn get_property_generic(instance: &Mpv, property: &str) -> Result<f64, Error> {
get_mpv_property::<f64>(instance, property)
}
}
impl GetPropertyTypeHandler for usize {
fn get_property_generic(instance: &Mpv, property: &str) -> Result<usize, Error> {
get_mpv_property::<usize>(instance, property)
}
}
impl GetPropertyTypeHandler for Vec<PlaylistEntry> {
fn get_property_generic(instance: &Mpv, property: &str) -> Result<Vec<PlaylistEntry>, Error> {
get_mpv_property::<Vec<PlaylistEntry>>(instance, property)
}
}
impl GetPropertyTypeHandler for HashMap<String, MpvDataType> {
fn get_property_generic(
instance: &Mpv,
property: &str,
) -> Result<HashMap<String, MpvDataType>, Error> {
get_mpv_property::<HashMap<String, MpvDataType>>(instance, property)
}
}
pub trait SetPropertyTypeHandler<T> {
fn set_property_generic(instance: &Mpv, property: &str, value: T) -> Result<(), Error>;
}
impl SetPropertyTypeHandler<bool> for bool {
fn set_property_generic(instance: &Mpv, property: &str, value: bool) -> Result<(), Error> {
set_mpv_property(instance, property, json!(value))
}
}
impl SetPropertyTypeHandler<String> for String {
fn set_property_generic(instance: &Mpv, property: &str, value: String) -> Result<(), Error> {
set_mpv_property(instance, property, json!(value))
}
}
impl SetPropertyTypeHandler<f64> for f64 {
fn set_property_generic(instance: &Mpv, property: &str, value: f64) -> Result<(), Error> {
set_mpv_property(instance, property, json!(value))
}
}
impl SetPropertyTypeHandler<usize> for usize {
fn set_property_generic(instance: &Mpv, property: &str, value: usize) -> Result<(), Error> {
set_mpv_property(instance, property, json!(value))
}
}
impl Mpv {
pub fn connect(socket: &str) -> Result<Mpv, Error> {
match UnixStream::connect(socket) {
Ok(stream) => {
let cloned_stream = stream.try_clone().expect("cloning UnixStream");
return Ok(Mpv {
stream,
reader: BufReader::new(cloned_stream),
name: String::from(socket),
});
}
Err(internal_error) => Err(Error(ErrorCode::ConnectError(internal_error.to_string()))),
}
}
pub fn disconnect(&self) {
let mut stream = &self.stream;
stream
.shutdown(std::net::Shutdown::Both)
.expect("socket disconnect");
let mut buffer = [0; 32];
for _ in 0..stream.bytes().count() {
stream.read(&mut buffer[..]).unwrap();
}
}
pub fn get_stream_ref(&self) -> &UnixStream {
&self.stream
}
pub fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, Error> {
match get_mpv_property(self, "metadata") {
Ok(map) => Ok(map),
Err(err) => Err(err),
}
}
pub fn get_playlist(&self) -> Result<Playlist, Error> {
match get_mpv_property::<Vec<PlaylistEntry>>(self, "playlist") {
Ok(entries) => Ok(Playlist(entries)),
Err(msg) => Err(msg),
}
}
/// # Description
///
/// Retrieves the property value from mpv.
///
/// ## Supported types
/// - String
/// - bool
/// - HashMap<String, String> (e.g. for the 'metadata' property)
/// - Vec<PlaylistEntry> (for the 'playlist' property)
/// - usize
/// - f64
///
/// ## Input arguments
///
/// - **property** defines the mpv property that should be retrieved
///
/// # Example
/// ```
/// use mpvipc::{Mpv, Error};
/// fn main() -> Result<(), Error> {
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
/// let paused: bool = mpv.get_property("pause")?;
/// let title: String = mpv.get_property("media-title")?;
/// Ok(())
/// }
/// ```
pub fn get_property<T: GetPropertyTypeHandler>(&self, property: &str) -> Result<T, Error> {
T::get_property_generic(self, property)
}
/// # Description
///
/// Retrieves the property value from mpv.
/// The result is always of type String, regardless of the type of the value of the mpv property
///
/// ## Input arguments
///
/// - **property** defines the mpv property that should be retrieved
///
/// # Example
///
/// ```
/// use mpvipc::{Mpv, Error};
/// fn main() -> Result<(), Error> {
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
/// let title = mpv.get_property_string("media-title")?;
/// Ok(())
/// }
/// ```
pub fn get_property_string(&self, property: &str) -> Result<String, Error> {
get_mpv_property_string(self, property)
}
pub fn kill(&self) -> Result<(), Error> {
self.run_command(MpvCommand::Quit)
}
/// # Description
///
/// Waits until an mpv event occurs and returns the Event.
///
/// # Example
///
/// ```ignore
/// let mut mpv = Mpv::connect("/tmp/mpvsocket")?;
/// loop {
/// let event = mpv.event_listen()?;
/// println!("{:?}", event);
/// }
/// ```
pub fn event_listen(&mut self) -> Result<Event, Error> {
listen(self)
}
pub fn event_listen_raw(&mut self) -> String {
listen_raw(self)
}
pub fn next(&self) -> Result<(), Error> {
self.run_command(MpvCommand::PlaylistNext)
}
pub fn observe_property(&self, id: isize, property: &str) -> Result<(), Error> {
self.run_command(MpvCommand::Observe {
id: id,
property: property.to_string(),
})
}
pub fn unobserve_property(&self, id: isize) -> Result<(), Error> {
self.run_command(MpvCommand::Unobserve(id))
}
pub fn pause(&self) -> Result<(), Error> {
set_mpv_property(self, "pause", json!(true))
}
pub fn prev(&self) -> Result<(), Error> {
self.run_command(MpvCommand::PlaylistPrev)
}
pub fn restart(&self) -> Result<(), Error> {
self.run_command(MpvCommand::Seek {
seconds: 0f64,
option: SeekOptions::Absolute,
})
}
/// # Description
///
/// Runs mpv commands. The arguments are passed as a String-Vector reference:
///
/// ## Input arguments
///
/// - **command** defines the mpv command that should be executed
/// - **args** a slice of &str's which define the arguments
///
/// # Example
/// ```
/// use mpvipc::{Mpv, Error};
/// fn main() -> Result<(), Error> {
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
///
/// //Run command 'playlist-shuffle' which takes no arguments
/// mpv.run_command(MpvCommand::PlaylistShuffle)?;
///
/// //Run command 'seek' which in this case takes two arguments
/// mpv.run_command(MpvCommand::Seek {
/// seconds: 0f64,
/// option: SeekOptions::Absolute,
/// })?;
/// Ok(())
/// }
/// ```
pub fn run_command(&self, command: MpvCommand) -> Result<(), Error> {
match command {
MpvCommand::LoadFile { file, option } => run_mpv_command(
self,
"loadfile",
&[
file.as_ref(),
match option {
PlaylistAddOptions::Append => "append",
PlaylistAddOptions::Replace => "replace",
},
],
),
MpvCommand::LoadList { file, option } => run_mpv_command(
self,
"loadlist",
&[
file.as_ref(),
match option {
PlaylistAddOptions::Append => "append",
PlaylistAddOptions::Replace => "replace",
},
],
),
MpvCommand::Observe { id, property } => observe_mpv_property(self, &id, &property),
MpvCommand::PlaylistClear => run_mpv_command(self, "playlist-clear", &[]),
MpvCommand::PlaylistMove { from, to } => {
run_mpv_command(self, "playlist-move", &[&from.to_string(), &to.to_string()])
}
MpvCommand::PlaylistNext => run_mpv_command(self, "playlist-next", &[]),
MpvCommand::PlaylistPrev => run_mpv_command(self, "playlist-prev", &[]),
MpvCommand::PlaylistRemove(id) => {
run_mpv_command(self, "playlist-remove", &[&id.to_string()])
}
MpvCommand::PlaylistShuffle => run_mpv_command(self, "playlist-shuffle", &[]),
MpvCommand::Quit => run_mpv_command(self, "quit", &[]),
MpvCommand::ScriptMessage(args) => {
let str_args: Vec<_> = args.iter().map(String::as_str).collect();
run_mpv_command(self, "script-message", &str_args)
}
MpvCommand::ScriptMessageTo { target, args } => {
let mut cmd_args: Vec<_> = vec![target.as_str()];
let mut str_args: Vec<_> = args.iter().map(String::as_str).collect();
cmd_args.append(&mut str_args);
run_mpv_command(self, "script-message-to", &cmd_args)
}
MpvCommand::Seek { seconds, option } => run_mpv_command(
self,
"seek",
&[
&seconds.to_string(),
match option {
SeekOptions::Absolute => "absolute",
SeekOptions::Relative => "relative",
SeekOptions::AbsolutePercent => "absolute-percent",
SeekOptions::RelativePercent => "relative-percent",
},
],
),
MpvCommand::Stop => run_mpv_command(self, "stop", &[]),
MpvCommand::Unobserve(id) => unobserve_mpv_property(self, &id),
}
}
/// Run a custom command.
/// This should only be used if the desired command is not implemented
/// with [MpvCommand].
pub fn run_command_raw(&self, command: &str, args: &[&str]) -> Result<(), Error> {
run_mpv_command(self, command, args)
}
pub fn playlist_add(
&self,
file: &str,
file_type: PlaylistAddTypeOptions,
option: PlaylistAddOptions,
) -> Result<(), Error> {
match file_type {
PlaylistAddTypeOptions::File => self.run_command(MpvCommand::LoadFile {
file: file.to_string(),
option,
}),
PlaylistAddTypeOptions::Playlist => self.run_command(MpvCommand::LoadList {
file: file.to_string(),
option,
}),
}
}
pub fn playlist_clear(&self) -> Result<(), Error> {
self.run_command(MpvCommand::PlaylistClear)
}
pub fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), Error> {
self.run_command(MpvCommand::PlaylistMove { from, to })
}
pub fn playlist_play_id(&self, id: usize) -> Result<(), Error> {
set_mpv_property(self, "playlist-pos", json!(id))
}
pub fn playlist_play_next(&self, id: usize) -> Result<(), Error> {
match get_mpv_property::<usize>(self, "playlist-pos") {
Ok(current_id) => self.run_command(MpvCommand::PlaylistMove {
from: id,
to: current_id + 1,
}),
Err(msg) => Err(msg),
}
}
pub fn playlist_remove_id(&self, id: usize) -> Result<(), Error> {
self.run_command(MpvCommand::PlaylistRemove(id))
}
pub fn playlist_shuffle(&self) -> Result<(), Error> {
self.run_command(MpvCommand::PlaylistShuffle)
}
pub fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), Error> {
self.run_command(MpvCommand::Seek { seconds, option })
}
pub fn set_loop_file(&self, option: Switch) -> Result<(), Error> {
let mut enabled = false;
match option {
Switch::On => enabled = true,
Switch::Off => {}
Switch::Toggle => match get_mpv_property_string(self, "loop-file") {
Ok(value) => match value.as_ref() {
"false" => {
enabled = true;
}
_ => {
enabled = false;
}
},
Err(msg) => return Err(msg),
},
}
set_mpv_property(self, "loop-file", json!(enabled))
}
pub fn set_loop_playlist(&self, option: Switch) -> Result<(), Error> {
let mut enabled = false;
match option {
Switch::On => enabled = true,
Switch::Off => {}
Switch::Toggle => match get_mpv_property_string(self, "loop-playlist") {
Ok(value) => match value.as_ref() {
"false" => {
enabled = true;
}
_ => {
enabled = false;
}
},
Err(msg) => return Err(msg),
},
}
set_mpv_property(self, "loop-playlist", json!(enabled))
}
pub fn set_mute(&self, option: Switch) -> Result<(), Error> {
let mut enabled = false;
match option {
Switch::On => enabled = true,
Switch::Off => {}
Switch::Toggle => match get_mpv_property::<bool>(self, "mute") {
Ok(value) => {
enabled = !value;
}
Err(msg) => return Err(msg),
},
}
set_mpv_property(self, "mute", json!(enabled))
}
/// # Description
///
/// Sets the mpv property _<property>_ to _<value>_.
///
/// ## Supported types
/// - String
/// - bool
/// - f64
/// - usize
///
/// ## Input arguments
///
/// - **property** defines the mpv property that should be retrieved
/// - **value** defines the value of the given mpv property _<property>_
///
/// # Example
/// ```
/// use mpvipc::{Mpv, Error};
/// fn main() -> Result<(), Error> {
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
/// mpv.set_property("pause", true)?;
/// Ok(())
/// }
/// ```
pub fn set_property<T: SetPropertyTypeHandler<T>>(
&self,
property: &str,
value: T,
) -> Result<(), Error> {
T::set_property_generic(self, property, value)
}
pub fn set_speed(&self, input_speed: f64, option: NumberChangeOptions) -> Result<(), Error> {
match get_mpv_property::<f64>(self, "speed") {
Ok(speed) => match option {
NumberChangeOptions::Increase => {
set_mpv_property(self, "speed", json!(speed + input_speed))
}
NumberChangeOptions::Decrease => {
set_mpv_property(self, "speed", json!(speed - input_speed))
}
NumberChangeOptions::Absolute => {
set_mpv_property(self, "speed", json!(input_speed))
}
},
Err(msg) => Err(msg),
}
}
pub fn set_volume(&self, input_volume: f64, option: NumberChangeOptions) -> Result<(), Error> {
match get_mpv_property::<f64>(self, "volume") {
Ok(volume) => match option {
NumberChangeOptions::Increase => {
set_mpv_property(self, "volume", json!(volume + input_volume))
}
NumberChangeOptions::Decrease => {
set_mpv_property(self, "volume", json!(volume - input_volume))
}
NumberChangeOptions::Absolute => {
set_mpv_property(self, "volume", json!(input_volume))
}
},
Err(msg) => Err(msg),
}
}
pub fn stop(&self) -> Result<(), Error> {
self.run_command(MpvCommand::Stop)
}
pub fn toggle(&self) -> Result<(), Error> {
run_mpv_command(self, "cycle", &["pause"])
}
}

View File

@ -1,182 +1,17 @@
use crate::message_parser::TypeHandler;
use self::message_parser::extract_mpv_response_data;
use self::message_parser::json_array_to_playlist;
use self::message_parser::json_array_to_vec;
use self::message_parser::json_map_to_hashmap;
use super::*; use super::*;
use log::{debug, warn}; use log::{debug, warn};
use serde_json::json; use serde_json::json;
use std::collections::HashMap; use serde_json::Value;
use std::io::prelude::*; use std::io::BufRead;
use std::io::BufReader; use std::io::BufReader;
use std::iter::Iterator; use std::io::Write;
#[derive(Debug, Clone)]
pub struct PlaylistEntry {
pub id: usize,
pub filename: String,
pub title: String,
pub current: bool,
}
pub trait TypeHandler: Sized {
fn get_value(value: Value) -> Result<Self, Error>;
fn as_string(&self) -> String;
}
impl TypeHandler for String {
fn get_value(value: Value) -> Result<String, Error> {
if let Value::Object(map) = value {
if let Value::String(ref error) = map["error"] {
if error == "success" && map.contains_key("data") {
if let Value::String(ref s) = map["data"] {
Ok(s.to_string())
} else {
Err(Error(ErrorCode::ValueDoesNotContainString))
}
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
}
fn as_string(&self) -> String {
self.to_string()
}
}
impl TypeHandler for bool {
fn get_value(value: Value) -> Result<bool, Error> {
if let Value::Object(map) = value {
if let Value::String(ref error) = map["error"] {
if error == "success" && map.contains_key("data") {
if let Value::Bool(ref b) = map["data"] {
Ok(*b)
} else {
Err(Error(ErrorCode::ValueDoesNotContainBool))
}
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
}
fn as_string(&self) -> String {
if *self {
"true".to_string()
} else {
"false".to_string()
}
}
}
impl TypeHandler for f64 {
fn get_value(value: Value) -> Result<f64, Error> {
if let Value::Object(map) = value {
if let Value::String(ref error) = map["error"] {
if error == "success" && map.contains_key("data") {
if let Value::Number(ref num) = map["data"] {
Ok(num.as_f64().unwrap())
} else {
Err(Error(ErrorCode::ValueDoesNotContainF64))
}
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
}
fn as_string(&self) -> String {
self.to_string()
}
}
impl TypeHandler for usize {
fn get_value(value: Value) -> Result<usize, Error> {
if let Value::Object(map) = value {
if let Value::String(ref error) = map["error"] {
if error == "success" && map.contains_key("data") {
if let Value::Number(ref num) = map["data"] {
Ok(num.as_u64().unwrap() as usize)
} else {
Err(Error(ErrorCode::ValueDoesNotContainUsize))
}
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
}
fn as_string(&self) -> String {
self.to_string()
}
}
impl TypeHandler for HashMap<String, MpvDataType> {
fn get_value(value: Value) -> Result<HashMap<String, MpvDataType>, Error> {
if let Value::Object(map) = value {
if let Value::String(ref error) = map["error"] {
if error == "success" && map.contains_key("data") {
if let Value::Object(ref inner_map) = map["data"] {
Ok(json_map_to_hashmap(inner_map))
} else {
Err(Error(ErrorCode::ValueDoesNotContainHashMap))
}
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
}
fn as_string(&self) -> String {
format!("{:?}", self)
}
}
impl TypeHandler for Vec<PlaylistEntry> {
fn get_value(value: Value) -> Result<Vec<PlaylistEntry>, Error> {
if let Value::Object(map) = value {
if let Value::String(ref error) = map["error"] {
if error == "success" && map.contains_key("data") {
if let Value::Array(ref playlist_vec) = map["data"] {
Ok(json_array_to_playlist(playlist_vec))
} else {
Err(Error(ErrorCode::ValueDoesNotContainPlaylist))
}
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
} else {
Err(Error(ErrorCode::UnexpectedValue))
}
}
fn as_string(&self) -> String {
format!("{:?}", self)
}
}
pub fn get_mpv_property<T: TypeHandler>(instance: &Mpv, property: &str) -> Result<T, Error> { pub fn get_mpv_property<T: TypeHandler>(instance: &Mpv, property: &str) -> Result<T, Error> {
let ipc_string = json!({"command": ["get_property", property]}); let ipc_string = json!({"command": ["get_property", property]});
@ -191,23 +26,7 @@ pub fn get_mpv_property_string(instance: &Mpv, property: &str) -> Result<String,
let val = serde_json::from_str::<Value>(&send_command_sync(instance, ipc_string)) let val = serde_json::from_str::<Value>(&send_command_sync(instance, ipc_string))
.map_err(|why| Error(ErrorCode::JsonParseError(why.to_string())))?; .map_err(|why| Error(ErrorCode::JsonParseError(why.to_string())))?;
let map = if let Value::Object(map) = val { let data = extract_mpv_response_data(&val)?;
Ok(map)
} else {
Err(Error(ErrorCode::UnexpectedValue))
}?;
let error = if let Value::String(ref error) = map["error"] {
Ok(error)
} else {
Err(Error(ErrorCode::UnexpectedValue))
}?;
let data = if error == "success" {
Ok(&map["data"])
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}?;
match data { match data {
Value::Bool(b) => Ok(b.to_string()), Value::Bool(b) => Ok(b.to_string()),
@ -219,15 +38,19 @@ pub fn get_mpv_property_string(instance: &Mpv, property: &str) -> Result<String,
} }
} }
fn validate_mpv_response(response: &str) -> Result<(), Error> {
serde_json::from_str::<Value>(response)
.map_err(|why| Error(ErrorCode::JsonParseError(why.to_string())))
.and_then(|value| extract_mpv_response_data(&value).map(|_| ()))
}
pub fn set_mpv_property(instance: &Mpv, property: &str, value: Value) -> Result<(), Error> { pub fn set_mpv_property(instance: &Mpv, property: &str, value: Value) -> Result<(), Error> {
let ipc_string = json!({ let ipc_string = json!({
"command": ["set_property", property, value] "command": ["set_property", property, value]
}); });
match serde_json::from_str::<Value>(&send_command_sync(instance, ipc_string)) { let response = &send_command_sync(instance, ipc_string);
Ok(_) => Ok(()), validate_mpv_response(response)
Err(why) => Err(Error(ErrorCode::JsonParseError(why.to_string()))),
}
} }
pub fn run_mpv_command(instance: &Mpv, command: &str, args: &[&str]) -> Result<(), Error> { pub fn run_mpv_command(instance: &Mpv, command: &str, args: &[&str]) -> Result<(), Error> {
@ -239,60 +62,27 @@ pub fn run_mpv_command(instance: &Mpv, command: &str, args: &[&str]) -> Result<(
args_array.push(json!(arg)); args_array.push(json!(arg));
} }
} }
match serde_json::from_str::<Value>(&send_command_sync(instance, ipc_string)) {
Ok(feedback) => { let response = &send_command_sync(instance, ipc_string);
if let Value::String(ref error) = feedback["error"] { validate_mpv_response(response)
if error == "success" {
Ok(())
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedResult))
}
}
Err(why) => Err(Error(ErrorCode::JsonParseError(why.to_string()))),
}
} }
pub fn observe_mpv_property(instance: &Mpv, id: &isize, property: &str) -> Result<(), Error> { pub fn observe_mpv_property(instance: &Mpv, id: &isize, property: &str) -> Result<(), Error> {
let ipc_string = json!({ let ipc_string = json!({
"command": ["observe_property", id, property] "command": ["observe_property", id, property]
}); });
match serde_json::from_str::<Value>(&send_command_sync(instance, ipc_string)) {
Ok(feedback) => { let response = &send_command_sync(instance, ipc_string);
if let Value::String(ref error) = feedback["error"] { validate_mpv_response(response)
if error == "success" {
Ok(())
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedResult))
}
}
Err(why) => Err(Error(ErrorCode::JsonParseError(why.to_string()))),
}
} }
pub fn unobserve_mpv_property(instance: &Mpv, id: &isize) -> Result<(), Error> { pub fn unobserve_mpv_property(instance: &Mpv, id: &isize) -> Result<(), Error> {
let ipc_string = json!({ let ipc_string = json!({
"command": ["unobserve_property", id] "command": ["unobserve_property", id]
}); });
match serde_json::from_str::<Value>(&send_command_sync(instance, ipc_string)) {
Ok(feedback) => { let response = &send_command_sync(instance, ipc_string);
if let Value::String(ref error) = feedback["error"] { validate_mpv_response(response)
if error == "success" {
Ok(())
} else {
Err(Error(ErrorCode::MpvError(error.to_string())))
}
} else {
Err(Error(ErrorCode::UnexpectedResult))
}
}
Err(why) => Err(Error(ErrorCode::JsonParseError(why.to_string()))),
}
} }
fn try_convert_property(name: &str, id: usize, data: MpvDataType) -> Event { fn try_convert_property(name: &str, id: usize, data: MpvDataType) -> Event {
@ -456,128 +246,3 @@ fn send_command_sync(instance: &Mpv, command: Value) -> String {
} }
} }
} }
fn json_map_to_hashmap(map: &serde_json::map::Map<String, Value>) -> HashMap<String, MpvDataType> {
let mut output_map: HashMap<String, MpvDataType> = HashMap::new();
for (ref key, ref value) in map.iter() {
match **value {
Value::Array(ref array) => {
output_map.insert(
key.to_string(),
MpvDataType::Array(json_array_to_vec(array)),
);
}
Value::Bool(ref b) => {
output_map.insert(key.to_string(), MpvDataType::Bool(*b));
}
Value::Number(ref n) => {
if n.is_u64() {
output_map.insert(
key.to_string(),
MpvDataType::Usize(n.as_u64().unwrap() as usize),
);
} else if n.is_f64() {
output_map.insert(key.to_string(), MpvDataType::Double(n.as_f64().unwrap()));
} else {
panic!("unimplemented number");
}
}
Value::String(ref s) => {
output_map.insert(key.to_string(), MpvDataType::String(s.to_string()));
}
Value::Object(ref m) => {
output_map.insert(
key.to_string(),
MpvDataType::HashMap(json_map_to_hashmap(m)),
);
}
Value::Null => {
unimplemented!();
}
}
}
output_map
}
fn json_array_to_vec(array: &Vec<Value>) -> Vec<MpvDataType> {
let mut output: Vec<MpvDataType> = Vec::new();
if array.len() > 0 {
match array[0] {
Value::Array(_) => {
for entry in array {
if let Value::Array(ref a) = *entry {
output.push(MpvDataType::Array(json_array_to_vec(a)));
}
}
}
Value::Bool(_) => {
for entry in array {
if let Value::Bool(ref b) = *entry {
output.push(MpvDataType::Bool(*b));
}
}
}
Value::Number(_) => {
for entry in array {
if let Value::Number(ref n) = *entry {
if n.is_u64() {
output.push(MpvDataType::Usize(n.as_u64().unwrap() as usize));
} else if n.is_f64() {
output.push(MpvDataType::Double(n.as_f64().unwrap()));
} else {
panic!("unimplemented number");
}
}
}
}
Value::Object(_) => {
for entry in array {
if let Value::Object(ref map) = *entry {
output.push(MpvDataType::HashMap(json_map_to_hashmap(map)));
}
}
}
Value::String(_) => {
for entry in array {
if let Value::String(ref s) = *entry {
output.push(MpvDataType::String(s.to_string()));
}
}
}
Value::Null => {
unimplemented!();
}
}
}
output
}
fn json_array_to_playlist(array: &Vec<Value>) -> Vec<PlaylistEntry> {
let mut output: Vec<PlaylistEntry> = Vec::new();
for (id, entry) in array.iter().enumerate() {
let mut filename: String = String::new();
let mut title: String = String::new();
let mut current: bool = false;
if let Value::String(ref f) = entry["filename"] {
filename = f.to_string();
}
if let Value::String(ref t) = entry["title"] {
title = t.to_string();
}
if let Value::Bool(ref b) = entry["current"] {
current = *b;
}
output.push(PlaylistEntry {
id,
filename,
title,
current,
});
}
output
}

View File

@ -1,735 +1,5 @@
pub mod ipc; mod api;
mod ipc;
mod message_parser;
use ipc::*; pub use api::*;
use serde_json::{json, Value};
use std::collections::HashMap;
use std::fmt::{self, Display};
use std::io::{BufReader, Read};
use std::os::unix::net::UnixStream;
#[derive(Debug, Clone)]
pub enum Event {
Shutdown,
StartFile,
EndFile,
FileLoaded,
TracksChanged,
TrackSwitched,
Idle,
Pause,
Unpause,
Tick,
VideoReconfig,
AudioReconfig,
MetadataUpdate,
Seek,
PlaybackRestart,
PropertyChange { id: usize, property: Property },
ChapterChange,
ClientMessage { args: Vec<String> },
Unimplemented,
}
#[derive(Debug, Clone)]
pub enum Property {
Path(Option<String>),
Pause(bool),
PlaybackTime(Option<f64>),
Duration(Option<f64>),
Metadata(Option<HashMap<String, MpvDataType>>),
Unknown { name: String, data: MpvDataType },
}
#[derive(Debug, Clone)]
pub enum MpvCommand {
LoadFile {
file: String,
option: PlaylistAddOptions,
},
LoadList {
file: String,
option: PlaylistAddOptions,
},
PlaylistClear,
PlaylistMove {
from: usize,
to: usize,
},
Observe {
id: isize,
property: String,
},
PlaylistNext,
PlaylistPrev,
PlaylistRemove(usize),
PlaylistShuffle,
Quit,
ScriptMessage(Vec<String>),
ScriptMessageTo {
target: String,
args: Vec<String>,
},
Seek {
seconds: f64,
option: SeekOptions,
},
Stop,
Unobserve(isize),
}
#[derive(Debug, Clone)]
pub enum MpvDataType {
Array(Vec<MpvDataType>),
Bool(bool),
Double(f64),
HashMap(HashMap<String, MpvDataType>),
Null,
Playlist(Playlist),
String(String),
Usize(usize),
}
#[derive(Debug, Clone)]
pub enum NumberChangeOptions {
Absolute,
Increase,
Decrease,
}
#[derive(Debug, Clone, Copy)]
pub enum PlaylistAddOptions {
Replace,
Append,
}
#[derive(Debug, Clone, Copy)]
pub enum PlaylistAddTypeOptions {
File,
Playlist,
}
#[derive(Debug, Clone, Copy)]
pub enum SeekOptions {
Relative,
Absolute,
RelativePercent,
AbsolutePercent,
}
#[derive(Debug, Clone, Copy)]
pub enum Switch {
On,
Off,
Toggle,
}
#[derive(Debug, Clone)]
pub enum ErrorCode {
MpvError(String),
JsonParseError(String),
ConnectError(String),
JsonContainsUnexptectedType,
UnexpectedResult,
UnexpectedValue,
MissingValue,
UnsupportedType,
ValueDoesNotContainBool,
ValueDoesNotContainF64,
ValueDoesNotContainHashMap,
ValueDoesNotContainPlaylist,
ValueDoesNotContainString,
ValueDoesNotContainUsize,
}
pub struct Mpv {
stream: UnixStream,
reader: BufReader<UnixStream>,
name: String,
}
#[derive(Debug, Clone)]
pub struct Playlist(pub Vec<PlaylistEntry>);
#[derive(Debug, Clone)]
pub struct Error(pub ErrorCode);
impl Drop for Mpv {
fn drop(&mut self) {
self.disconnect();
}
}
impl fmt::Debug for Mpv {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_tuple("Mpv").field(&self.name).finish()
}
}
impl Clone for Mpv {
fn clone(&self) -> Self {
let stream = self.stream.try_clone().expect("cloning UnixStream");
let cloned_stream = stream.try_clone().expect("cloning UnixStream");
Mpv {
stream,
reader: BufReader::new(cloned_stream),
name: self.name.clone(),
}
}
fn clone_from(&mut self, source: &Self) {
let stream = source.stream.try_clone().expect("cloning UnixStream");
let cloned_stream = stream.try_clone().expect("cloning UnixStream");
*self = Mpv {
stream,
reader: BufReader::new(cloned_stream),
name: source.name.clone(),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
impl std::error::Error for Error {}
impl Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ErrorCode::ConnectError(ref msg) => f.write_str(&format!("ConnectError: {}", msg)),
ErrorCode::JsonParseError(ref msg) => f.write_str(&format!("JsonParseError: {}", msg)),
ErrorCode::MpvError(ref msg) => f.write_str(&format!("MpvError: {}", msg)),
ErrorCode::JsonContainsUnexptectedType => {
f.write_str("Mpv sent a value with an unexpected type")
}
ErrorCode::UnexpectedResult => f.write_str("Unexpected result received"),
ErrorCode::UnexpectedValue => f.write_str("Unexpected value received"),
ErrorCode::MissingValue => f.write_str("Missing value"),
ErrorCode::UnsupportedType => f.write_str("Unsupported type received"),
ErrorCode::ValueDoesNotContainBool => {
f.write_str("The received value is not of type \'std::bool\'")
}
ErrorCode::ValueDoesNotContainF64 => {
f.write_str("The received value is not of type \'std::f64\'")
}
ErrorCode::ValueDoesNotContainHashMap => {
f.write_str("The received value is not of type \'std::collections::HashMap\'")
}
ErrorCode::ValueDoesNotContainPlaylist => {
f.write_str("The received value is not of type \'mpvipc::Playlist\'")
}
ErrorCode::ValueDoesNotContainString => {
f.write_str("The received value is not of type \'std::string::String\'")
}
ErrorCode::ValueDoesNotContainUsize => {
f.write_str("The received value is not of type \'std::usize\'")
}
}
}
}
pub trait GetPropertyTypeHandler: Sized {
fn get_property_generic(instance: &Mpv, property: &str) -> Result<Self, Error>;
}
impl GetPropertyTypeHandler for bool {
fn get_property_generic(instance: &Mpv, property: &str) -> Result<bool, Error> {
get_mpv_property::<bool>(instance, property)
}
}
impl GetPropertyTypeHandler for String {
fn get_property_generic(instance: &Mpv, property: &str) -> Result<String, Error> {
get_mpv_property::<String>(instance, property)
}
}
impl GetPropertyTypeHandler for f64 {
fn get_property_generic(instance: &Mpv, property: &str) -> Result<f64, Error> {
get_mpv_property::<f64>(instance, property)
}
}
impl GetPropertyTypeHandler for usize {
fn get_property_generic(instance: &Mpv, property: &str) -> Result<usize, Error> {
get_mpv_property::<usize>(instance, property)
}
}
impl GetPropertyTypeHandler for Vec<PlaylistEntry> {
fn get_property_generic(instance: &Mpv, property: &str) -> Result<Vec<PlaylistEntry>, Error> {
get_mpv_property::<Vec<PlaylistEntry>>(instance, property)
}
}
impl GetPropertyTypeHandler for HashMap<String, MpvDataType> {
fn get_property_generic(
instance: &Mpv,
property: &str,
) -> Result<HashMap<String, MpvDataType>, Error> {
get_mpv_property::<HashMap<String, MpvDataType>>(instance, property)
}
}
pub trait SetPropertyTypeHandler<T> {
fn set_property_generic(instance: &Mpv, property: &str, value: T) -> Result<(), Error>;
}
impl SetPropertyTypeHandler<bool> for bool {
fn set_property_generic(instance: &Mpv, property: &str, value: bool) -> Result<(), Error> {
set_mpv_property(instance, property, json!(value))
}
}
impl SetPropertyTypeHandler<String> for String {
fn set_property_generic(instance: &Mpv, property: &str, value: String) -> Result<(), Error> {
set_mpv_property(instance, property, json!(value))
}
}
impl SetPropertyTypeHandler<f64> for f64 {
fn set_property_generic(instance: &Mpv, property: &str, value: f64) -> Result<(), Error> {
set_mpv_property(instance, property, json!(value))
}
}
impl SetPropertyTypeHandler<usize> for usize {
fn set_property_generic(instance: &Mpv, property: &str, value: usize) -> Result<(), Error> {
set_mpv_property(instance, property, json!(value))
}
}
impl Mpv {
pub fn connect(socket: &str) -> Result<Mpv, Error> {
match UnixStream::connect(socket) {
Ok(stream) => {
let cloned_stream = stream.try_clone().expect("cloning UnixStream");
return Ok(Mpv {
stream,
reader: BufReader::new(cloned_stream),
name: String::from(socket),
});
}
Err(internal_error) => Err(Error(ErrorCode::ConnectError(internal_error.to_string()))),
}
}
pub fn disconnect(&self) {
let mut stream = &self.stream;
stream
.shutdown(std::net::Shutdown::Both)
.expect("socket disconnect");
let mut buffer = [0; 32];
for _ in 0..stream.bytes().count() {
stream.read(&mut buffer[..]).unwrap();
}
}
pub fn get_stream_ref(&self) -> &UnixStream {
&self.stream
}
pub fn get_metadata(&self) -> Result<HashMap<String, MpvDataType>, Error> {
match get_mpv_property(self, "metadata") {
Ok(map) => Ok(map),
Err(err) => Err(err),
}
}
pub fn get_playlist(&self) -> Result<Playlist, Error> {
match get_mpv_property::<Vec<PlaylistEntry>>(self, "playlist") {
Ok(entries) => Ok(Playlist(entries)),
Err(msg) => Err(msg),
}
}
/// # Description
///
/// Retrieves the property value from mpv.
///
/// ## Supported types
/// - String
/// - bool
/// - HashMap<String, String> (e.g. for the 'metadata' property)
/// - Vec<PlaylistEntry> (for the 'playlist' property)
/// - usize
/// - f64
///
/// ## Input arguments
///
/// - **property** defines the mpv property that should be retrieved
///
/// # Example
/// ```
/// use mpvipc::{Mpv, Error};
/// fn main() -> Result<(), Error> {
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
/// let paused: bool = mpv.get_property("pause")?;
/// let title: String = mpv.get_property("media-title")?;
/// Ok(())
/// }
/// ```
pub fn get_property<T: GetPropertyTypeHandler>(&self, property: &str) -> Result<T, Error> { T::get_property_generic(self, property)
}
/// # Description
///
/// Retrieves the property value from mpv.
/// The result is always of type String, regardless of the type of the value of the mpv property
///
/// ## Input arguments
///
/// - **property** defines the mpv property that should be retrieved
///
/// # Example
///
/// ```
/// use mpvipc::{Mpv, Error};
/// fn main() -> Result<(), Error> {
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
/// let title = mpv.get_property_string("media-title")?;
/// Ok(())
/// }
/// ```
pub fn get_property_string(&self, property: &str) -> Result<String, Error> {
get_mpv_property_string(self, property)
}
pub fn kill(&self) -> Result<(), Error> {
self.run_command(MpvCommand::Quit)
}
/// # Description
///
/// Waits until an mpv event occurs and returns the Event.
///
/// # Example
///
/// ```ignore
/// let mut mpv = Mpv::connect("/tmp/mpvsocket")?;
/// loop {
/// let event = mpv.event_listen()?;
/// println!("{:?}", event);
/// }
/// ```
pub fn event_listen(&mut self) -> Result<Event, Error> {
listen(self)
}
pub fn event_listen_raw(&mut self) -> String {
listen_raw(self)
}
pub fn next(&self) -> Result<(), Error> {
self.run_command(MpvCommand::PlaylistNext)
}
pub fn observe_property(&self, id: isize, property: &str) -> Result<(), Error> {
self.run_command(MpvCommand::Observe {
id: id,
property: property.to_string(),
})
}
pub fn unobserve_property(&self, id: isize) -> Result<(), Error> {
self.run_command(MpvCommand::Unobserve(id))
}
pub fn pause(&self) -> Result<(), Error> {
set_mpv_property(self, "pause", json!(true))
}
pub fn prev(&self) -> Result<(), Error> {
self.run_command(MpvCommand::PlaylistPrev)
}
pub fn restart(&self) -> Result<(), Error> {
self.run_command(MpvCommand::Seek {
seconds: 0f64,
option: SeekOptions::Absolute,
})
}
/// # Description
///
/// Runs mpv commands. The arguments are passed as a String-Vector reference:
///
/// ## Input arguments
///
/// - **command** defines the mpv command that should be executed
/// - **args** a slice of &str's which define the arguments
///
/// # Example
/// ```
/// use mpvipc::{Mpv, Error};
/// fn main() -> Result<(), Error> {
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
///
/// //Run command 'playlist-shuffle' which takes no arguments
/// mpv.run_command(MpvCommand::PlaylistShuffle)?;
///
/// //Run command 'seek' which in this case takes two arguments
/// mpv.run_command(MpvCommand::Seek {
/// seconds: 0f64,
/// option: SeekOptions::Absolute,
/// })?;
/// Ok(())
/// }
/// ```
pub fn run_command(&self, command: MpvCommand) -> Result<(), Error> {
match command {
MpvCommand::LoadFile { file, option } => run_mpv_command(
self,
"loadfile",
&[
file.as_ref(),
match option {
PlaylistAddOptions::Append => "append",
PlaylistAddOptions::Replace => "replace",
},
],
),
MpvCommand::LoadList { file, option } => run_mpv_command(
self,
"loadlist",
&[
file.as_ref(),
match option {
PlaylistAddOptions::Append => "append",
PlaylistAddOptions::Replace => "replace",
},
],
),
MpvCommand::Observe { id, property } => observe_mpv_property(self, &id, &property),
MpvCommand::PlaylistClear => run_mpv_command(self, "playlist-clear", &[]),
MpvCommand::PlaylistMove { from, to } => {
run_mpv_command(self, "playlist-move", &[&from.to_string(), &to.to_string()])
}
MpvCommand::PlaylistNext => run_mpv_command(self, "playlist-next", &[]),
MpvCommand::PlaylistPrev => run_mpv_command(self, "playlist-prev", &[]),
MpvCommand::PlaylistRemove(id) => {
run_mpv_command(self, "playlist-remove", &[&id.to_string()])
}
MpvCommand::PlaylistShuffle => run_mpv_command(self, "playlist-shuffle", &[]),
MpvCommand::Quit => run_mpv_command(self, "quit", &[]),
MpvCommand::ScriptMessage(args) => {
let str_args: Vec<_> = args.iter().map(String::as_str).collect();
run_mpv_command(self, "script-message", &str_args)
}
MpvCommand::ScriptMessageTo { target, args } => {
let mut cmd_args: Vec<_> = vec![target.as_str()];
let mut str_args: Vec<_> = args.iter().map(String::as_str).collect();
cmd_args.append(&mut str_args);
run_mpv_command(self, "script-message-to", &cmd_args)
}
MpvCommand::Seek { seconds, option } => run_mpv_command(
self,
"seek",
&[
&seconds.to_string(),
match option {
SeekOptions::Absolute => "absolute",
SeekOptions::Relative => "relative",
SeekOptions::AbsolutePercent => "absolute-percent",
SeekOptions::RelativePercent => "relative-percent",
},
],
),
MpvCommand::Stop => run_mpv_command(self, "stop", &[]),
MpvCommand::Unobserve(id) => unobserve_mpv_property(self, &id),
}
}
/// Run a custom command.
/// This should only be used if the desired command is not implemented
/// with [MpvCommand].
pub fn run_command_raw(&self, command: &str, args: &[&str]) -> Result<(), Error> {
run_mpv_command(self, command, args)
}
pub fn playlist_add(
&self,
file: &str,
file_type: PlaylistAddTypeOptions,
option: PlaylistAddOptions,
) -> Result<(), Error> {
match file_type {
PlaylistAddTypeOptions::File => self.run_command(MpvCommand::LoadFile {
file: file.to_string(),
option,
}),
PlaylistAddTypeOptions::Playlist => self.run_command(MpvCommand::LoadList {
file: file.to_string(),
option,
}),
}
}
pub fn playlist_clear(&self) -> Result<(), Error> {
self.run_command(MpvCommand::PlaylistClear)
}
pub fn playlist_move_id(&self, from: usize, to: usize) -> Result<(), Error> {
self.run_command(MpvCommand::PlaylistMove { from, to })
}
pub fn playlist_play_id(&self, id: usize) -> Result<(), Error> {
set_mpv_property(self, "playlist-pos", json!(id))
}
pub fn playlist_play_next(&self, id: usize) -> Result<(), Error> {
match get_mpv_property::<usize>(self, "playlist-pos") {
Ok(current_id) => self.run_command(MpvCommand::PlaylistMove {
from: id,
to: current_id + 1,
}),
Err(msg) => Err(msg),
}
}
pub fn playlist_remove_id(&self, id: usize) -> Result<(), Error> {
self.run_command(MpvCommand::PlaylistRemove(id))
}
pub fn playlist_shuffle(&self) -> Result<(), Error> {
self.run_command(MpvCommand::PlaylistShuffle)
}
pub fn seek(&self, seconds: f64, option: SeekOptions) -> Result<(), Error> {
self.run_command(MpvCommand::Seek { seconds, option })
}
pub fn set_loop_file(&self, option: Switch) -> Result<(), Error> {
let mut enabled = false;
match option {
Switch::On => enabled = true,
Switch::Off => {}
Switch::Toggle => match get_mpv_property_string(self, "loop-file") {
Ok(value) => match value.as_ref() {
"false" => {
enabled = true;
}
_ => {
enabled = false;
}
},
Err(msg) => return Err(msg),
},
}
set_mpv_property(self, "loop-file", json!(enabled))
}
pub fn set_loop_playlist(&self, option: Switch) -> Result<(), Error> {
let mut enabled = false;
match option {
Switch::On => enabled = true,
Switch::Off => {}
Switch::Toggle => match get_mpv_property_string(self, "loop-playlist") {
Ok(value) => match value.as_ref() {
"false" => {
enabled = true;
}
_ => {
enabled = false;
}
},
Err(msg) => return Err(msg),
},
}
set_mpv_property(self, "loop-playlist", json!(enabled))
}
pub fn set_mute(&self, option: Switch) -> Result<(), Error> {
let mut enabled = false;
match option {
Switch::On => enabled = true,
Switch::Off => {}
Switch::Toggle => match get_mpv_property::<bool>(self, "mute") {
Ok(value) => {
enabled = !value;
}
Err(msg) => return Err(msg),
},
}
set_mpv_property(self, "mute", json!(enabled))
}
/// # Description
///
/// Sets the mpv property _<property>_ to _<value>_.
///
/// ## Supported types
/// - String
/// - bool
/// - f64
/// - usize
///
/// ## Input arguments
///
/// - **property** defines the mpv property that should be retrieved
/// - **value** defines the value of the given mpv property _<property>_
///
/// # Example
/// ```
/// use mpvipc::{Mpv, Error};
/// fn main() -> Result<(), Error> {
/// let mpv = Mpv::connect("/tmp/mpvsocket")?;
/// mpv.set_property("pause", true)?;
/// Ok(())
/// }
/// ```
pub fn set_property<T: SetPropertyTypeHandler<T>>(
&self,
property: &str,
value: T,
) -> Result<(), Error> {
T::set_property_generic(self, property, value)
}
pub fn set_speed(&self, input_speed: f64, option: NumberChangeOptions) -> Result<(), Error> {
match get_mpv_property::<f64>(self, "speed") {
Ok(speed) => match option {
NumberChangeOptions::Increase => {
set_mpv_property(self, "speed", json!(speed + input_speed))
}
NumberChangeOptions::Decrease => {
set_mpv_property(self, "speed", json!(speed - input_speed))
}
NumberChangeOptions::Absolute => {
set_mpv_property(self, "speed", json!(input_speed))
}
},
Err(msg) => Err(msg),
}
}
pub fn set_volume(&self, input_volume: f64, option: NumberChangeOptions) -> Result<(), Error> {
match get_mpv_property::<f64>(self, "volume") {
Ok(volume) => match option {
NumberChangeOptions::Increase => {
set_mpv_property(self, "volume", json!(volume + input_volume))
}
NumberChangeOptions::Decrease => {
set_mpv_property(self, "volume", json!(volume - input_volume))
}
NumberChangeOptions::Absolute => {
set_mpv_property(self, "volume", json!(input_volume))
}
},
Err(msg) => Err(msg),
}
}
pub fn stop(&self) -> Result<(), Error> {
self.run_command(MpvCommand::Stop)
}
pub fn toggle(&self) -> Result<(), Error> {
run_mpv_command(self, "cycle", &["pause"])
}
}

232
src/message_parser.rs Normal file
View File

@ -0,0 +1,232 @@
use std::collections::HashMap;
use serde_json::Value;
use crate::{Error, ErrorCode, MpvDataType, PlaylistEntry};
pub trait TypeHandler: Sized {
fn get_value(value: Value) -> Result<Self, Error>;
fn as_string(&self) -> String;
}
pub(crate) fn extract_mpv_response_data(value: &Value) -> Result<&Value, Error> {
value
.as_object()
.map(|o| (o.get("error").and_then(|e| e.as_str()), o.get("data")))
.ok_or(Error(ErrorCode::UnexpectedValue))
.and_then(|(error, data)| match error {
Some("success") => data.ok_or(Error(ErrorCode::UnexpectedValue)),
Some(e) => Err(Error(ErrorCode::MpvError(e.to_string()))),
None => Err(Error(ErrorCode::UnexpectedValue)),
})
}
impl TypeHandler for String {
fn get_value(value: Value) -> Result<String, Error> {
extract_mpv_response_data(&value)
.and_then(|d| {
d.as_str()
.ok_or(Error(ErrorCode::ValueDoesNotContainString))
})
.map(|s| s.to_string())
}
fn as_string(&self) -> String {
self.to_string()
}
}
impl TypeHandler for bool {
fn get_value(value: Value) -> Result<bool, Error> {
extract_mpv_response_data(&value)
.and_then(|d| d.as_bool().ok_or(Error(ErrorCode::ValueDoesNotContainBool)))
}
fn as_string(&self) -> String {
if *self {
"true".to_string()
} else {
"false".to_string()
}
}
}
impl TypeHandler for f64 {
fn get_value(value: Value) -> Result<f64, Error> {
extract_mpv_response_data(&value)
.and_then(|d| d.as_f64().ok_or(Error(ErrorCode::ValueDoesNotContainF64)))
}
fn as_string(&self) -> String {
self.to_string()
}
}
impl TypeHandler for usize {
fn get_value(value: Value) -> Result<usize, Error> {
extract_mpv_response_data(&value)
.and_then(|d| d.as_u64().ok_or(Error(ErrorCode::ValueDoesNotContainUsize)))
.map(|u| u as usize)
}
fn as_string(&self) -> String {
self.to_string()
}
}
impl TypeHandler for HashMap<String, MpvDataType> {
fn get_value(value: Value) -> Result<HashMap<String, MpvDataType>, Error> {
extract_mpv_response_data(&value)
.and_then(|d| {
d.as_object()
.ok_or(Error(ErrorCode::ValueDoesNotContainHashMap))
})
.map(json_map_to_hashmap)
}
fn as_string(&self) -> String {
format!("{:?}", self)
}
}
impl TypeHandler for Vec<PlaylistEntry> {
fn get_value(value: Value) -> Result<Vec<PlaylistEntry>, Error> {
extract_mpv_response_data(&value)
.and_then(|d| {
d.as_array()
.ok_or(Error(ErrorCode::ValueDoesNotContainPlaylist))
})
.map(json_array_to_playlist)
}
fn as_string(&self) -> String {
format!("{:?}", self)
}
}
pub(crate) fn json_map_to_hashmap(
map: &serde_json::map::Map<String, Value>,
) -> HashMap<String, MpvDataType> {
let mut output_map: HashMap<String, MpvDataType> = HashMap::new();
for (ref key, ref value) in map.iter() {
match **value {
Value::Array(ref array) => {
output_map.insert(
key.to_string(),
MpvDataType::Array(json_array_to_vec(array)),
);
}
Value::Bool(ref b) => {
output_map.insert(key.to_string(), MpvDataType::Bool(*b));
}
Value::Number(ref n) => {
if n.is_u64() {
output_map.insert(
key.to_string(),
MpvDataType::Usize(n.as_u64().unwrap() as usize),
);
} else if n.is_f64() {
output_map.insert(key.to_string(), MpvDataType::Double(n.as_f64().unwrap()));
} else {
panic!("unimplemented number");
}
}
Value::String(ref s) => {
output_map.insert(key.to_string(), MpvDataType::String(s.to_string()));
}
Value::Object(ref m) => {
output_map.insert(
key.to_string(),
MpvDataType::HashMap(json_map_to_hashmap(m)),
);
}
Value::Null => {
unimplemented!();
}
}
}
output_map
}
pub(crate) fn json_array_to_vec(array: &Vec<Value>) -> Vec<MpvDataType> {
let mut output: Vec<MpvDataType> = Vec::new();
if array.len() > 0 {
match array[0] {
Value::Array(_) => {
for entry in array {
if let Value::Array(ref a) = *entry {
output.push(MpvDataType::Array(json_array_to_vec(a)));
}
}
}
Value::Bool(_) => {
for entry in array {
if let Value::Bool(ref b) = *entry {
output.push(MpvDataType::Bool(*b));
}
}
}
Value::Number(_) => {
for entry in array {
if let Value::Number(ref n) = *entry {
if n.is_u64() {
output.push(MpvDataType::Usize(n.as_u64().unwrap() as usize));
} else if n.is_f64() {
output.push(MpvDataType::Double(n.as_f64().unwrap()));
} else {
panic!("unimplemented number");
}
}
}
}
Value::Object(_) => {
for entry in array {
if let Value::Object(ref map) = *entry {
output.push(MpvDataType::HashMap(json_map_to_hashmap(map)));
}
}
}
Value::String(_) => {
for entry in array {
if let Value::String(ref s) = *entry {
output.push(MpvDataType::String(s.to_string()));
}
}
}
Value::Null => {
unimplemented!();
}
}
}
output
}
pub(crate) fn json_array_to_playlist(array: &Vec<Value>) -> Vec<PlaylistEntry> {
let mut output: Vec<PlaylistEntry> = Vec::new();
for (id, entry) in array.iter().enumerate() {
let mut filename: String = String::new();
let mut title: String = String::new();
let mut current: bool = false;
if let Value::String(ref f) = entry["filename"] {
filename = f.to_string();
}
if let Value::String(ref t) = entry["title"] {
title = t.to_string();
}
if let Value::Bool(ref b) = entry["current"] {
current = *b;
}
output.push(PlaylistEntry {
id,
filename,
title,
current,
});
}
output
}