1use std::collections::HashMap;
13
14use serde::{Deserialize, Serialize};
15
16use crate::{MpvDataType, MpvError, PlaylistEntry};
17
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26#[serde(rename_all = "kebab-case")]
27pub enum Property {
28 Path(Option<String>),
29 Pause(bool),
30 PlaybackTime(Option<f64>),
31 Duration(Option<f64>),
32 Metadata(Option<HashMap<String, MpvDataType>>),
33 Playlist(Vec<PlaylistEntry>),
34 PlaylistPos(Option<usize>),
35 LoopFile(LoopProperty),
36 LoopPlaylist(LoopProperty),
37 TimePos(Option<f64>),
38 TimeRemaining(Option<f64>),
39 Speed(f64),
40 Volume(f64),
41 Mute(bool),
42 EofReached(bool),
43 Unknown {
44 name: String,
45 data: Option<MpvDataType>,
46 },
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50#[serde(rename_all = "kebab-case")]
51pub enum LoopProperty {
52 N(usize),
53 Inf,
54 No,
55}
56
57pub fn parse_property(name: &str, data: Option<MpvDataType>) -> Result<Property, MpvError> {
62 match name {
63 "path" => {
64 let path = match data {
65 Some(MpvDataType::String(s)) => Some(s),
66 Some(MpvDataType::Null) => None,
67 Some(data) => {
68 return Err(MpvError::DataContainsUnexpectedType {
69 expected_type: "String".to_owned(),
70 received: data,
71 })
72 }
73 None => {
74 return Err(MpvError::MissingMpvData);
75 }
76 };
77 Ok(Property::Path(path))
78 }
79 "pause" => {
80 let pause = match data {
81 Some(MpvDataType::Bool(b)) => b,
82 Some(data) => {
83 return Err(MpvError::DataContainsUnexpectedType {
84 expected_type: "bool".to_owned(),
85 received: data,
86 })
87 }
88 None => {
89 return Err(MpvError::MissingMpvData);
90 }
91 };
92 Ok(Property::Pause(pause))
93 }
94 "playback-time" => {
95 let playback_time = match data {
96 Some(MpvDataType::Double(d)) => Some(d),
97 None | Some(MpvDataType::Null) => None,
98 Some(data) => {
99 return Err(MpvError::DataContainsUnexpectedType {
100 expected_type: "f64".to_owned(),
101 received: data,
102 })
103 }
104 };
105 Ok(Property::PlaybackTime(playback_time))
106 }
107 "duration" => {
108 let duration = match data {
109 Some(MpvDataType::Double(d)) => Some(d),
110 None | Some(MpvDataType::Null) => None,
111 Some(data) => {
112 return Err(MpvError::DataContainsUnexpectedType {
113 expected_type: "f64".to_owned(),
114 received: data,
115 })
116 }
117 };
118 Ok(Property::Duration(duration))
119 }
120 "metadata" => {
121 let metadata = match data {
122 Some(MpvDataType::HashMap(m)) => Some(m),
123 None | Some(MpvDataType::Null) => None,
124 Some(data) => {
125 return Err(MpvError::DataContainsUnexpectedType {
126 expected_type: "HashMap".to_owned(),
127 received: data,
128 })
129 }
130 };
131 Ok(Property::Metadata(metadata))
132 }
133 "playlist" => {
134 let playlist = match data {
135 Some(MpvDataType::Array(a)) => mpv_array_to_playlist(&a)?,
136 None => Vec::new(),
137 Some(data) => {
138 return Err(MpvError::DataContainsUnexpectedType {
139 expected_type: "Array".to_owned(),
140 received: data,
141 })
142 }
143 };
144 Ok(Property::Playlist(playlist))
145 }
146 "playlist-pos" => {
147 let playlist_pos = match data {
148 Some(MpvDataType::Usize(u)) => Some(u),
149 Some(MpvDataType::MinusOne) => None,
150 Some(MpvDataType::Null) => None,
151 None => None,
152 Some(data) => {
153 return Err(MpvError::DataContainsUnexpectedType {
154 expected_type: "usize or -1".to_owned(),
155 received: data,
156 })
157 }
158 };
159 Ok(Property::PlaylistPos(playlist_pos))
160 }
161 "loop-file" => {
162 let loop_file = match data.to_owned() {
163 Some(MpvDataType::Usize(n)) => Some(LoopProperty::N(n)),
164 Some(MpvDataType::Bool(b)) => match b {
165 true => Some(LoopProperty::Inf),
166 false => Some(LoopProperty::No),
167 },
168 Some(MpvDataType::String(s)) => match s.as_str() {
169 "inf" => Some(LoopProperty::Inf),
170 _ => None,
171 },
172 _ => None,
173 }
174 .ok_or(match data {
175 Some(data) => MpvError::DataContainsUnexpectedType {
176 expected_type: "'inf', bool, or usize".to_owned(),
177 received: data,
178 },
179 None => MpvError::MissingMpvData,
180 })?;
181 Ok(Property::LoopFile(loop_file))
182 }
183 "loop-playlist" => {
184 let loop_playlist = match data.to_owned() {
185 Some(MpvDataType::Usize(n)) => Some(LoopProperty::N(n)),
186 Some(MpvDataType::Bool(b)) => match b {
187 true => Some(LoopProperty::Inf),
188 false => Some(LoopProperty::No),
189 },
190 Some(MpvDataType::String(s)) => match s.as_str() {
191 "inf" => Some(LoopProperty::Inf),
192 _ => None,
193 },
194 _ => None,
195 }
196 .ok_or(match data {
197 Some(data) => MpvError::DataContainsUnexpectedType {
198 expected_type: "'inf', bool, or usize".to_owned(),
199 received: data,
200 },
201 None => MpvError::MissingMpvData,
202 })?;
203
204 Ok(Property::LoopPlaylist(loop_playlist))
205 }
206 "time-pos" => {
207 let time_pos = match data {
208 Some(MpvDataType::Double(d)) => Some(d),
209 Some(data) => {
210 return Err(MpvError::DataContainsUnexpectedType {
211 expected_type: "f64".to_owned(),
212 received: data,
213 })
214 }
215 None => None,
216 };
217
218 Ok(Property::TimePos(time_pos))
219 }
220 "time-remaining" => {
221 let time_remaining = match data {
222 Some(MpvDataType::Double(d)) => Some(d),
223 Some(data) => {
224 return Err(MpvError::DataContainsUnexpectedType {
225 expected_type: "f64".to_owned(),
226 received: data,
227 })
228 }
229 None => None,
230 };
231 Ok(Property::TimeRemaining(time_remaining))
232 }
233 "speed" => {
234 let speed = match data {
235 Some(MpvDataType::Double(d)) => d,
236 Some(data) => {
237 return Err(MpvError::DataContainsUnexpectedType {
238 expected_type: "f64".to_owned(),
239 received: data,
240 })
241 }
242 None => {
243 return Err(MpvError::MissingMpvData);
244 }
245 };
246 Ok(Property::Speed(speed))
247 }
248 "volume" => {
249 let volume = match data {
250 Some(MpvDataType::Double(d)) => d,
251 Some(data) => {
252 return Err(MpvError::DataContainsUnexpectedType {
253 expected_type: "f64".to_owned(),
254 received: data,
255 })
256 }
257 None => {
258 return Err(MpvError::MissingMpvData);
259 }
260 };
261 Ok(Property::Volume(volume))
262 }
263 "mute" => {
264 let mute = match data {
265 Some(MpvDataType::Bool(b)) => b,
266 Some(data) => {
267 return Err(MpvError::DataContainsUnexpectedType {
268 expected_type: "bool".to_owned(),
269 received: data,
270 })
271 }
272 None => {
273 return Err(MpvError::MissingMpvData);
274 }
275 };
276 Ok(Property::Mute(mute))
277 }
278 "eof-reached" => {
279 let eof_reached = match data {
280 Some(MpvDataType::Bool(b)) => b,
281 Some(data) => {
282 return Err(MpvError::DataContainsUnexpectedType {
283 expected_type: "bool".to_owned(),
284 received: data,
285 })
286 }
287 None => true,
288 };
289 Ok(Property::EofReached(eof_reached))
290 }
291 _ => Ok(Property::Unknown {
293 name: name.to_owned(),
294 data,
295 }),
296 }
297}
298
299fn mpv_data_to_playlist_entry(
300 map: &HashMap<String, MpvDataType>,
301) -> Result<PlaylistEntry, MpvError> {
302 let filename = match map.get("filename") {
303 Some(MpvDataType::String(s)) => s.to_string(),
304 Some(data) => {
305 return Err(MpvError::DataContainsUnexpectedType {
306 expected_type: "String".to_owned(),
307 received: data.clone(),
308 })
309 }
310 None => return Err(MpvError::MissingMpvData),
311 };
312 let title = match map.get("title") {
313 Some(MpvDataType::String(s)) => Some(s.to_string()),
314 Some(data) => {
315 return Err(MpvError::DataContainsUnexpectedType {
316 expected_type: "String".to_owned(),
317 received: data.clone(),
318 })
319 }
320 None => None,
321 };
322 let current = match map.get("current") {
323 Some(MpvDataType::Bool(b)) => *b,
324 Some(data) => {
325 return Err(MpvError::DataContainsUnexpectedType {
326 expected_type: "bool".to_owned(),
327 received: data.clone(),
328 })
329 }
330 None => false,
331 };
332 Ok(PlaylistEntry {
333 id: 0,
334 filename,
335 title,
336 current,
337 })
338}
339
340fn mpv_array_to_playlist(array: &[MpvDataType]) -> Result<Vec<PlaylistEntry>, MpvError> {
341 array
342 .iter()
343 .map(|value| match value {
344 MpvDataType::HashMap(map) => mpv_data_to_playlist_entry(map),
345 _ => Err(MpvError::DataContainsUnexpectedType {
346 expected_type: "HashMap".to_owned(),
347 received: value.clone(),
348 }),
349 })
350 .enumerate()
351 .map(|(id, entry)| entry.map(|entry| PlaylistEntry { id, ..entry }))
352 .collect()
353}