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
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50
#[serde(rename_all = "kebab-case")]
51
pub enum LoopProperty {
52
    N(usize),
53
    Inf,
54
    No,
55
}
56

            
57
/// Parse a highlevel [`Property`] object from mpv data.
58
///
59
/// This is intended to be used with the `data` field of
60
/// `Event::PropertyChange` and the response from `Mpv::get_property_value()`.
61
22
pub fn parse_property(name: &str, data: Option<MpvDataType>) -> Result<Property, MpvError> {
62
22
    match name {
63
22
        "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
22
        "pause" => {
80
4
            let pause = match data {
81
4
                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
4
            Ok(Property::Pause(pause))
93
        }
94
18
        "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
18
        "duration" => {
108
2
            let duration = match data {
109
                Some(MpvDataType::Double(d)) => Some(d),
110
2
                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
2
            Ok(Property::Duration(duration))
119
        }
120
16
        "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
16
        "playlist" => {
134
4
            let playlist = match data {
135
4
                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
4
            Ok(Property::Playlist(playlist))
145
        }
146
12
        "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
12
        "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
12
        "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
12
        "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
12
        "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
12
        "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
12
        "volume" => {
249
6
            let volume = match data {
250
6
                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
6
            Ok(Property::Volume(volume))
262
        }
263
6
        "mute" => {
264
6
            let mute = match data {
265
6
                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
6
            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
        // TODO: add missing cases
292
        _ => Ok(Property::Unknown {
293
            name: name.to_owned(),
294
            data,
295
        }),
296
    }
297
22
}
298

            
299
6
fn mpv_data_to_playlist_entry(
300
6
    map: &HashMap<String, MpvDataType>,
301
6
) -> Result<PlaylistEntry, MpvError> {
302
6
    let filename = match map.get("filename") {
303
6
        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
6
    let title = match map.get("title") {
313
6
        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
6
    let current = match map.get("current") {
323
6
        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
6
    Ok(PlaylistEntry {
333
6
        id: 0,
334
6
        filename,
335
6
        title,
336
6
        current,
337
6
    })
338
6
}
339

            
340
4
fn mpv_array_to_playlist(array: &[MpvDataType]) -> Result<Vec<PlaylistEntry>, MpvError> {
341
4
    array
342
4
        .iter()
343
6
        .map(|value| match value {
344
6
            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
6
        })
350
4
        .enumerate()
351
6
        .map(|(id, entry)| entry.map(|entry| PlaylistEntry { id, ..entry }))
352
4
        .collect()
353
4
}