diff options
Diffstat (limited to 'libmpv2/src/mpv.rs')
-rw-r--r-- | libmpv2/src/mpv.rs | 620 |
1 files changed, 0 insertions, 620 deletions
diff --git a/libmpv2/src/mpv.rs b/libmpv2/src/mpv.rs deleted file mode 100644 index 9d554a6..0000000 --- a/libmpv2/src/mpv.rs +++ /dev/null @@ -1,620 +0,0 @@ -// yt - A fully featured command line YouTube client -// -// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> -// SPDX-License-Identifier: GPL-3.0-or-later -// -// This file is part of Yt. -// -// You should have received a copy of the License along with this program. -// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. - -macro_rules! mpv_cstr_to_str { - ($cstr: expr) => { - std::ffi::CStr::from_ptr($cstr) - .to_str() - .map_err(Error::from) - }; -} - -mod errors; - -/// Event handling -pub mod events; -/// Custom protocols (`protocol://$url`) for playback -#[cfg(feature = "protocols")] -pub mod protocol; -/// Custom rendering -#[cfg(feature = "render")] -pub mod render; - -use log::debug; - -pub use self::errors::*; -use self::events::EventContext; -use super::*; - -use std::{ - ffi::CString, - mem::MaybeUninit, - ops::Deref, - ptr::{self, NonNull}, - sync::atomic::AtomicBool, -}; - -fn mpv_err<T>(ret: T, err: ctype::c_int) -> Result<T> { - if err == 0 { - Ok(ret) - } else { - // debug!("Creating a raw error: {}", to_string_mpv_error(err)); - Err(Error::Raw(err)) - } -} - -/// This trait describes which types are allowed to be passed to getter mpv APIs. -pub unsafe trait GetData: Sized { - #[doc(hidden)] - fn get_from_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(mut fun: F) -> Result<Self> { - let mut val = MaybeUninit::uninit(); - let _ = fun(val.as_mut_ptr() as *mut _)?; - Ok(unsafe { val.assume_init() }) - } - fn get_format() -> Format; -} - -/// This trait describes which types are allowed to be passed to setter mpv APIs. -pub unsafe trait SetData: Sized { - #[doc(hidden)] - fn call_as_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>( - mut self, - mut fun: F, - ) -> Result<T> { - fun(&mut self as *mut Self as _) - } - fn get_format() -> Format; -} - -unsafe impl GetData for f64 { - fn get_format() -> Format { - Format::Double - } -} - -unsafe impl SetData for f64 { - fn get_format() -> Format { - Format::Double - } -} - -unsafe impl GetData for i64 { - fn get_format() -> Format { - Format::Int64 - } -} - -pub mod mpv_node { - use self::sys_node::SysMpvNode; - use crate::{Error, Format, GetData, Result}; - use std::{mem::MaybeUninit, os::raw::c_void, ptr}; - - #[derive(Debug, Clone)] - pub enum MpvNode { - String(String), - Flag(bool), - Int64(i64), - Double(f64), - ArrayIter(MpvNodeArrayIter), - MapIter(MpvNodeMapIter), - None, - } - - impl MpvNode { - pub fn bool(&self) -> Option<bool> { - if let MpvNode::Flag(value) = *self { - Some(value) - } else { - None - } - } - pub fn i64(&self) -> Option<i64> { - if let MpvNode::Int64(value) = *self { - Some(value) - } else { - None - } - } - pub fn f64(&self) -> Option<f64> { - if let MpvNode::Double(value) = *self { - Some(value) - } else { - None - } - } - - pub fn str(&self) -> Option<&str> { - if let MpvNode::String(value) = self { - Some(value) - } else { - None - } - } - - pub fn array(self) -> Option<MpvNodeArrayIter> { - if let MpvNode::ArrayIter(value) = self { - Some(value) - } else { - None - } - } - - pub fn map(self) -> Option<MpvNodeMapIter> { - if let MpvNode::MapIter(value) = self { - Some(value) - } else { - None - } - } - } - - impl PartialEq for MpvNode { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::String(l0), Self::String(r0)) => l0 == r0, - (Self::Flag(l0), Self::Flag(r0)) => l0 == r0, - (Self::Int64(l0), Self::Int64(r0)) => l0 == r0, - (Self::Double(l0), Self::Double(r0)) => l0 == r0, - (Self::ArrayIter(l0), Self::ArrayIter(r0)) => l0.clone().eq(r0.clone()), - (Self::MapIter(l0), Self::MapIter(r0)) => l0.clone().eq(r0.clone()), - _ => core::mem::discriminant(self) == core::mem::discriminant(other), - } - } - } - - #[derive(Debug)] - struct DropWrapper(libmpv2_sys::mpv_node); - - impl Drop for DropWrapper { - fn drop(&mut self) { - unsafe { - libmpv2_sys::mpv_free_node_contents(&mut self.0 as *mut libmpv2_sys::mpv_node) - }; - } - } - - pub mod sys_node { - use super::{DropWrapper, MpvNode, MpvNodeArrayIter, MpvNodeMapIter}; - use crate::{mpv_error, mpv_format, Error, Result}; - use std::rc::Rc; - - #[derive(Debug, Clone)] - pub struct SysMpvNode { - // Reference counted pointer to a parent node so it stays alive long enough. - // - // MPV has one big cleanup function that takes a node so store the parent node - // and force it to stay alive until the reference count hits 0. - parent: Option<Rc<DropWrapper>>, - node: libmpv2_sys::mpv_node, - } - - impl SysMpvNode { - pub fn new(node: libmpv2_sys::mpv_node, drop: bool) -> Self { - Self { - parent: if drop { - Some(Rc::new(DropWrapper(node))) - } else { - None - }, - node, - } - } - - pub fn child(self: Self, node: libmpv2_sys::mpv_node) -> Self { - Self { - parent: self.parent, - node, - } - } - - pub fn value(&self) -> Result<MpvNode> { - let node = self.node; - Ok(match node.format { - mpv_format::Flag => MpvNode::Flag(unsafe { node.u.flag } == 1), - mpv_format::Int64 => MpvNode::Int64(unsafe { node.u.int64 }), - mpv_format::Double => MpvNode::Double(unsafe { node.u.double_ }), - mpv_format::String => { - let text = unsafe { mpv_cstr_to_str!(node.u.string) }?.to_owned(); - MpvNode::String(text) - } - mpv_format::Array => { - let list = unsafe { *node.u.list }; - let iter = MpvNodeArrayIter { - node: self.clone(), - start: unsafe { *node.u.list }.values, - end: unsafe { list.values.offset(list.num.try_into().unwrap()) }, - }; - return Ok(MpvNode::ArrayIter(iter)); - } - - mpv_format::Map => MpvNode::MapIter(MpvNodeMapIter { - list: unsafe { *node.u.list }, - curr: 0, - node: self.clone(), - }), - mpv_format::None => MpvNode::None, - _ => return Err(Error::Raw(mpv_error::PropertyError)), - }) - } - } - } - - #[derive(Debug, Clone)] - pub struct MpvNodeArrayIter { - // Reference counted pointer to a parent node so it stays alive long enough. - // - // MPV has one big cleanup function that takes a node so store the parent node - // and force it to stay alive until the reference count hits 0. - node: SysMpvNode, - start: *const libmpv2_sys::mpv_node, - end: *const libmpv2_sys::mpv_node, - } - - impl Iterator for MpvNodeArrayIter { - type Item = MpvNode; - - fn next(&mut self) -> Option<Self::Item> { - if self.start == self.end { - None - } else { - unsafe { - let result = ptr::read(self.start); - let node = SysMpvNode::child(self.node.clone(), result); - self.start = self.start.offset(1); - node.value().ok() - } - } - } - } - - #[derive(Debug, Clone)] - pub struct MpvNodeMapIter { - // Reference counted pointer to a parent node so it stays alive long enough. - // - // MPV has one big cleanup function that takes a node so store the parent node - // and force it to stay alive until the reference count hits 0. - node: SysMpvNode, - list: libmpv2_sys::mpv_node_list, - curr: usize, - } - - impl Iterator for MpvNodeMapIter { - type Item = (String, MpvNode); - - fn next(&mut self) -> Option<Self::Item> { - if self.curr >= self.list.num.try_into().unwrap() { - None - } else { - let offset = self.curr.try_into().unwrap(); - let (key, value) = unsafe { - ( - mpv_cstr_to_str!(*self.list.keys.offset(offset)), - *self.list.values.offset(offset), - ) - }; - self.curr += 1; - let node = SysMpvNode::child(self.node.clone(), value); - Some((key.unwrap().to_string(), node.value().unwrap())) - } - } - } - - unsafe impl GetData for MpvNode { - fn get_from_c_void<T, F: FnMut(*mut c_void) -> Result<T>>(mut fun: F) -> Result<Self> { - let mut val = MaybeUninit::uninit(); - fun(val.as_mut_ptr() as *mut _)?; - let sys_node = unsafe { val.assume_init() }; - let node = SysMpvNode::new(sys_node, true); - node.value() - } - - fn get_format() -> Format { - Format::Node - } - } -} - -unsafe impl SetData for i64 { - fn get_format() -> Format { - Format::Int64 - } -} - -unsafe impl GetData for bool { - fn get_format() -> Format { - Format::Flag - } -} - -unsafe impl SetData for bool { - fn call_as_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(self, mut fun: F) -> Result<T> { - let mut cpy: i64 = if self { 1 } else { 0 }; - fun(&mut cpy as *mut i64 as *mut _) - } - - fn get_format() -> Format { - Format::Flag - } -} - -unsafe impl GetData for String { - fn get_from_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(mut fun: F) -> Result<String> { - let ptr = &mut ptr::null(); - fun(ptr as *mut *const ctype::c_char as _)?; - - let ret = unsafe { mpv_cstr_to_str!(*ptr) }?.to_owned(); - unsafe { libmpv2_sys::mpv_free(*ptr as *mut _) }; - Ok(ret) - } - - fn get_format() -> Format { - Format::String - } -} - -unsafe impl SetData for String { - fn call_as_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(self, mut fun: F) -> Result<T> { - let string = CString::new(self)?; - fun((&mut string.as_ptr()) as *mut *const ctype::c_char as *mut _) - } - - fn get_format() -> Format { - Format::String - } -} - -/// Wrapper around an `&str` returned by mpv, that properly deallocates it with mpv's allocator. -#[derive(Debug, Hash, Eq, PartialEq)] -pub struct MpvStr<'a>(&'a str); -impl<'a> Deref for MpvStr<'a> { - type Target = str; - - fn deref(&self) -> &str { - self.0 - } -} -impl<'a> Drop for MpvStr<'a> { - fn drop(&mut self) { - unsafe { libmpv2_sys::mpv_free(self.0.as_ptr() as *mut u8 as _) }; - } -} - -unsafe impl<'a> GetData for MpvStr<'a> { - fn get_from_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>( - mut fun: F, - ) -> Result<MpvStr<'a>> { - let ptr = &mut ptr::null(); - let _ = fun(ptr as *mut *const ctype::c_char as _)?; - - Ok(MpvStr(unsafe { mpv_cstr_to_str!(*ptr) }?)) - } - - fn get_format() -> Format { - Format::String - } -} - -unsafe impl<'a> SetData for &'a str { - fn call_as_c_void<T, F: FnMut(*mut ctype::c_void) -> Result<T>>(self, mut fun: F) -> Result<T> { - let string = CString::new(self)?; - fun((&mut string.as_ptr()) as *mut *const ctype::c_char as *mut _) - } - - fn get_format() -> Format { - Format::String - } -} - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -/// Subset of `mpv_format` used by the public API. -pub enum Format { - String, - Flag, - Int64, - Double, - Node, -} - -impl Format { - fn as_mpv_format(&self) -> MpvFormat { - match *self { - Format::String => mpv_format::String, - Format::Flag => mpv_format::Flag, - Format::Int64 => mpv_format::Int64, - Format::Double => mpv_format::Double, - Format::Node => mpv_format::Node, - } - } -} - -/// Context passed to the `initializer` of `Mpv::with_initialzer`. -pub struct MpvInitializer { - ctx: *mut libmpv2_sys::mpv_handle, -} - -impl MpvInitializer { - /// Set the value of a property. - pub fn set_property<T: SetData>(&self, name: &str, data: T) -> Result<()> { - let name = CString::new(name)?; - let format = T::get_format().as_mpv_format() as _; - data.call_as_c_void(|ptr| { - mpv_err((), unsafe { - libmpv2_sys::mpv_set_property(self.ctx, name.as_ptr(), format, ptr) - }) - }) - } - - /// Set the value of an option - pub fn set_option<T: SetData>(&self, name: &str, data: T) -> Result<()> { - let name = CString::new(name)?; - let format = T::get_format().as_mpv_format() as _; - data.call_as_c_void(|ptr| { - mpv_err((), unsafe { - libmpv2_sys::mpv_set_option(self.ctx, name.as_ptr(), format, ptr) - }) - }) - } -} - -/// The central mpv context. -pub struct Mpv { - /// The handle to the mpv core - pub ctx: NonNull<libmpv2_sys::mpv_handle>, - event_context: EventContext, - #[cfg(feature = "protocols")] - protocols_guard: AtomicBool, -} - -unsafe impl Send for Mpv {} -unsafe impl Sync for Mpv {} - -impl Drop for Mpv { - fn drop(&mut self) { - unsafe { - libmpv2_sys::mpv_terminate_destroy(self.ctx.as_ptr()); - } - } -} - -impl Mpv { - /// Create a new `Mpv`. - /// The default settings can be probed by running: `$ mpv --show-profile=libmpv`. - pub fn new() -> Result<Mpv> { - Mpv::with_initializer(|_| Ok(())) - } - - /// Create a new `Mpv`. - /// The same as `Mpv::new`, but you can set properties before `Mpv` is initialized. - pub fn with_initializer<F: FnOnce(MpvInitializer) -> Result<()>>( - initializer: F, - ) -> Result<Mpv> { - let api_version = unsafe { libmpv2_sys::mpv_client_api_version() }; - if crate::MPV_CLIENT_API_MAJOR != api_version >> 16 { - return Err(Error::VersionMismatch { - linked: crate::MPV_CLIENT_API_VERSION, - loaded: api_version, - }); - } - - let ctx = unsafe { libmpv2_sys::mpv_create() }; - if ctx.is_null() { - return Err(Error::Null); - } - - initializer(MpvInitializer { ctx })?; - mpv_err((), unsafe { libmpv2_sys::mpv_initialize(ctx) }).map_err(|err| { - unsafe { libmpv2_sys::mpv_terminate_destroy(ctx) }; - err - })?; - - let ctx = unsafe { NonNull::new_unchecked(ctx) }; - - Ok(Mpv { - ctx, - event_context: EventContext::new(ctx), - #[cfg(feature = "protocols")] - protocols_guard: AtomicBool::new(false), - }) - } - - /// Execute a command - pub fn execute(&self, name: &str, args: &[&str]) -> Result<()> { - if args.is_empty() { - debug!("Running mpv command: '{}'", name); - } else { - debug!("Running mpv command: '{} {}'", name, args.join(" ")); - } - - self.command(name, args)?; - - Ok(()) - } - - /// Load a configuration file. The path has to be absolute, and a file. - pub fn load_config(&self, path: &str) -> Result<()> { - let file = CString::new(path)?.into_raw(); - let ret = mpv_err((), unsafe { - libmpv2_sys::mpv_load_config_file(self.ctx.as_ptr(), file) - }); - unsafe { - drop(CString::from_raw(file)); - }; - ret - } - - pub fn event_context(&self) -> &EventContext { - &self.event_context - } - - pub fn event_context_mut(&mut self) -> &mut EventContext { - &mut self.event_context - } - - /// Send a command to the `Mpv` instance. This uses `mpv_command_string` internally, - /// so that the syntax is the same as described in the [manual for the input.conf](https://mpv.io/manual/master/#list-of-input-commands). - /// - /// Note that you may have to escape strings with `""` when they contain spaces. - /// - /// # Examples - /// - /// ``` - /// # use libmpv2::{Mpv}; - /// # use libmpv2::mpv_node::MpvNode; - /// # use std::collections::HashMap; - /// mpv.command("loadfile", &["test-data/jellyfish.mp4", "append-play"]).unwrap(); - /// # let node = mpv.get_property::<MpvNode>("playlist").unwrap(); - /// # let mut list = node.array().unwrap().collect::<Vec<_>>(); - /// # let map = list.pop().unwrap().map().unwrap().collect::<HashMap<_, _>>(); - /// # assert_eq!(map, HashMap::from([(String::from("id"), MpvNode::Int64(1)), (String::from("current"), MpvNode::Flag(true)), (String::from("filename"), MpvNode::String(String::from("test-data/jellyfish.mp4")))])); - /// ``` - pub fn command(&self, name: &str, args: &[&str]) -> Result<()> { - let mut cmd = name.to_owned(); - - for elem in args { - cmd.push(' '); - cmd.push_str(elem); - } - - let raw = CString::new(cmd)?; - mpv_err((), unsafe { - libmpv2_sys::mpv_command_string(self.ctx.as_ptr(), raw.as_ptr()) - }) - } - - /// Set the value of a property. - pub fn set_property<T: SetData>(&self, name: &str, data: T) -> Result<()> { - let name = CString::new(name)?; - let format = T::get_format().as_mpv_format() as _; - data.call_as_c_void(|ptr| { - mpv_err((), unsafe { - libmpv2_sys::mpv_set_property(self.ctx.as_ptr(), name.as_ptr(), format, ptr) - }) - }) - } - - /// Get the value of a property. - pub fn get_property<T: GetData>(&self, name: &str) -> Result<T> { - let name = CString::new(name)?; - - let format = T::get_format().as_mpv_format() as _; - T::get_from_c_void(|ptr| { - mpv_err((), unsafe { - libmpv2_sys::mpv_get_property(self.ctx.as_ptr(), name.as_ptr(), format, ptr) - }) - }) - } - - /// Internal time in microseconds, this has an arbitrary offset, and will never go backwards. - /// - /// This can be called at any time, even if it was stated that no API function should be called. - pub fn get_internal_time(&self) -> i64 { - unsafe { libmpv2_sys::mpv_get_time_us(self.ctx.as_ptr()) } - } -} |