mpv.py: add support for asynchronous commands

This commit is contained in:
Elias Müller 2020-09-28 20:24:15 +02:00 committed by jaseg
parent e8c8736a1a
commit 107c563f8d

52
mpv.py
View file

@ -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