Continue reworking event infrastrucutre.

Note: We do not decode property values by default anymore.
This commit is contained in:
jaseg 2022-04-22 23:01:13 +02:00
parent f4c9f2fec4
commit c90a5f692f
2 changed files with 66 additions and 33 deletions

83
mpv.py
View file

@ -282,14 +282,14 @@ class MpvEventID(c_int):
SEEK = 20 SEEK = 20
PLAYBACK_RESTART = 21 PLAYBACK_RESTART = 21
PROPERTY_CHANGE = 22 PROPERTY_CHANGE = 22
EVENT_QUEUE_OVERFLOW = 24 QUEUE_OVERFLOW = 24
EVENT_HOOK = 25 HOOK = 25
ANY = ( SHUTDOWN, LOG_MESSAGE, GET_PROPERTY_REPLY, SET_PROPERTY_REPLY, COMMAND_REPLY, START_FILE, END_FILE, ANY = ( SHUTDOWN, LOG_MESSAGE, GET_PROPERTY_REPLY, SET_PROPERTY_REPLY, COMMAND_REPLY, START_FILE, END_FILE,
FILE_LOADED, CLIENT_MESSAGE, VIDEO_RECONFIG, AUDIO_RECONFIG, SEEK, PLAYBACK_RESTART, PROPERTY_CHANGE) FILE_LOADED, CLIENT_MESSAGE, VIDEO_RECONFIG, AUDIO_RECONFIG, SEEK, PLAYBACK_RESTART, PROPERTY_CHANGE)
def __repr__(self): def __repr__(self):
return _mpv_event_name(self.value).decode() return f'<MpvEventID {self.value} (_mpv_event_name(self.value).decode("utf-8"))>'
@classmethod @classmethod
def from_str(kls, s): def from_str(kls, s):
@ -383,15 +383,15 @@ class MpvEvent(Structure):
def data(self): def data(self):
dtype = { dtype = {
MpvEventID.GET_PROPERTY_REPLY: MpvEventProperty, MpvEventID.GET_PROPERTY_REPLY: MpvEventProperty,
MpvEventID.PROPERTY_CHANGE: MpvEventProperty MpvEventID.PROPERTY_CHANGE: MpvEventProperty,
MpvEventID.LOG_MESSAGE: MpvEventLogMessage, MpvEventID.LOG_MESSAGE: MpvEventLogMessage,
MpvEventID.CLIENT_MESSAGE: MpvEventClientMessage, MpvEventID.CLIENT_MESSAGE: MpvEventClientMessage,
MpvEventID.START_FILE: MpvEventStartFile, MpvEventID.START_FILE: MpvEventStartFile,
MpvEventID.END_FILE: MpvEventEndFile, MpvEventID.END_FILE: MpvEventEndFile,
MpvEventID.HOOK: MpvEventHook, MpvEventID.HOOK: MpvEventHook,
MpvEventID.COMMAND_REPLY* MpvEventCommand, MpvEventID.COMMAND_REPLY: MpvEventCommand,
}.get(self.event_id) }.get(self.event_id.value)
return cast(self.data, POINTER(dtype)).contents.as_dict(decoder=decoder) return cast(self._data, POINTER(dtype)).contents if dtype else None
def as_dict(self, decoder=identity_decoder): def as_dict(self, decoder=identity_decoder):
out = cast(create_string_buffer(sizeof(MpvNode)), POINTER(MpvNode)) out = cast(create_string_buffer(sizeof(MpvNode)), POINTER(MpvNode))
@ -400,15 +400,39 @@ class MpvEvent(Structure):
_mpv_free_node_contents(out) _mpv_free_node_contents(out)
return rv return rv
def __str__(self):
d = self.data
return f'<{type(d).__name__} ({self.event_id.value}) err={self.error} p={self.reply_userdata:016x} d={self.as_dict()}>'
class MpvEventProperty(Structure): class MpvEventProperty(Structure):
_fields_ = [('name', c_char_p), _fields_ = [('_name', c_char_p),
('format', MpvFormat), ('format', MpvFormat),
('data', MpvNodeUnion)] ('data', MpvNodeUnion)]
@property
def name(self):
return self._name.decode("utf-8")
@property
def value(self):
return MpvNode.node_cast_value(self.data, self.format.value)
class MpvEventLogMessage(Structure): class MpvEventLogMessage(Structure):
_fields_ = [('prefix', c_char_p), _fields_ = [('_prefix', c_char_p),
('level', c_char_p), ('_level', c_char_p),
('text', c_char_p)] ('_text', c_char_p)]
@property
def prefix(self):
return self._prefix.decode("utf-8")
@property
def level(self):
return self._level.decode("utf-8")
@property
def text(self):
return self._text
class MpvEventEndFile(Structure): class MpvEventEndFile(Structure):
_fields_ = [('reason', c_int), _fields_ = [('reason', c_int),
@ -421,30 +445,32 @@ class MpvEventEndFile(Structure):
ERROR = 4 ERROR = 4
REDIRECT = 5 REDIRECT = 5
# For backwards-compatibility
@property
def value(self):
return self.reason
class MpvEventStartFile(Structure): class MpvEventStartFile(Structure):
_fields_ = [('playlist_entry_id', c_ulonglong),] _fields_ = [('playlist_entry_id', c_ulonglong),]
class MpvEventScriptInputDispatch(Structure):
_fields_ = [('arg0', c_int),
('type', c_char_p)]
class MpvEventClientMessage(Structure): class MpvEventClientMessage(Structure):
_fields_ = [('num_args', c_int), _fields_ = [('_num_args', c_int),
('args', POINTER(c_char_p))] ('_args', POINTER(c_char_p))]
@property
def args(self):
return [ self._args[i] for i in range(self._num_args) ]
class MpvEventCommand(Structure): class MpvEventCommand(Structure):
_fields_ = [('result', MpvNode)] _fields_ = [('_result', MpvNode)]
def result(self):
return self._result.node_value()
class MpvEventHook(Structure): class MpvEventHook(Structure):
_fields_ = [('name', c_char_p), _fields_ = [('_name', c_char_p),
('id', c_ulonglong),] ('id', c_ulonglong),]
@property
def name(self):
return self._name.decode("utf-8")
StreamReadFn = CFUNCTYPE(c_int64, c_void_p, POINTER(c_char), c_uint64) StreamReadFn = CFUNCTYPE(c_int64, c_void_p, POINTER(c_char), c_uint64)
StreamSeekFn = CFUNCTYPE(c_int64, c_void_p, c_int64) StreamSeekFn = CFUNCTYPE(c_int64, c_void_p, c_int64)
StreamSizeFn = CFUNCTYPE(c_int64, c_void_p) StreamSizeFn = CFUNCTYPE(c_int64, c_void_p)
@ -856,13 +882,15 @@ class MPV(object):
for event in _event_generator(self._event_handle): for event in _event_generator(self._event_handle):
try: try:
eid = event.event_id.value eid = event.event_id.value
if not eid == MpvEventID.LOG_MESSAGE:
print(event)
with self._event_handler_lock: with self._event_handler_lock:
if eid == MpvEventID.SHUTDOWN: if eid == MpvEventID.SHUTDOWN:
self._core_shutdown = True self._core_shutdown = True
for callback in self._event_callbacks: for callback in self._event_callbacks:
callback(devent) callback(event)
if eid == MpvEventID.PROPERTY_CHANGE: if eid == MpvEventID.PROPERTY_CHANGE:
pc = event.data pc = event.data
@ -877,6 +905,7 @@ class MPV(object):
if eid == MpvEventID.CLIENT_MESSAGE: if eid == MpvEventID.CLIENT_MESSAGE:
# {'event': {'args': ['key-binding', 'foo', 'u-', 'g']}, 'reply_userdata': 0, 'error': 0, 'event_id': 16} # {'event': {'args': ['key-binding', 'foo', 'u-', 'g']}, 'reply_userdata': 0, 'error': 0, 'event_id': 16}
target, *args = event.data.args target, *args = event.data.args
target = target.decode("utf-8")
if target in self._message_handlers: if target in self._message_handlers:
self._message_handlers[target](*args) self._message_handlers[target](*args)
@ -1659,6 +1688,10 @@ class MPV(object):
self.command('enable-section', binding_name, 'allow-hide-cursor+allow-vo-dragging') self.command('enable-section', binding_name, 'allow-hide-cursor+allow-vo-dragging')
def _handle_key_binding_message(self, binding_name, key_state, key_name=None, key_char=None): def _handle_key_binding_message(self, binding_name, key_state, key_name=None, key_char=None):
binding_name = binding_name.decode('utf-8')
key_state = key_state.decode('utf-8')
key_name = key_name.decode('utf-8') if key_name is not None else None
key_char = key_char.decode('utf-8') if key_char is not None else None
self._key_binding_handlers[binding_name](key_state, key_name, key_char) self._key_binding_handlers[binding_name](key_state, key_name, key_char)
def unregister_key_binding(self, keydef): def unregister_key_binding(self, keydef):

View file

@ -243,7 +243,7 @@ class ObservePropertyTest(MpvTestCase):
time.sleep(0.1) #couple frames time.sleep(0.1) #couple frames
m.terminate() # needed for synchronization of event thread m.terminate() # needed for synchronization of event thread
handler.assert_has_calls([mock.call('vid', 'auto')]) handler.assert_has_calls([mock.call('vid', b'auto')])
def test_property_observer_decorator(self): def test_property_observer_decorator(self):
handler = mock.Mock() handler = mock.Mock()
@ -380,7 +380,7 @@ class KeyBindingTest(MpvTestCase):
def test_wait_for_property_error_forwarding(self): def test_wait_for_property_error_forwarding(self):
def run(): def run():
nonlocal self nonlocal self
self.m.wait_until_playing() self.m.wait_until_playing(timeout=2)
self.m.mute = True self.m.mute = True
t = threading.Thread(target=run, daemon=True) t = threading.Thread(target=run, daemon=True)
t.start() t.start()
@ -396,7 +396,7 @@ class KeyBindingTest(MpvTestCase):
def test_register_simple_decorator_fun_chaining(self): def test_register_simple_decorator_fun_chaining(self):
self.m.loop = 'inf' self.m.loop = 'inf'
self.m.play(TESTVID) self.m.play(TESTVID)
self.m.wait_until_playing() self.m.wait_until_playing(timeout=2)
handler1, handler2 = mock.Mock(), mock.Mock() handler1, handler2 = mock.Mock(), mock.Mock()
@ -412,7 +412,7 @@ class KeyBindingTest(MpvTestCase):
self.assertEqual(reg_test_fun.mpv_key_bindings, ['b', 'a']) self.assertEqual(reg_test_fun.mpv_key_bindings, ['b', 'a'])
def keypress_and_sync(key): def keypress_and_sync(key):
with self.m.prepare_and_wait_for_event('client_message'): with self.m.prepare_and_wait_for_event('client_message', timeout=2):
self.m.keypress(key) self.m.keypress(key)
keypress_and_sync('a') keypress_and_sync('a')
@ -610,7 +610,7 @@ class TestLifecycle(unittest.TestCase):
handler() handler()
t = threading.Thread(target=run, daemon=True) t = threading.Thread(target=run, daemon=True)
t.start() t.start()
m.wait_until_playing() m.wait_until_playing(timeout=2)
m.mute = True m.mute = True
t.join() t.join()
m.terminate() m.terminate()
@ -682,7 +682,7 @@ class TestLifecycle(unittest.TestCase):
m = mpv.MPV(vo=testvo, log_handler=handler) m = mpv.MPV(vo=testvo, log_handler=handler)
m.play(TESTVID) m.play(TESTVID)
# Wait for playback to start # Wait for playback to start
m.wait_until_playing() m.wait_until_playing(timeout=2)
m.command("print-text", 'This is a python-mpv test') m.command("print-text", 'This is a python-mpv test')
m.wait_for_playback() m.wait_for_playback()
m.terminate() m.terminate()
@ -713,7 +713,7 @@ class CommandTests(MpvTestCase):
time.sleep(0.5) time.sleep(0.5)
self.m.loadfile(TESTVID) self.m.loadfile(TESTVID)
self.m.wait_until_playing() self.m.wait_until_playing(timeout=2)
self.m.sub_add(TESTSRT) self.m.sub_add(TESTSRT)
self.m.wait_for_playback() self.m.wait_for_playback()
@ -727,7 +727,7 @@ class CommandTests(MpvTestCase):
time.sleep(0.5) time.sleep(0.5)
self.m.loadfile(TESTVID) self.m.loadfile(TESTVID)
self.m.wait_until_playing() self.m.wait_until_playing(timeout=2)
self.m.command_async('sub_add', TESTSRT, callback=callback) self.m.command_async('sub_add', TESTSRT, callback=callback)
reply = self.m.command_async('expand-text', 'test ${mute}') reply = self.m.command_async('expand-text', 'test ${mute}')
assert reply.result() == 'test no' assert reply.result() == 'test no'