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

            
7
use std::collections::HashMap;
8

            
9
use serde::{Deserialize, Serialize};
10

            
11
use crate::{Event, MpvDataType, MpvError, PlaylistEntry};
12

            
13
/// All possible properties that can be observed through the event system.
14
///
15
/// Not all properties are guaranteed to be implemented.
16
/// If something is missing, please open an issue.
17
///
18
/// Otherwise, the property will be returned as a `Property::Unknown` variant.
19
///
20
/// See <https://mpv.io/manual/master/#properties> for
21
/// the upstream list of properties.
22
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23
#[serde(rename_all = "kebab-case")]
24
pub enum Property {
25
    Path(Option<String>),
26
    Pause(bool),
27
    PlaybackTime(Option<f64>),
28
    Duration(Option<f64>),
29
    Metadata(Option<HashMap<String, MpvDataType>>),
30
    Playlist(Vec<PlaylistEntry>),
31
    PlaylistPos(Option<usize>),
32
    LoopFile(LoopProperty),
33
    LoopPlaylist(LoopProperty),
34
    Speed(f64),
35
    Volume(f64),
36
    Mute(bool),
37
    Unknown {
38
        name: String,
39
        data: Option<MpvDataType>,
40
    },
41
}
42

            
43
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
44
#[serde(rename_all = "kebab-case")]
45
pub enum LoopProperty {
46
    N(usize),
47
    Inf,
48
    No,
49
}
50

            
51
/// Parse a highlevel [`Property`] object from json, used for [`Event::PropertyChange`].
52
20
pub fn parse_event_property(event: Event) -> Result<(usize, Property), MpvError> {
53
20
    let (id, name, data) = match event {
54
20
        Event::PropertyChange { id, name, data } => (id, name, data),
55
        // TODO: return proper error
56
        _ => {
57
            panic!("Event is not a PropertyChange event")
58
        }
59
    };
60

            
61
20
    match name.as_str() {
62
20
        "path" => {
63
            let path = match data {
64
                Some(MpvDataType::String(s)) => Some(s),
65
                Some(MpvDataType::Null) => None,
66
                Some(data) => {
67
                    return Err(MpvError::DataContainsUnexpectedType {
68
                        expected_type: "String".to_owned(),
69
                        received: data,
70
                    })
71
                }
72
                None => {
73
                    return Err(MpvError::MissingMpvData);
74
                }
75
            };
76
            Ok((id, Property::Path(path)))
77
        }
78
20
        "pause" => {
79
4
            let pause = match data {
80
4
                Some(MpvDataType::Bool(b)) => b,
81
                Some(data) => {
82
                    return Err(MpvError::DataContainsUnexpectedType {
83
                        expected_type: "bool".to_owned(),
84
                        received: data,
85
                    })
86
                }
87
                None => {
88
                    return Err(MpvError::MissingMpvData);
89
                }
90
            };
91
4
            Ok((id, Property::Pause(pause)))
92
        }
93
16
        "playback-time" => {
94
            let playback_time = match data {
95
                Some(MpvDataType::Double(d)) => Some(d),
96
                None | Some(MpvDataType::Null) => None,
97
                Some(data) => {
98
                    return Err(MpvError::DataContainsUnexpectedType {
99
                        expected_type: "f64".to_owned(),
100
                        received: data,
101
                    })
102
                }
103
            };
104
            Ok((id, Property::PlaybackTime(playback_time)))
105
        }
106
16
        "duration" => {
107
2
            let duration = match data {
108
                Some(MpvDataType::Double(d)) => Some(d),
109
2
                None | Some(MpvDataType::Null) => None,
110
                Some(data) => {
111
                    return Err(MpvError::DataContainsUnexpectedType {
112
                        expected_type: "f64".to_owned(),
113
                        received: data,
114
                    })
115
                }
116
            };
117
2
            Ok((id, Property::Duration(duration)))
118
        }
119
14
        "metadata" => {
120
            let metadata = match data {
121
                Some(MpvDataType::HashMap(m)) => Some(m),
122
                None | Some(MpvDataType::Null) => None,
123
                Some(data) => {
124
                    return Err(MpvError::DataContainsUnexpectedType {
125
                        expected_type: "HashMap".to_owned(),
126
                        received: data,
127
                    })
128
                }
129
            };
130
            Ok((id, Property::Metadata(metadata)))
131
        }
132
        // "playlist" => {
133
        //     let playlist = match data {
134
        //         MpvDataType::Array(a) => json_array_to_playlist(&a),
135
        //         MpvDataType::Null => Vec::new(),
136
        //         _ => return Err(Error(ErrorCode::ValueDoesNotContainPlaylist)),
137
        //     };
138
        //     Ok((id, Property::Playlist(playlist)))
139
        // }
140
14
        "playlist-pos" => {
141
            let playlist_pos = match data {
142
                Some(MpvDataType::Usize(u)) => Some(u),
143
                Some(MpvDataType::MinusOne) => None,
144
                Some(MpvDataType::Null) => None,
145
                None => None,
146
                Some(data) => {
147
                    return Err(MpvError::DataContainsUnexpectedType {
148
                        expected_type: "usize or -1".to_owned(),
149
                        received: data,
150
                    })
151
                }
152
            };
153
            Ok((id, Property::PlaylistPos(playlist_pos)))
154
        }
155
14
        "loop-file" => {
156
            let loop_file = match data.to_owned() {
157
                Some(MpvDataType::Usize(n)) => Some(LoopProperty::N(n)),
158
                Some(MpvDataType::Bool(b)) => match b {
159
                    true => Some(LoopProperty::Inf),
160
                    false => Some(LoopProperty::No),
161
                },
162
                Some(MpvDataType::String(s)) => match s.as_str() {
163
                    "inf" => Some(LoopProperty::Inf),
164
                    _ => None,
165
                },
166
                _ => None,
167
            }
168
            .ok_or(match data {
169
                Some(data) => MpvError::DataContainsUnexpectedType {
170
                    expected_type: "'inf', bool, or usize".to_owned(),
171
                    received: data,
172
                },
173
                None => MpvError::MissingMpvData,
174
            })?;
175
            Ok((id, Property::LoopFile(loop_file)))
176
        }
177
14
        "loop-playlist" => {
178
            let loop_playlist = match data.to_owned() {
179
                Some(MpvDataType::Usize(n)) => Some(LoopProperty::N(n)),
180
                Some(MpvDataType::Bool(b)) => match b {
181
                    true => Some(LoopProperty::Inf),
182
                    false => Some(LoopProperty::No),
183
                },
184
                Some(MpvDataType::String(s)) => match s.as_str() {
185
                    "inf" => Some(LoopProperty::Inf),
186
                    _ => None,
187
                },
188
                _ => None,
189
            }
190
            .ok_or(match data {
191
                Some(data) => MpvError::DataContainsUnexpectedType {
192
                    expected_type: "'inf', bool, or usize".to_owned(),
193
                    received: data,
194
                },
195
                None => MpvError::MissingMpvData,
196
            })?;
197

            
198
            Ok((id, Property::LoopPlaylist(loop_playlist)))
199
        }
200
14
        "speed" => {
201
            let speed = match data {
202
                Some(MpvDataType::Double(d)) => d,
203
                Some(data) => {
204
                    return Err(MpvError::DataContainsUnexpectedType {
205
                        expected_type: "f64".to_owned(),
206
                        received: data,
207
                    })
208
                }
209
                None => {
210
                    return Err(MpvError::MissingMpvData);
211
                }
212
            };
213
            Ok((id, Property::Speed(speed)))
214
        }
215
14
        "volume" => {
216
8
            let volume = match data {
217
8
                Some(MpvDataType::Double(d)) => d,
218
                Some(data) => {
219
                    return Err(MpvError::DataContainsUnexpectedType {
220
                        expected_type: "f64".to_owned(),
221
                        received: data,
222
                    })
223
                }
224
                None => {
225
                    return Err(MpvError::MissingMpvData);
226
                }
227
            };
228
8
            Ok((id, Property::Volume(volume)))
229
        }
230
6
        "mute" => {
231
6
            let mute = match data {
232
6
                Some(MpvDataType::Bool(b)) => b,
233
                Some(data) => {
234
                    return Err(MpvError::DataContainsUnexpectedType {
235
                        expected_type: "bool".to_owned(),
236
                        received: data,
237
                    })
238
                }
239
                None => {
240
                    return Err(MpvError::MissingMpvData);
241
                }
242
            };
243
6
            Ok((id, Property::Mute(mute)))
244
        }
245
        // TODO: add missing cases
246
        _ => Ok((id, Property::Unknown { name, data })),
247
    }
248
20
}