tauri_plugin_libmpv/
models.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4pub struct MpvInstance {
5    pub mpv: libmpv2::Mpv,
6}
7
8#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct MpvConfig {
11    pub initial_options: Option<HashMap<String, serde_json::Value>>,
12    pub observed_properties: Option<HashMap<String, String>>,
13}
14
15pub struct MpvNode(serde_json::Value);
16
17impl MpvNode {
18    pub fn into_inner(self) -> serde_json::Value {
19        self.0
20    }
21}
22
23unsafe fn cstr_to_str<'a>(cstr: *const std::os::raw::c_char) -> crate::Result<&'a str> {
24    if cstr.is_null() {
25        return Ok("");
26    }
27    std::ffi::CStr::from_ptr(cstr)
28        .to_str()
29        .map_err(|e| crate::Error::GetProperty(format!("Invalid UTF-8 sequence: {}", e)))
30}
31
32unsafe fn convert_node_to_value(
33    node: *const libmpv2_sys::mpv_node,
34) -> crate::Result<serde_json::Value> {
35    Ok(match (*node).format {
36        libmpv2::mpv_format::None => serde_json::Value::Null,
37        libmpv2::mpv_format::String | libmpv2::mpv_format::OsdString => {
38            let s = cstr_to_str((*node).u.string)?;
39            serde_json::Value::String(s.to_string())
40        }
41        libmpv2::mpv_format::Flag => serde_json::Value::Bool((*node).u.flag != 0),
42        libmpv2::mpv_format::Int64 => serde_json::Value::Number((*node).u.int64.into()),
43        libmpv2::mpv_format::Double => {
44            let f = (*node).u.double_;
45            serde_json::Number::from_f64(f)
46                .map_or(serde_json::Value::Null, serde_json::Value::Number)
47        }
48        libmpv2::mpv_format::Array => {
49            if (*node).u.list.is_null() {
50                return Ok(serde_json::Value::Array(Vec::new()));
51            }
52
53            let list = (*node).u.list as *const libmpv2_sys::mpv_node_list;
54            let mut arr = Vec::with_capacity((*list).num as usize);
55            for i in 0..(*list).num {
56                arr.push(convert_node_to_value((*list).values.add(i as usize))?);
57            }
58            serde_json::Value::Array(arr)
59        }
60        libmpv2::mpv_format::Map => {
61            if (*node).u.list.is_null() {
62                return Ok(serde_json::Value::Object(serde_json::Map::new()));
63            }
64
65            let list = (*node).u.list as *const libmpv2_sys::mpv_node_list;
66            let mut map = serde_json::Map::new();
67            for i in 0..(*list).num {
68                let key = cstr_to_str(*(*list).keys.add(i as usize))?;
69                let value = convert_node_to_value((*list).values.add(i as usize))?;
70                map.insert(key.to_string(), value);
71            }
72            serde_json::Value::Object(map)
73        }
74        _ => serde_json::Value::Null,
75    })
76}
77
78unsafe impl libmpv2::GetData for MpvNode {
79    fn get_from_c_void<T, F: FnMut(*mut std::ffi::c_void) -> libmpv2::Result<T>>(
80        mut fun: F,
81    ) -> libmpv2::Result<Self> {
82        let mut node = std::mem::MaybeUninit::<libmpv2_sys::mpv_node>::uninit();
83        fun(node.as_mut_ptr() as *mut _)?;
84
85        let node_ptr = node.as_mut_ptr();
86
87        let result = match std::panic::catch_unwind(|| unsafe { convert_node_to_value(node_ptr) }) {
88            Ok(Ok(value)) => Ok(MpvNode(value)),
89            _ => Err(libmpv2::Error::Raw(libmpv2::mpv_error::Generic)),
90        };
91
92        unsafe { libmpv2_sys::mpv_free_node_contents(node_ptr) };
93
94        result
95    }
96
97    fn get_format() -> libmpv2::Format {
98        libmpv2::Format::Node
99    }
100}
101
102#[derive(Debug, Clone, Serialize)]
103#[serde(untagged)]
104pub enum SerializablePropertyData {
105    Str(String),
106    OsdStr(String),
107    Flag(bool),
108    Int64(i64),
109    Double(f64),
110}
111
112impl<'a> From<libmpv2::events::PropertyData<'a>> for SerializablePropertyData {
113    fn from(data: libmpv2::events::PropertyData<'a>) -> Self {
114        match data {
115            libmpv2::events::PropertyData::Str(s) => SerializablePropertyData::Str(s.to_string()),
116            libmpv2::events::PropertyData::OsdStr(s) => {
117                SerializablePropertyData::OsdStr(s.to_string())
118            }
119            libmpv2::events::PropertyData::Flag(b) => SerializablePropertyData::Flag(b),
120            libmpv2::events::PropertyData::Int64(i) => SerializablePropertyData::Int64(i),
121            libmpv2::events::PropertyData::Double(d) => SerializablePropertyData::Double(d),
122        }
123    }
124}
125
126#[derive(Debug, Clone, Serialize)]
127#[serde(tag = "event", rename_all = "kebab-case")]
128pub enum SerializableMpvEvent {
129    Shutdown,
130    LogMessage {
131        prefix: String,
132        level: String,
133        text: String,
134        log_level: String,
135    },
136    GetPropertyReply {
137        name: String,
138        result: SerializablePropertyData,
139        reply_userdata: u64,
140    },
141    SetPropertyReply {
142        reply_userdata: u64,
143    },
144    CommandReply {
145        reply_userdata: u64,
146    },
147    StartFile,
148    EndFile {
149        reason: String,
150    },
151    FileLoaded,
152    ClientMessage {
153        message: Vec<String>,
154    },
155    VideoReconfig,
156    AudioReconfig,
157    Seek,
158    PlaybackRestart,
159    PropertyChange {
160        name: String,
161        change: SerializablePropertyData,
162        reply_userdata: u64,
163    },
164    QueueOverflow,
165    Deprecated,
166}
167
168impl<'a> From<libmpv2::events::Event<'a>> for SerializableMpvEvent {
169    fn from(event: libmpv2::events::Event<'a>) -> Self {
170        match event {
171            libmpv2::events::Event::Shutdown => SerializableMpvEvent::Shutdown,
172            libmpv2::events::Event::LogMessage {
173                prefix,
174                level,
175                text,
176                log_level,
177            } => {
178                let log_level = match log_level {
179                    libmpv2::mpv_log_level::Debug => "debug",
180                    libmpv2::mpv_log_level::Error => "error",
181                    libmpv2::mpv_log_level::Fatal => "fatal",
182                    libmpv2::mpv_log_level::Info => "info",
183                    libmpv2::mpv_log_level::None => "none",
184                    libmpv2::mpv_log_level::Warn => "warn",
185                    libmpv2::mpv_log_level::V => "v",
186                    libmpv2::mpv_log_level::Trace => "trace",
187                    _ => todo!(),
188                }
189                .to_string();
190                SerializableMpvEvent::LogMessage {
191                    prefix: prefix.to_string(),
192                    level: level.to_string(),
193                    text: text.to_string(),
194                    log_level,
195                }
196            }
197            libmpv2::events::Event::GetPropertyReply {
198                name,
199                result,
200                reply_userdata,
201            } => SerializableMpvEvent::GetPropertyReply {
202                name: name.to_string(),
203                result: result.into(),
204                reply_userdata,
205            },
206            libmpv2::events::Event::SetPropertyReply(reply_userdata) => {
207                SerializableMpvEvent::SetPropertyReply { reply_userdata }
208            }
209            libmpv2::events::Event::CommandReply(reply_userdata) => {
210                SerializableMpvEvent::CommandReply { reply_userdata }
211            }
212            libmpv2::events::Event::StartFile => SerializableMpvEvent::StartFile,
213            libmpv2::events::Event::EndFile(reason) => {
214                let reason_str = match reason {
215                    libmpv2::mpv_end_file_reason::Eof => "eof",
216                    libmpv2::mpv_end_file_reason::Stop => "stop",
217                    libmpv2::mpv_end_file_reason::Quit => "quit",
218                    libmpv2::mpv_end_file_reason::Error => "error",
219                    libmpv2::mpv_end_file_reason::Redirect => "redirect",
220                    _ => todo!(),
221                }
222                .to_string();
223                SerializableMpvEvent::EndFile { reason: reason_str }
224            }
225            libmpv2::events::Event::FileLoaded => SerializableMpvEvent::FileLoaded,
226            libmpv2::events::Event::ClientMessage(message) => SerializableMpvEvent::ClientMessage {
227                message: message.iter().map(|s| s.to_string()).collect(),
228            },
229            libmpv2::events::Event::VideoReconfig => SerializableMpvEvent::VideoReconfig,
230            libmpv2::events::Event::AudioReconfig => SerializableMpvEvent::AudioReconfig,
231            libmpv2::events::Event::Seek => SerializableMpvEvent::Seek,
232            libmpv2::events::Event::PlaybackRestart => SerializableMpvEvent::PlaybackRestart,
233            libmpv2::events::Event::PropertyChange {
234                name,
235                change,
236                reply_userdata,
237            } => SerializableMpvEvent::PropertyChange {
238                name: name.to_string(),
239                change: change.into(),
240                reply_userdata,
241            },
242            libmpv2::events::Event::QueueOverflow => SerializableMpvEvent::QueueOverflow,
243            libmpv2::events::Event::Deprecated(_) => SerializableMpvEvent::Deprecated,
244        }
245    }
246}
247
248#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
249pub struct VideoMarginRatio {
250    pub left: Option<f64>,
251    pub right: Option<f64>,
252    pub top: Option<f64>,
253    pub bottom: Option<f64>,
254}