1
//! JSON parsing logic for events from [`MpvIpc`](crate::ipc::MpvIpc).
2

            
3
use std::str::FromStr;
4

            
5
use serde::{Deserialize, Serialize};
6
use serde_json::{Map, Value};
7

            
8
use crate::{ipc::MpvIpcEvent, message_parser::json_to_value, MpvDataType, MpvError};
9

            
10
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11
#[serde(rename_all = "kebab-case")]
12
pub enum EventEndFileReason {
13
    Eof,
14
    Stop,
15
    Quit,
16
    Error,
17
    Redirect,
18
    Unknown,
19
    Unimplemented(String),
20
}
21

            
22
impl FromStr for EventEndFileReason {
23
    type Err = ();
24

            
25
    fn from_str(s: &str) -> Result<Self, Self::Err> {
26
        match s {
27
            "eof" => Ok(EventEndFileReason::Eof),
28
            "stop" => Ok(EventEndFileReason::Stop),
29
            "quit" => Ok(EventEndFileReason::Quit),
30
            "error" => Ok(EventEndFileReason::Error),
31
            "redirect" => Ok(EventEndFileReason::Redirect),
32
            reason => Ok(EventEndFileReason::Unimplemented(reason.to_string())),
33
        }
34
    }
35
}
36

            
37
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38
#[serde(rename_all = "kebab-case")]
39
pub enum EventLogMessageLevel {
40
    Info,
41
    Warn,
42
    Error,
43
    Fatal,
44
    Verbose,
45
    Debug,
46
    Trace,
47
    Unimplemented(String),
48
}
49

            
50
impl FromStr for EventLogMessageLevel {
51
    type Err = ();
52

            
53
    fn from_str(s: &str) -> Result<Self, Self::Err> {
54
        match s {
55
            "info" => Ok(EventLogMessageLevel::Info),
56
            "warn" => Ok(EventLogMessageLevel::Warn),
57
            "error" => Ok(EventLogMessageLevel::Error),
58
            "fatal" => Ok(EventLogMessageLevel::Fatal),
59
            "verbose" => Ok(EventLogMessageLevel::Verbose),
60
            "debug" => Ok(EventLogMessageLevel::Debug),
61
            "trace" => Ok(EventLogMessageLevel::Trace),
62
            level => Ok(EventLogMessageLevel::Unimplemented(level.to_string())),
63
        }
64
    }
65
}
66

            
67
/// All possible events that can be sent by mpv.
68
///
69
/// Not all event types are guaranteed to be implemented.
70
/// If something is missing, please open an issue.
71
///
72
/// Otherwise, the event will be returned as an `Event::Unimplemented` variant.
73
///
74
/// See <https://mpv.io/manual/master/#list-of-events> for
75
/// the upstream list of events.
76
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77
#[serde(rename_all = "kebab-case")]
78
pub enum Event {
79
    StartFile {
80
        playlist_entry_id: usize,
81
    },
82
    EndFile {
83
        reason: EventEndFileReason,
84
        playlist_entry_id: usize,
85
        file_error: Option<String>,
86
        playlist_insert_id: Option<usize>,
87
        playlist_insert_num_entries: Option<usize>,
88
    },
89
    FileLoaded,
90
    Seek,
91
    PlaybackRestart,
92
    Shutdown,
93
    LogMessage {
94
        prefix: String,
95
        level: EventLogMessageLevel,
96
        text: String,
97
    },
98
    Hook {
99
        hook_id: usize,
100
    },
101
    GetPropertyReply,
102
    SetPropertyReply,
103
    CommandReply {
104
        result: String,
105
    },
106
    ClientMessage {
107
        args: Vec<String>,
108
    },
109
    VideoReconfig,
110
    AudioReconfig,
111
    PropertyChange {
112
        id: Option<u64>,
113
        name: String,
114
        data: Option<MpvDataType>,
115
    },
116
    EventQueueOverflow,
117
    None,
118

            
119
    /// Deprecated since mpv v0.33.0
120
    Idle,
121

            
122
    /// Deprecated since mpv v0.31.0
123
    Tick,
124

            
125
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
126
    TracksChanged,
127

            
128
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
129
    TrackSwitched,
130

            
131
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
132
    Pause,
133

            
134
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
135
    Unpause,
136

            
137
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
138
    MetadataUpdate,
139

            
140
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
141
    ChapterChange,
142

            
143
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
144
    ScriptInputDispatch,
145

            
146
    /// Catch-all for unimplemented events
147
    Unimplemented(Map<String, Value>),
148
}
149

            
150
macro_rules! get_key_as {
151
    ($as_type:ident, $key:expr, $event:ident) => {{
152
        let tmp = $event.get($key).ok_or(MpvError::MissingKeyInObject {
153
            key: $key.to_owned(),
154
            map: $event.clone(),
155
        })?;
156

            
157
        tmp.$as_type()
158
            .ok_or(MpvError::ValueContainsUnexpectedType {
159
                expected_type: stringify!($as_type).strip_prefix("as_").unwrap().to_owned(),
160
                received: tmp.clone(),
161
            })?
162
    }};
163
}
164

            
165
macro_rules! get_optional_key_as {
166
    ($as_type:ident, $key:expr, $event:ident) => {{
167
        if let Some(tmp) = $event.get($key) {
168
            Some(
169
                tmp.$as_type()
170
                    .ok_or(MpvError::ValueContainsUnexpectedType {
171
                        expected_type: stringify!($as_type).strip_prefix("as_").unwrap().to_owned(),
172
                        received: tmp.clone(),
173
                    })?,
174
            )
175
        } else {
176
            None
177
        }
178
    }};
179
}
180

            
181
// NOTE: I have not been able to test all of these events,
182
//       so some of the parsing logic might be incorrect.
183
//       In particular, I have not been able to make mpv
184
//       produce any of the commented out events, and since
185
//       the documentation for the most part just says
186
//       "See C API", I have not pursued this further.
187
//
188
//       If you need this, please open an issue or a PR.
189

            
190
/// Parse a highlevel [`Event`] objects from json.
191
28
pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, MpvError> {
192
28
    let MpvIpcEvent(event) = raw_event;
193
28

            
194
28
    event
195
28
        .as_object()
196
28
        .ok_or(MpvError::ValueContainsUnexpectedType {
197
28
            expected_type: "object".to_owned(),
198
28
            received: event.clone(),
199
28
        })
200
28
        .and_then(|event| {
201
28
            let event_name = get_key_as!(as_str, "event", event);
202

            
203
28
            match event_name {
204
28
                "start-file" => parse_start_file(event),
205
28
                "end-file" => parse_end_file(event),
206
28
                "file-loaded" => Ok(Event::FileLoaded),
207
28
                "seek" => Ok(Event::Seek),
208
28
                "playback-restart" => Ok(Event::PlaybackRestart),
209
28
                "shutdown" => Ok(Event::Shutdown),
210
28
                "log-message" => parse_log_message(event),
211
28
                "hook" => parse_hook(event),
212

            
213
                // TODO: fix these. They are asynchronous responses to different requests.
214
                //       see:
215
                //         - https://github.com/mpv-player/mpv/blob/5f768a688b706cf94041adf5bed7c7004af2ec5a/libmpv/client.h#L1158-L1160
216
                //         - https://github.com/mpv-player/mpv/blob/5f768a688b706cf94041adf5bed7c7004af2ec5a/libmpv/client.h#L1095-L1098
217
                //         - https://github.com/mpv-player/mpv/blob/5f768a688b706cf94041adf5bed7c7004af2ec5a/libmpv/client.h#L972-L982
218
                // "get-property-reply" =>
219
                // "set-property-reply" =>
220
                // "command-reply" =>
221
28
                "client-message" => parse_client_message(event),
222
28
                "video-reconfig" => Ok(Event::VideoReconfig),
223
28
                "audio-reconfig" => Ok(Event::AudioReconfig),
224
28
                "property-change" => parse_property_change(event),
225
8
                "tick" => Ok(Event::Tick),
226
8
                "idle" => Ok(Event::Idle),
227
                "tracks-changed" => Ok(Event::TracksChanged),
228
                "track-switched" => Ok(Event::TrackSwitched),
229
                "pause" => Ok(Event::Pause),
230
                "unpause" => Ok(Event::Unpause),
231
                "metadata-update" => Ok(Event::MetadataUpdate),
232
                "chapter-change" => Ok(Event::ChapterChange),
233
                _ => Ok(Event::Unimplemented(event.to_owned())),
234
            }
235
28
        })
236
28
}
237

            
238
fn parse_start_file(event: &Map<String, Value>) -> Result<Event, MpvError> {
239
    let playlist_entry_id = get_key_as!(as_u64, "playlist_entry_id", event) as usize;
240

            
241
    Ok(Event::StartFile { playlist_entry_id })
242
}
243

            
244
fn parse_end_file(event: &Map<String, Value>) -> Result<Event, MpvError> {
245
    let reason = get_key_as!(as_str, "reason", event);
246
    let playlist_entry_id = get_key_as!(as_u64, "playlist_entry_id", event) as usize;
247
    let file_error = get_optional_key_as!(as_str, "file_error", event).map(|s| s.to_string());
248
    let playlist_insert_id =
249
        get_optional_key_as!(as_u64, "playlist_insert_id", event).map(|i| i as usize);
250
    let playlist_insert_num_entries =
251
        get_optional_key_as!(as_u64, "playlist_insert_num_entries", event).map(|i| i as usize);
252

            
253
    Ok(Event::EndFile {
254
        reason: reason
255
            .parse()
256
            .unwrap_or(EventEndFileReason::Unimplemented(reason.to_string())),
257
        playlist_entry_id,
258
        file_error,
259
        playlist_insert_id,
260
        playlist_insert_num_entries,
261
    })
262
}
263

            
264
fn parse_log_message(event: &Map<String, Value>) -> Result<Event, MpvError> {
265
    let prefix = get_key_as!(as_str, "prefix", event).to_owned();
266
    let level = get_key_as!(as_str, "level", event);
267
    let text = get_key_as!(as_str, "text", event).to_owned();
268

            
269
    Ok(Event::LogMessage {
270
        prefix,
271
        level: level
272
            .parse()
273
            .unwrap_or(EventLogMessageLevel::Unimplemented(level.to_string())),
274
        text,
275
    })
276
}
277

            
278
fn parse_hook(event: &Map<String, Value>) -> Result<Event, MpvError> {
279
    let hook_id = get_key_as!(as_u64, "hook_id", event) as usize;
280
    Ok(Event::Hook { hook_id })
281
}
282

            
283
fn parse_client_message(event: &Map<String, Value>) -> Result<Event, MpvError> {
284
    let args = get_key_as!(as_array, "args", event)
285
        .iter()
286
        .map(|arg| {
287
            arg.as_str()
288
                .ok_or(MpvError::ValueContainsUnexpectedType {
289
                    expected_type: "string".to_owned(),
290
                    received: arg.clone(),
291
                })
292
                .map(|s| s.to_string())
293
        })
294
        .collect::<Result<Vec<String>, MpvError>>()?;
295
    Ok(Event::ClientMessage { args })
296
}
297

            
298
20
fn parse_property_change(event: &Map<String, Value>) -> Result<Event, MpvError> {
299
20
    let id = get_optional_key_as!(as_u64, "id", event);
300
20
    let property_name = get_key_as!(as_str, "name", event);
301
20
    let data = event.get("data").map(json_to_value).transpose()?;
302

            
303
20
    Ok(Event::PropertyChange {
304
20
        id,
305
20
        name: property_name.to_string(),
306
20
        data,
307
20
    })
308
20
}