1
//! JSON parsing logic for properties returned by
2
//! [`Event::PropertyChange`], and used internally in `MpvExt`
3
//! to parse the response from `Mpv::get_property()`.
4
//!
5
//! This module is used to parse the json data from the `data` field of
6
//! known properties. Mpv has about 1000 different properties
7
//! as of `v0.38.0`, so this module will only implement the most common ones.
8

            
9
// TODO: reuse this logic for providing a more typesafe response API to `Mpv::get_property()`
10
//       Although this data is currently of type `Option<`
11

            
12
use std::collections::HashMap;
13

            
14
use serde::{Deserialize, Serialize};
15

            
16
use crate::{MpvDataType, MpvError, PlaylistEntry};
17

            
18
/// An incomplete list of properties that mpv can return.
19
///
20
/// Unimplemented properties will be returned with it's data
21
/// as a `Property::Unknown` variant.
22
///
23
/// See <https://mpv.io/manual/master/#properties> for
24
/// the upstream list of properties.
25
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26
#[serde(rename_all = "kebab-case")]
27
pub 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
/// Loop mode used by mpv for files and playlists.
50
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51
#[serde(rename_all = "kebab-case")]
52
pub enum LoopProperty {
53
    /// Loop N times
54
    N(usize),
55
    /// Loop infinitely
56
    Inf,
57
    /// Disable looping
58
    No,
59
}
60

            
61
/// Parse a highlevel [`Property`] object from mpv data.
62
///
63
/// This is intended to be used with the `data` field of
64
/// `Event::PropertyChange` and the response from `Mpv::get_property_value()`.
65
24
pub fn parse_property(name: &str, data: Option<MpvDataType>) -> Result<Property, MpvError> {
66
24
    match name {
67
24
        "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
24
        "pause" => {
84
8
            let pause = match data {
85
8
                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
8
            Ok(Property::Pause(pause))
97
        }
98
16
        "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
16
        "duration" => {
112
2
            let duration = match data {
113
                Some(MpvDataType::Double(d)) => Some(d),
114
2
                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
2
            Ok(Property::Duration(duration))
123
        }
124
14
        "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
14
        "playlist" => {
138
4
            let playlist = match data {
139
4
                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
4
            Ok(Property::Playlist(playlist))
149
        }
150
10
        "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
10
        "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
10
        "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
10
        "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
10
        "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
10
        "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
10
        "volume" => {
253
6
            let volume = match data {
254
6
                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
6
            Ok(Property::Volume(volume))
266
        }
267
4
        "mute" => {
268
4
            let mute = match data {
269
4
                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
4
            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
        // TODO: add missing cases
296
        _ => Ok(Property::Unknown {
297
            name: name.to_owned(),
298
            data,
299
        }),
300
    }
301
24
}
302

            
303
6
fn mpv_data_to_playlist_entry(
304
6
    map: &HashMap<String, MpvDataType>,
305
6
) -> Result<PlaylistEntry, MpvError> {
306
6
    let filename = match map.get("filename") {
307
6
        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
6
    let title = match map.get("title") {
317
6
        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
6
    let current = match map.get("current") {
327
6
        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
6
    Ok(PlaylistEntry {
337
6
        id: 0,
338
6
        filename,
339
6
        title,
340
6
        current,
341
6
    })
342
6
}
343

            
344
4
fn mpv_array_to_playlist(array: &[MpvDataType]) -> Result<Vec<PlaylistEntry>, MpvError> {
345
4
    array
346
4
        .iter()
347
6
        .map(|value| match value {
348
6
            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
6
        })
354
4
        .enumerate()
355
6
        .map(|(id, entry)| entry.map(|entry| PlaylistEntry { id, ..entry }))
356
4
        .collect()
357
4
}