mpvipc_async/
event_parser.rs

1//! JSON parsing logic for events from [`MpvIpc`](crate::ipc::MpvIpc).
2
3use std::str::FromStr;
4
5use serde::{Deserialize, Serialize};
6use serde_json::{Map, Value};
7
8use crate::{ipc::MpvIpcEvent, message_parser::json_to_value, MpvDataType, MpvError};
9
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[serde(rename_all = "kebab-case")]
12pub enum EventEndFileReason {
13    Eof,
14    Stop,
15    Quit,
16    Error,
17    Redirect,
18    Unknown,
19    Unimplemented(String),
20}
21
22impl 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")]
39pub enum EventLogMessageLevel {
40    Info,
41    Warn,
42    Error,
43    Fatal,
44    Verbose,
45    Debug,
46    Trace,
47    Unimplemented(String),
48}
49
50impl 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")]
78pub 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
150macro_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
165macro_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.
191pub(crate) fn parse_event(raw_event: MpvIpcEvent) -> Result<Event, MpvError> {
192    let MpvIpcEvent(event) = raw_event;
193
194    event
195        .as_object()
196        .ok_or(MpvError::ValueContainsUnexpectedType {
197            expected_type: "object".to_owned(),
198            received: event.clone(),
199        })
200        .and_then(|event| {
201            let event_name = get_key_as!(as_str, "event", event);
202
203            match event_name {
204                "start-file" => parse_start_file(event),
205                "end-file" => parse_end_file(event),
206                "file-loaded" => Ok(Event::FileLoaded),
207                "seek" => Ok(Event::Seek),
208                "playback-restart" => Ok(Event::PlaybackRestart),
209                "shutdown" => Ok(Event::Shutdown),
210                "log-message" => parse_log_message(event),
211                "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                "client-message" => parse_client_message(event),
222                "video-reconfig" => Ok(Event::VideoReconfig),
223                "audio-reconfig" => Ok(Event::AudioReconfig),
224                "property-change" => parse_property_change(event),
225                "tick" => Ok(Event::Tick),
226                "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        })
236}
237
238fn 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
244fn 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
264fn 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
278fn 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
283fn 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
298fn parse_property_change(event: &Map<String, Value>) -> Result<Event, MpvError> {
299    let id = get_optional_key_as!(as_u64, "id", event);
300    let property_name = get_key_as!(as_str, "name", event);
301    let data = event.get("data").map(json_to_value).transpose()?;
302
303    Ok(Event::PropertyChange {
304        id,
305        name: property_name.to_string(),
306        data,
307    })
308}