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}