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, Error, ErrorCode, MpvDataType};
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: usize,
113
        name: String,
114
        data: 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
// NOTE: I have not been able to test all of these events,
151
//       so some of the parsing logic might be incorrect.
152
//       In particular, I have not been able to make mpv
153
//       produce any of the commented out events, and since
154
//       the documentation for the most part just says
155
//       "See C API", I have not pursued this further.
156
//
157
//       If you need this, please open an issue or a PR.
158

            
159
/// Parse a highlevel [`Event`] objects from json.
160
#[allow(deprecated)]
161
8
pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, Error> {
162
8
    let MpvIpcEvent(event) = raw_event;
163
8

            
164
8
    event
165
8
        .as_object()
166
8
        .ok_or(Error(ErrorCode::JsonContainsUnexptectedType))
167
8
        .and_then(|event| {
168
8
            let event_name = event
169
8
                .get("event")
170
8
                .ok_or(Error(ErrorCode::MissingValue))?
171
8
                .as_str()
172
8
                .ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
173

            
174
8
            match event_name {
175
8
                "start-file" => parse_start_file(event),
176
8
                "end-file" => parse_end_file(event),
177
8
                "file-loaded" => Ok(Event::FileLoaded),
178
8
                "seek" => Ok(Event::Seek),
179
8
                "playback-restart" => Ok(Event::PlaybackRestart),
180
8
                "shutdown" => Ok(Event::Shutdown),
181
8
                "log-message" => parse_log_message(event),
182
8
                "hook" => parse_hook(event),
183
                // "get-property-reply" =>
184
                // "set-property-reply" =>
185
                // "command-reply" =>
186
8
                "client-message" => parse_client_message(event),
187
8
                "video-reconfig" => Ok(Event::VideoReconfig),
188
8
                "audio-reconfig" => Ok(Event::AudioReconfig),
189
8
                "property-change" => parse_property_change(event),
190
                "tick" => Ok(Event::Tick),
191
                "idle" => Ok(Event::Idle),
192
                "tracks-changed" => Ok(Event::TracksChanged),
193
                "track-switched" => Ok(Event::TrackSwitched),
194
                "pause" => Ok(Event::Pause),
195
                "unpause" => Ok(Event::Unpause),
196
                "metadata-update" => Ok(Event::MetadataUpdate),
197
                "chapter-change" => Ok(Event::ChapterChange),
198
                _ => Ok(Event::Unimplemented(event.to_owned())),
199
            }
200
8
        })
201
8
}
202

            
203
fn parse_start_file(event: &Map<String, Value>) -> Result<Event, Error> {
204
    let playlist_entry_id = event
205
        .get("playlist_entry_id")
206
        .ok_or(Error(ErrorCode::MissingValue))?
207
        .as_u64()
208
        .ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
209
    Ok(Event::StartFile { playlist_entry_id })
210
}
211

            
212
fn parse_end_file(event: &Map<String, Value>) -> Result<Event, Error> {
213
    let reason = event
214
        .get("reason")
215
        .ok_or(Error(ErrorCode::MissingValue))?
216
        .as_str()
217
        .ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
218
    let playlist_entry_id = event
219
        .get("playlist_entry_id")
220
        .ok_or(Error(ErrorCode::MissingValue))?
221
        .as_u64()
222
        .ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
223
    let file_error = event
224
        .get("file_error")
225
        .and_then(|v| v.as_str().map(|s| s.to_string()));
226
    let playlist_insert_id = event
227
        .get("playlist_insert_id")
228
        .and_then(|v| v.as_u64().map(|u| u as usize));
229
    let playlist_insert_num_entries = event
230
        .get("playlist_insert_num_entries")
231
        .and_then(|v| v.as_u64().map(|u| u as usize));
232

            
233
    Ok(Event::EndFile {
234
        reason: reason
235
            .parse()
236
            .unwrap_or(EventEndFileReason::Unimplemented(reason.to_string())),
237
        playlist_entry_id,
238
        file_error,
239
        playlist_insert_id,
240
        playlist_insert_num_entries,
241
    })
242
}
243

            
244
fn parse_log_message(event: &Map<String, Value>) -> Result<Event, Error> {
245
    let prefix = event
246
        .get("prefix")
247
        .ok_or(Error(ErrorCode::MissingValue))?
248
        .as_str()
249
        .ok_or(Error(ErrorCode::ValueDoesNotContainString))?
250
        .to_string();
251
    let level = event
252
        .get("level")
253
        .ok_or(Error(ErrorCode::MissingValue))?
254
        .as_str()
255
        .ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
256
    let text = event
257
        .get("text")
258
        .ok_or(Error(ErrorCode::MissingValue))?
259
        .as_str()
260
        .ok_or(Error(ErrorCode::ValueDoesNotContainString))?
261
        .to_string();
262

            
263
    Ok(Event::LogMessage {
264
        prefix,
265
        level: level
266
            .parse()
267
            .unwrap_or(EventLogMessageLevel::Unimplemented(level.to_string())),
268
        text,
269
    })
270
}
271

            
272
fn parse_hook(event: &Map<String, Value>) -> Result<Event, Error> {
273
    let hook_id = event
274
        .get("hook_id")
275
        .ok_or(Error(ErrorCode::MissingValue))?
276
        .as_u64()
277
        .ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
278
    Ok(Event::Hook { hook_id })
279
}
280

            
281
fn parse_client_message(event: &Map<String, Value>) -> Result<Event, Error> {
282
    let args = event
283
        .get("args")
284
        .ok_or(Error(ErrorCode::MissingValue))?
285
        .as_array()
286
        .ok_or(Error(ErrorCode::ValueDoesNotContainString))?
287
        .iter()
288
        .map(|arg| {
289
            arg.as_str()
290
                .ok_or(Error(ErrorCode::ValueDoesNotContainString))
291
                .map(|s| s.to_string())
292
        })
293
        .collect::<Result<Vec<String>, Error>>()?;
294
    Ok(Event::ClientMessage { args })
295
}
296

            
297
8
fn parse_property_change(event: &Map<String, Value>) -> Result<Event, Error> {
298
8
    let id = event
299
8
        .get("id")
300
8
        .ok_or(Error(ErrorCode::MissingValue))?
301
8
        .as_u64()
302
8
        .ok_or(Error(ErrorCode::ValueDoesNotContainUsize))? as usize;
303
8
    let property_name = event
304
8
        .get("name")
305
8
        .ok_or(Error(ErrorCode::MissingValue))?
306
8
        .as_str()
307
8
        .ok_or(Error(ErrorCode::ValueDoesNotContainString))?;
308
8
    let data = event
309
8
        .get("data")
310
8
        .ok_or(Error(ErrorCode::MissingValue))?
311
8
        .clone();
312
8

            
313
8
    Ok(Event::PropertyChange {
314
8
        id,
315
8
        name: property_name.to_string(),
316
8
        data: json_to_value(&data)?,
317
    })
318
8
}