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::{MpvDataType, MpvError, ipc::MpvIpcEvent, message_parser::json_to_value};
9

            
10
/// Reason behind the `MPV_EVENT_END_FILE` event.
11
///
12
/// Ref: <https://mpv.io/manual/stable/#command-interface-mpv-event-end-file>
13
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
14
#[serde(rename_all = "kebab-case")]
15
pub enum EventEndFileReason {
16
    /// The file has ended. This can (but doesn't have to) include
17
    /// incomplete files or broken network connections under circumstances.
18
    Eof,
19

            
20
    /// Playback was ended by a command.
21
    Stop,
22

            
23
    /// Playback was ended by sending the quit command.
24
    Quit,
25

            
26
    /// An error happened. In this case, an `error` field is present with the error string.
27
    Error,
28

            
29
    /// Happens with playlists and similar. For details, see
30
    /// [`MPV_END_FILE_REASON_REDIRECT`](https://github.com/mpv-player/mpv/blob/72efbfd009a2b3259055133d74b88c81b1115ae1/include/mpv/client.h#L1493)
31
    /// in the C API.
32
    Redirect,
33

            
34
    /// Unknown. Normally doesn't happen, unless the Lua API is out of sync
35
    /// with the C API. (Likewise, it could happen that your script gets reason
36
    /// strings that did not exist yet at the time your script was written.)
37
    Unknown,
38

            
39
    /// A catch-all enum variant in case `mpvipc-async` has not implemented the
40
    /// returned error yet.
41
    Unimplemented(String),
42
}
43

            
44
impl FromStr for EventEndFileReason {
45
    type Err = ();
46

            
47
3
    fn from_str(s: &str) -> Result<Self, Self::Err> {
48
3
        match s {
49
3
            "eof" => Ok(EventEndFileReason::Eof),
50
2
            "stop" => Ok(EventEndFileReason::Stop),
51
2
            "quit" => Ok(EventEndFileReason::Quit),
52
2
            "error" => Ok(EventEndFileReason::Error),
53
1
            "redirect" => Ok(EventEndFileReason::Redirect),
54
1
            reason => Ok(EventEndFileReason::Unimplemented(reason.to_string())),
55
        }
56
3
    }
57
}
58

            
59
/// The log level of a log message event.
60
///
61
/// Ref:
62
/// - <https://mpv.io/manual/stable/#command-interface-mpv-event-log-message>
63
/// - <https://mpv.io/manual/stable/#mp-msg-functions>
64
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65
#[serde(rename_all = "kebab-case")]
66
pub enum EventLogMessageLevel {
67
    Info,
68
    Warn,
69
    Error,
70
    Fatal,
71
    Verbose,
72
    Debug,
73
    Trace,
74

            
75
    /// A catch-all enum variant in case `mpvipc-async` has not implemented the
76
    /// returned log-level yet.
77
    Unimplemented(String),
78
}
79

            
80
impl FromStr for EventLogMessageLevel {
81
    type Err = ();
82

            
83
1
    fn from_str(s: &str) -> Result<Self, Self::Err> {
84
1
        match s {
85
1
            "info" => Ok(EventLogMessageLevel::Info),
86
            "warn" => Ok(EventLogMessageLevel::Warn),
87
            "error" => Ok(EventLogMessageLevel::Error),
88
            "fatal" => Ok(EventLogMessageLevel::Fatal),
89
            "verbose" => Ok(EventLogMessageLevel::Verbose),
90
            "debug" => Ok(EventLogMessageLevel::Debug),
91
            "trace" => Ok(EventLogMessageLevel::Trace),
92
            level => Ok(EventLogMessageLevel::Unimplemented(level.to_string())),
93
        }
94
1
    }
95
}
96

            
97
/// All possible events that can be sent by mpv.
98
///
99
/// Not all event types are guaranteed to be implemented.
100
/// If something is missing, please open an issue.
101
///
102
/// Otherwise, the event will be returned as an `Event::Unimplemented` variant.
103
///
104
/// See <https://mpv.io/manual/master/#list-of-events> for
105
/// the upstream list of events.
106
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
107
#[serde(rename_all = "kebab-case")]
108
pub enum Event {
109
    StartFile {
110
        playlist_entry_id: usize,
111
    },
112
    EndFile {
113
        reason: EventEndFileReason,
114
        playlist_entry_id: usize,
115
        file_error: Option<String>,
116
        playlist_insert_id: Option<usize>,
117
        playlist_insert_num_entries: Option<usize>,
118
    },
119
    FileLoaded,
120
    Seek,
121
    PlaybackRestart,
122
    Shutdown,
123
    LogMessage {
124
        prefix: String,
125
        level: EventLogMessageLevel,
126
        text: String,
127
    },
128
    Hook {
129
        hook_id: usize,
130
    },
131
    GetPropertyReply,
132
    SetPropertyReply,
133
    CommandReply {
134
        result: String,
135
    },
136
    ClientMessage {
137
        args: Vec<String>,
138
    },
139
    VideoReconfig,
140
    AudioReconfig,
141
    PropertyChange {
142
        id: Option<u64>,
143
        name: String,
144
        data: Option<MpvDataType>,
145
    },
146
    EventQueueOverflow,
147
    None,
148

            
149
    /// Deprecated since mpv v0.33.0
150
    Idle,
151

            
152
    /// Deprecated since mpv v0.31.0
153
    Tick,
154

            
155
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
156
    TracksChanged,
157

            
158
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
159
    TrackSwitched,
160

            
161
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
162
    Pause,
163

            
164
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
165
    Unpause,
166

            
167
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
168
    MetadataUpdate,
169

            
170
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
171
    ChapterChange,
172

            
173
    /// Deprecated since mpv v0.7.0, removed in mpv v0.35.0
174
    ScriptInputDispatch,
175

            
176
    /// Catch-all for unimplemented events
177
    Unimplemented(Map<String, Value>),
178
}
179

            
180
macro_rules! get_key_as {
181
    ($as_type:ident, $key:expr, $event:ident) => {{
182
        let tmp = $event.get($key).ok_or(MpvError::MissingKeyInObject {
183
            key: $key.to_owned(),
184
            map: $event.clone(),
185
        })?;
186

            
187
        tmp.$as_type()
188
            .ok_or(MpvError::ValueContainsUnexpectedType {
189
                expected_type: stringify!($as_type).strip_prefix("as_").unwrap().to_owned(),
190
                received: tmp.clone(),
191
            })?
192
    }};
193
}
194

            
195
macro_rules! get_optional_key_as {
196
    ($as_type:ident, $key:expr, $event:ident) => {{
197
        match $event.get($key) {
198
            Some(Value::Null) => None,
199
            Some(tmp) => Some(
200
                tmp.$as_type()
201
                    .ok_or(MpvError::ValueContainsUnexpectedType {
202
                        expected_type: stringify!($as_type).strip_prefix("as_").unwrap().to_owned(),
203
                        received: tmp.clone(),
204
                    })?,
205
            ),
206
            None => None,
207
        }
208
    }};
209
}
210

            
211
// NOTE: I have not been able to test all of these events,
212
//       so some of the parsing logic might be incorrect.
213
//       In particular, I have not been able to make mpv
214
//       produce any of the commented out events, and since
215
//       the documentation for the most part just says
216
//       "See C API", I have not pursued this further.
217
//
218
//       If you need this, please open an issue or a PR.
219

            
220
/// Parse a highlevel [`Event`] objects from json.
221
53
pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, MpvError> {
222
53
    let MpvIpcEvent(event) = raw_event;
223

            
224
53
    event
225
53
        .as_object()
226
53
        .ok_or(MpvError::ValueContainsUnexpectedType {
227
53
            expected_type: "object".to_owned(),
228
53
            received: event.clone(),
229
53
        })
230
53
        .and_then(|event| {
231
53
            let event_name = get_key_as!(as_str, "event", event);
232

            
233
53
            match event_name {
234
53
                "start-file" => parse_start_file(event),
235
52
                "end-file" => parse_end_file(event),
236
49
                "file-loaded" => Ok(Event::FileLoaded),
237
48
                "seek" => Ok(Event::Seek),
238
47
                "playback-restart" => Ok(Event::PlaybackRestart),
239
46
                "shutdown" => Ok(Event::Shutdown),
240
45
                "log-message" => parse_log_message(event),
241
44
                "hook" => parse_hook(event),
242

            
243
                // TODO: fix these. They are asynchronous responses to different requests.
244
                //       see:
245
                //         - https://github.com/mpv-player/mpv/blob/5f768a688b706cf94041adf5bed7c7004af2ec5a/libmpv/client.h#L1158-L1160
246
                //         - https://github.com/mpv-player/mpv/blob/5f768a688b706cf94041adf5bed7c7004af2ec5a/libmpv/client.h#L1095-L1098
247
                //         - https://github.com/mpv-player/mpv/blob/5f768a688b706cf94041adf5bed7c7004af2ec5a/libmpv/client.h#L972-L982
248
                // "get-property-reply" =>
249
                // "set-property-reply" =>
250
                // "command-reply" =>
251
43
                "client-message" => parse_client_message(event),
252
42
                "video-reconfig" => Ok(Event::VideoReconfig),
253
41
                "audio-reconfig" => Ok(Event::AudioReconfig),
254
40
                "property-change" => parse_property_change(event),
255
17
                "tick" => Ok(Event::Tick),
256
16
                "idle" => Ok(Event::Idle),
257
7
                "tracks-changed" => Ok(Event::TracksChanged),
258
6
                "track-switched" => Ok(Event::TrackSwitched),
259
5
                "pause" => Ok(Event::Pause),
260
4
                "unpause" => Ok(Event::Unpause),
261
3
                "metadata-update" => Ok(Event::MetadataUpdate),
262
2
                "chapter-change" => Ok(Event::ChapterChange),
263
1
                _ => Ok(Event::Unimplemented(event.to_owned())),
264
            }
265
53
        })
266
53
}
267

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

            
271
1
    Ok(Event::StartFile { playlist_entry_id })
272
1
}
273

            
274
3
fn parse_end_file(event: &Map<String, Value>) -> Result<Event, MpvError> {
275
3
    let reason = get_key_as!(as_str, "reason", event);
276
3
    let playlist_entry_id = get_key_as!(as_u64, "playlist_entry_id", event) as usize;
277
3
    let file_error = get_optional_key_as!(as_str, "file_error", event).map(|s| s.to_string());
278
3
    let playlist_insert_id =
279
3
        get_optional_key_as!(as_u64, "playlist_insert_id", event).map(|i| i as usize);
280
3
    let playlist_insert_num_entries =
281
3
        get_optional_key_as!(as_u64, "playlist_insert_num_entries", event).map(|i| i as usize);
282

            
283
3
    Ok(Event::EndFile {
284
3
        reason: reason
285
3
            .parse()
286
3
            .unwrap_or(EventEndFileReason::Unimplemented(reason.to_string())),
287
3
        playlist_entry_id,
288
3
        file_error,
289
3
        playlist_insert_id,
290
3
        playlist_insert_num_entries,
291
3
    })
292
3
}
293

            
294
1
fn parse_log_message(event: &Map<String, Value>) -> Result<Event, MpvError> {
295
1
    let prefix = get_key_as!(as_str, "prefix", event).to_owned();
296
1
    let level = get_key_as!(as_str, "level", event);
297
1
    let text = get_key_as!(as_str, "text", event).to_owned();
298

            
299
1
    Ok(Event::LogMessage {
300
1
        prefix,
301
1
        level: level
302
1
            .parse()
303
1
            .unwrap_or(EventLogMessageLevel::Unimplemented(level.to_string())),
304
1
        text,
305
1
    })
306
1
}
307

            
308
1
fn parse_hook(event: &Map<String, Value>) -> Result<Event, MpvError> {
309
1
    let hook_id = get_key_as!(as_u64, "hook_id", event) as usize;
310
1
    Ok(Event::Hook { hook_id })
311
1
}
312

            
313
1
fn parse_client_message(event: &Map<String, Value>) -> Result<Event, MpvError> {
314
1
    let args = get_key_as!(as_array, "args", event)
315
1
        .iter()
316
3
        .map(|arg| {
317
3
            arg.as_str()
318
3
                .ok_or(MpvError::ValueContainsUnexpectedType {
319
3
                    expected_type: "string".to_owned(),
320
3
                    received: arg.clone(),
321
3
                })
322
3
                .map(|s| s.to_string())
323
3
        })
324
1
        .collect::<Result<Vec<String>, MpvError>>()?;
325
1
    Ok(Event::ClientMessage { args })
326
1
}
327

            
328
23
fn parse_property_change(event: &Map<String, Value>) -> Result<Event, MpvError> {
329
23
    let id = get_optional_key_as!(as_u64, "id", event);
330
23
    let property_name = get_key_as!(as_str, "name", event);
331
23
    let data = event.get("data").map(json_to_value).transpose()?;
332

            
333
23
    Ok(Event::PropertyChange {
334
23
        id,
335
23
        name: property_name.to_string(),
336
23
        data,
337
23
    })
338
23
}
339

            
340
#[cfg(test)]
341
mod tests {
342
    use super::*;
343
    use crate::ipc::MpvIpcEvent;
344
    use serde_json::json;
345

            
346
    #[test]
347
1
    fn test_parse_simple_events() {
348
1
        let simple_events = vec![
349
1
            (json!({"event": "file-loaded"}), Event::FileLoaded),
350
1
            (json!({"event": "seek"}), Event::Seek),
351
1
            (json!({"event": "playback-restart"}), Event::PlaybackRestart),
352
1
            (json!({"event": "shutdown"}), Event::Shutdown),
353
1
            (json!({"event": "video-reconfig"}), Event::VideoReconfig),
354
1
            (json!({"event": "audio-reconfig"}), Event::AudioReconfig),
355
1
            (json!({"event": "tick"}), Event::Tick),
356
1
            (json!({"event": "idle"}), Event::Idle),
357
1
            (json!({"event": "tracks-changed"}), Event::TracksChanged),
358
1
            (json!({"event": "track-switched"}), Event::TrackSwitched),
359
1
            (json!({"event": "pause"}), Event::Pause),
360
1
            (json!({"event": "unpause"}), Event::Unpause),
361
1
            (json!({"event": "metadata-update"}), Event::MetadataUpdate),
362
1
            (json!({"event": "chapter-change"}), Event::ChapterChange),
363
        ];
364

            
365
14
        for (raw_event_json, expected_event) in simple_events {
366
14
            let raw_event = MpvIpcEvent(raw_event_json);
367
14
            let event = parse_event(raw_event).unwrap();
368
14
            assert_eq!(event, expected_event);
369
        }
370
1
    }
371

            
372
    #[test]
373
1
    fn test_parse_start_file_event() {
374
1
        let raw_event = MpvIpcEvent(json!({
375
1
            "event": "start-file",
376
1
            "playlist_entry_id": 1
377
1
        }));
378

            
379
1
        let event = parse_event(raw_event).unwrap();
380

            
381
1
        assert_eq!(
382
            event,
383
            Event::StartFile {
384
                playlist_entry_id: 1
385
            }
386
        );
387
1
    }
388

            
389
    #[test]
390
1
    fn test_parse_end_file_event() {
391
1
        let raw_event = MpvIpcEvent(json!({
392
1
            "event": "end-file",
393
1
            "reason": "eof",
394
1
            "playlist_entry_id": 2,
395
1
            "file_error": null,
396
1
            "playlist_insert_id": 3,
397
1
            "playlist_insert_num_entries": 5
398
1
        }));
399
1
        let event = parse_event(raw_event).unwrap();
400
1
        assert_eq!(
401
            event,
402
            Event::EndFile {
403
                reason: EventEndFileReason::Eof,
404
                playlist_entry_id: 2,
405
                file_error: None,
406
                playlist_insert_id: Some(3),
407
                playlist_insert_num_entries: Some(5)
408
            }
409
        );
410

            
411
1
        let raw_event_with_error = MpvIpcEvent(json!({
412
1
            "event": "end-file",
413
1
            "reason": "error",
414
1
            "playlist_entry_id": 4,
415
1
            "file_error": "File not found",
416
1
        }));
417
1
        let event_with_error = parse_event(raw_event_with_error).unwrap();
418
1
        assert_eq!(
419
            event_with_error,
420
1
            Event::EndFile {
421
1
                reason: EventEndFileReason::Error,
422
1
                playlist_entry_id: 4,
423
1
                file_error: Some("File not found".to_string()),
424
1
                playlist_insert_id: None,
425
1
                playlist_insert_num_entries: None,
426
1
            }
427
        );
428

            
429
1
        let raw_event_unimplemented = MpvIpcEvent(json!({
430
1
            "event": "end-file",
431
1
            "reason": "unknown-reason",
432
1
            "playlist_entry_id": 5
433
1
        }));
434
1
        let event_unimplemented = parse_event(raw_event_unimplemented).unwrap();
435
1
        assert_eq!(
436
            event_unimplemented,
437
1
            Event::EndFile {
438
1
                reason: EventEndFileReason::Unimplemented("unknown-reason".to_string()),
439
1
                playlist_entry_id: 5,
440
1
                file_error: None,
441
1
                playlist_insert_id: None,
442
1
                playlist_insert_num_entries: None,
443
1
            }
444
        );
445
1
    }
446

            
447
    #[test]
448
1
    fn test_parse_log_message_event() {
449
1
        let raw_event = MpvIpcEvent(json!({
450
1
            "event": "log-message",
451
1
            "prefix": "mpv",
452
1
            "level": "info",
453
1
            "text": "This is a log message"
454
1
        }));
455
1
        let event = parse_event(raw_event).unwrap();
456
1
        assert_eq!(
457
            event,
458
1
            Event::LogMessage {
459
1
                prefix: "mpv".to_string(),
460
1
                level: EventLogMessageLevel::Info,
461
1
                text: "This is a log message".to_string(),
462
1
            }
463
        );
464
1
    }
465

            
466
    #[test]
467
1
    fn test_parse_hook_event() {
468
1
        let raw_event = MpvIpcEvent(json!({
469
1
            "event": "hook",
470
1
            "hook_id": 42
471
1
        }));
472
1
        let event = parse_event(raw_event).unwrap();
473
1
        assert_eq!(event, Event::Hook { hook_id: 42 });
474
1
    }
475

            
476
    #[test]
477
1
    fn test_parse_client_message_event() {
478
1
        let raw_event = MpvIpcEvent(json!({
479
1
            "event": "client-message",
480
1
            "args": ["arg1", "arg2", "arg3"]
481
1
        }));
482
1
        let event = parse_event(raw_event).unwrap();
483
1
        assert_eq!(
484
            event,
485
1
            Event::ClientMessage {
486
1
                args: vec!["arg1".to_string(), "arg2".to_string(), "arg3".to_string()]
487
1
            }
488
        );
489
1
    }
490

            
491
    #[test]
492
1
    fn test_parse_property_change_event() {
493
1
        let raw_event = MpvIpcEvent(json!({
494
1
            "event": "property-change",
495
1
            "id": 1,
496
1
            "name": "pause",
497
1
            "data": true
498
1
        }));
499
1
        let event = parse_event(raw_event).unwrap();
500
1
        assert_eq!(
501
            event,
502
1
            Event::PropertyChange {
503
1
                id: Some(1),
504
1
                name: "pause".to_string(),
505
1
                data: Some(MpvDataType::Bool(true)),
506
1
            }
507
        );
508
1
    }
509

            
510
    #[test]
511
1
    fn test_parse_unimplemented_event() {
512
1
        let raw_event = MpvIpcEvent(json!({
513
1
            "event": "some-unimplemented-event",
514
1
            "some_key": "some_value"
515
1
        }));
516
1
        let event = parse_event(raw_event).unwrap();
517
1
        assert_eq!(
518
            event,
519
1
            Event::Unimplemented(
520
1
                json!({
521
1
                    "event": "some-unimplemented-event",
522
1
                    "some_key": "some_value"
523
1
                })
524
1
                .as_object()
525
1
                .unwrap()
526
1
                .to_owned()
527
1
            )
528
        );
529
1
    }
530
}