From 107c563f8d71d1eda8098f797620996ac1e60eee Mon Sep 17 00:00:00 2001 From: Elias Müller Date: Mon, 28 Sep 2020 20:24:15 +0200 Subject: mpv.py: add support for asynchronous commands --- mpv.py | 52 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/mpv.py b/mpv.py index 7ffb198..82df90b 100644 --- a/mpv.py +++ b/mpv.py @@ -390,6 +390,7 @@ class MpvEvent(Structure): def as_dict(self, decoder=identity_decoder): dtype = {MpvEventID.END_FILE: MpvEventEndFile, + MpvEventID.COMMAND_REPLY: MpvEventCommand, MpvEventID.PROPERTY_CHANGE: MpvEventProperty, MpvEventID.GET_PROPERTY_REPLY: MpvEventProperty, MpvEventID.LOG_MESSAGE: MpvEventLogMessage, @@ -455,6 +456,14 @@ class MpvEventClientMessage(Structure): def as_dict(self, decoder=identity_decoder): return { 'args': [ self.args[i].decode('utf-8') for i in range(self.num_args) ] } + +class MpvEventCommand(Structure): + _fields_ = [('result', MpvNode)] + + def as_dict(self, decoder=identity_decoder): + return {'result': self.result.node_value(decoder)} + + StreamReadFn = CFUNCTYPE(c_int64, c_void_p, POINTER(c_char), c_uint64) StreamSeekFn = CFUNCTYPE(c_int64, c_void_p, c_int64) StreamSizeFn = CFUNCTYPE(c_int64, c_void_p) @@ -544,7 +553,6 @@ _handle_func('mpv_command', [POINTER(c_char_p)], _handle_func('mpv_command_string', [c_char_p, c_char_p], c_int, ec_errcheck) _handle_func('mpv_command_async', [c_ulonglong, POINTER(c_char_p)], c_int, ec_errcheck) _handle_func('mpv_command_node', [POINTER(MpvNode), POINTER(MpvNode)], c_int, ec_errcheck) -_handle_func('mpv_command_async', [c_ulonglong, POINTER(MpvNode)], c_int, ec_errcheck) _handle_func('mpv_set_property', [c_char_p, MpvFormat, c_void_p], c_int, ec_errcheck) _handle_func('mpv_set_property_string', [c_char_p, c_char_p], c_int, ec_errcheck) @@ -641,6 +649,12 @@ def _event_generator(handle): yield event +def _create_null_term_cmd_arg_array(name, args): + args = [name.encode('utf-8')] + [(arg if type(arg) is bytes else str(arg).encode('utf-8')) + for arg in args if arg is not None] + [None] + return (c_char_p * len(args))(*args) + + _py_to_mpv = lambda name: name.replace('_', '-') _mpv_to_py = lambda name: name.replace('-', '_') @@ -805,6 +819,9 @@ class MPV(object): To make your program not barf hard the first time its used on a weird file system **always** access properties containing file names or file tags through ``MPV.raw``. """ + + _UINT_64_MAX = 2 ** 64 - 1 + def __init__(self, *extra_mpv_flags, log_handler=None, start_event_thread=True, loglevel=None, **extra_mpv_opts): """Create an MPV instance. @@ -832,6 +849,9 @@ class MPV(object): self.lazy = _DecoderPropertyProxy(self, lazy_decoder) self._event_callbacks = [] + self._event_async_callbacks = {} + self._event_async_callback_counter = 0 + self._event_async_callback_counter_lock = threading.Lock() self._event_handler_lock = threading.Lock() self._property_handlers = collections.defaultdict(lambda: []) self._quit_handlers = set() @@ -884,6 +904,15 @@ class MPV(object): if target in self._message_handlers: self._message_handlers[target](*args) + if eid == MpvEventID.COMMAND_REPLY: + key = devent['reply_userdata'] + err = devent['error'] + callback = self._event_async_callbacks.pop(key, None) + if callback: + callback(err, devent['event']['result']) + elif err: + warn('Error while executing asynchronous command') + if eid == MpvEventID.SHUTDOWN: _mpv_destroy(self._event_handle) return @@ -1072,9 +1101,17 @@ class MPV(object): def string_command(self, name, *args): """Execute a raw command.""" - args = [name.encode('utf-8')] + [ (arg if type(arg) is bytes else str(arg).encode('utf-8')) - for arg in args if arg is not None ] + [None] - _mpv_command(self.handle, (c_char_p*len(args))(*args)) + args = _create_null_term_cmd_arg_array(name, args) + _mpv_command(self.handle, args) + + def command_async(self, name, *args, callback=None): + """Same as mpv_command, but run the command asynchronously. Once the command ran, the callback will be invoked, + if provided. The first argument of the callback will be a boolean value. It will be set to True, if there was an + error, False else. The second argument of the callback depends on the command. + """ + args = _create_null_term_cmd_arg_array(name, args) + key = self._register_async_callback(callback) + _mpv_command_async(self._event_handle, key, args) def node_command(self, name, *args, decoder=strict_decoder): self.command(name, *args, decoder=decoder) @@ -1853,6 +1890,13 @@ class MPV(object): except AttributeError: return None + def _register_async_callback(self, callback): + with self._event_async_callback_counter_lock: + key = self._event_async_callback_counter = (self._event_async_callback_counter + 1) % MPV._UINT_64_MAX + self._event_async_callbacks[key] = callback + return key + + class MpvRenderContext: def __init__(self, mpv, api_type, **kwargs): self._mpv = mpv -- cgit