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