Hello community,
here is the log from the commit of package python-python-mpv for
openSUSE:Factory checked in at 2020-07-20 21:02:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-mpv (Old)
and /work/SRC/openSUSE:Factory/.python-python-mpv.new.3592 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-python-mpv"
Mon Jul 20 21:02:36 2020 rev:14 rq:821925 version:0.5.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-python-mpv/python-python-mpv.changes
2020-07-17 20:50:46.756915856 +0200
+++
/work/SRC/openSUSE:Factory/.python-python-mpv.new.3592/python-python-mpv.changes
2020-07-20 21:04:36.981290325 +0200
@@ -1,0 +2,21 @@
+Mon Jul 20 14:50:15 UTC 2020 - [email protected]
+
+- Update to version 0.5.1
+ * mpv.py: terminate: Raise warning when called from event
+ thread.
+ * mpv.py: add wait_for_shutdown
+ * mpv.py: add check_core_alive, check core in __getattr__,
+ __setattr__
+
+-------------------------------------------------------------------
+Mon Jul 20 07:38:56 UTC 2020 - Luigi Baldoni <[email protected]>
+
+- Update to version 0.5.0
+ * mpv.py: add prepare_and_wait_for_property
+ * mpv.py: Update copyright date
+ * mpv.py: Add docstrings to new additions to API
+ * Sprinkle some thread safety over event loop, add
+ *wait_for_event
+ * mpv.py: improve shutdown handling, replace wait_for_playback
+
+-------------------------------------------------------------------
Old:
----
python-mpv-0.4.8.tar.gz
New:
----
python-mpv-0.5.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-python-mpv.spec ++++++
--- /var/tmp/diff_new_pack.vh2SNY/_old 2020-07-20 21:04:39.661293043 +0200
+++ /var/tmp/diff_new_pack.vh2SNY/_new 2020-07-20 21:04:39.665293046 +0200
@@ -17,7 +17,7 @@
Name: python-python-mpv
-Version: 0.4.8
+Version: 0.5.1
Release: 0
Summary: Python interface to the mpv media player
License: AGPL-3.0-or-later
++++++ python-mpv-0.4.8.tar.gz -> python-mpv-0.5.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-mpv-0.4.8/PKG-INFO
new/python-mpv-0.5.1/PKG-INFO
--- old/python-mpv-0.4.8/PKG-INFO 2020-07-16 21:06:35.000000000 +0200
+++ new/python-mpv-0.5.1/PKG-INFO 2020-07-20 14:20:52.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: python-mpv
-Version: 0.4.8
+Version: 0.5.1
Summary: A python interface to the mpv media player
Home-page: https://github.com/jaseg/python-mpv
Author: jaseg
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-mpv-0.4.8/mpv.py new/python-mpv-0.5.1/mpv.py
--- old/python-mpv-0.4.8/mpv.py 2020-07-16 19:19:44.000000000 +0200
+++ new/python-mpv-0.5.1/mpv.py 2020-07-19 22:29:09.000000000 +0200
@@ -2,7 +2,7 @@
# vim: ts=4 sw=4 et
#
# Python MPV library module
-# Copyright (C) 2017 Sebastian Götte <[email protected]>
+# Copyright (C) 2017-2020 Sebastian Götte <[email protected]>
#
# This program is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation, either version
3 of the License, or (at your option) any
@@ -23,6 +23,7 @@
import sys
from warnings import warn
from functools import partial, wraps
+from contextlib import contextmanager
import collections
import re
import traceback
@@ -53,6 +54,9 @@
fs_enc = sys.getfilesystemencoding()
+class ShutdownError(SystemError):
+ pass
+
class MpvHandle(c_void_p):
pass
@@ -633,41 +637,6 @@
yield event
-def _event_loop(event_handle, playback_cond, event_callbacks,
message_handlers, property_handlers, log_handler):
- for event in _event_generator(event_handle):
- try:
- devent = event.as_dict(decoder=lazy_decoder) # copy data from
ctypes
- eid = devent['event_id']
- for callback in event_callbacks:
- callback(devent)
-
- if eid in (MpvEventID.SHUTDOWN, MpvEventID.END_FILE):
- with playback_cond:
- playback_cond.notify_all()
-
- if eid == MpvEventID.PROPERTY_CHANGE:
- pc = devent['event']
- name, value, _fmt = pc['name'], pc['value'], pc['format']
- for handler in property_handlers[name]:
- handler(name, value)
-
- if eid == MpvEventID.LOG_MESSAGE and log_handler is not None:
- ev = devent['event']
- log_handler(ev['level'], ev['prefix'], ev['text'])
-
- if eid == MpvEventID.CLIENT_MESSAGE:
- # {'event': {'args': ['key-binding', 'foo', 'u-', 'g']},
'reply_userdata': 0, 'error': 0, 'event_id': 16}
- target, *args = devent['event']['args']
- if target in message_handlers:
- message_handlers[target](*args)
-
- if eid == MpvEventID.SHUTDOWN:
- _mpv_detach_destroy(event_handle)
- return
-
- except Exception as e:
- traceback.print_exc()
-
_py_to_mpv = lambda name: name.replace('_', '-')
_mpv_to_py = lambda name: name.replace('-', '_')
@@ -840,6 +809,7 @@
self.handle = _mpv_create()
self._event_thread = None
+ self._core_shutdown = False
_mpv_set_option_string(self.handle, b'audio-display', b'no')
istr = lambda o: ('yes' if o else 'no') if type(o) is bool else str(o)
@@ -858,13 +828,13 @@
self.lazy = _DecoderPropertyProxy(self, lazy_decoder)
self._event_callbacks = []
+ self._event_handler_lock = threading.Lock()
self._property_handlers = collections.defaultdict(lambda: [])
+ self._quit_handlers = set()
self._message_handlers = {}
self._key_binding_handlers = {}
- self._playback_cond = threading.Condition()
self._event_handle = _mpv_create_client(self.handle,
b'py_event_handler')
- self._loop = partial(_event_loop, self._event_handle,
self._playback_cond, self._event_callbacks,
- self._message_handlers, self._property_handlers, log_handler)
+ self._log_handler = log_handler
self._stream_protocol_cbs = {}
self._stream_protocol_frontends = collections.defaultdict(lambda: {})
self.register_stream_protocol('python', self._python_stream_open)
@@ -881,32 +851,157 @@
else:
self._event_thread = None
- def wait_for_playback(self):
- """Waits until playback of the current title is paused or done."""
- with self._playback_cond:
- self._playback_cond.wait()
+ def _loop(self):
+ for event in _event_generator(self._event_handle):
+ try:
+ devent = event.as_dict(decoder=lazy_decoder) # copy data from
ctypes
+ eid = devent['event_id']
+
+ with self._event_handler_lock:
+ if eid == MpvEventID.SHUTDOWN:
+ self._core_shutdown = True
+
+ for callback in self._event_callbacks:
+ callback(devent)
+
+ if eid == MpvEventID.PROPERTY_CHANGE:
+ pc = devent['event']
+ name, value, _fmt = pc['name'], pc['value'], pc['format']
+ for handler in self._property_handlers[name]:
+ handler(name, value)
+
+ if eid == MpvEventID.LOG_MESSAGE and self._log_handler is not
None:
+ ev = devent['event']
+ self._log_handler(ev['level'], ev['prefix'], ev['text'])
+
+ if eid == MpvEventID.CLIENT_MESSAGE:
+ # {'event': {'args': ['key-binding', 'foo', 'u-', 'g']},
'reply_userdata': 0, 'error': 0, 'event_id': 16}
+ target, *args = devent['event']['args']
+ if target in self._message_handlers:
+ self._message_handlers[target](*args)
+
+ if eid == MpvEventID.SHUTDOWN:
+ _mpv_detach_destroy(self._event_handle)
+ return
+
+ except Exception as e:
+ print('Exception inside python-mpv event loop:',
file=sys.stderr)
+ traceback.print_exc()
+
+ @property
+ def core_shutdown(self):
+ """Property indicating whether the core has been shut down. Possible
causes for this are e.g. the `quit` command
+ or a user closing the mpv window."""
+ return self._core_shutdown
+
+ def check_core_alive(self):
+ """ This method can be used as a sanity check to tests whether the
core is still alive at the time it is
+ called."""
+ if self._core_shutdown:
+ raise ShutdownError('libmpv core has been shutdown')
def wait_until_paused(self):
- """Waits until playback of the current title is paused or done."""
+ """Waits until playback of the current title is paused or done. Raises
a ShutdownError if the core is shutdown while
+ waiting."""
self.wait_for_property('core-idle')
+ def wait_for_playback(self):
+ """Waits until playback of the current title is finished. Raises a
ShutdownError if the core is shutdown while
+ waiting.
+ """
+ self.wait_for_event('end_file')
+
def wait_until_playing(self):
- """Waits until playback of the current title has started."""
+ """Waits until playback of the current title has started. Raises a
ShutdownError if the core is shutdown while
+ waiting."""
self.wait_for_property('core-idle', lambda idle: not idle)
def wait_for_property(self, name, cond=lambda val: val,
level_sensitive=True):
"""Waits until ``cond`` evaluates to a truthy value on the named
property. This can be used to wait for
- properties such as ``idle_active`` indicating the player is done with
regular playback and just idling around
+ properties such as ``idle_active`` indicating the player is done with
regular playback and just idling around.
+ Raises a ShutdownError when the core is shutdown while waiting.
"""
+ with self.prepare_and_wait_for_property(name, cond, level_sensitive):
+ pass
+
+ def wait_for_shutdown(self):
+ '''Wait for core to shutdown (e.g. through quit() or terminate()).'''
sema = threading.Semaphore(value=0)
+
+ @self.event_callback('shutdown')
+ def shutdown_handler(event):
+ sema.release()
+
+ sema.acquire()
+ shutdown_handler.unregister_mpv_events()
+
+ @contextmanager
+ def prepare_and_wait_for_property(self, name, cond=lambda val: val,
level_sensitive=True):
+ """Context manager that waits until ``cond`` evaluates to a truthy
value on the named property. See
+ prepare_and_wait_for_event for usage.
+ Raises a ShutdownError when the core is shutdown while waiting.
+ """
+ sema = threading.Semaphore(value=0)
+
def observer(name, val):
if cond(val):
sema.release()
self.observe_property(name, observer)
+
+ @self.event_callback('shutdown')
+ def shutdown_handler(event):
+ sema.release()
+
+ yield
if not level_sensitive or not cond(getattr(self, name.replace('-',
'_'))):
sema.acquire()
+
+ self.check_core_alive()
+
+ shutdown_handler.unregister_mpv_events()
self.unobserve_property(name, observer)
+ def wait_for_event(self, *event_types, cond=lambda evt: True):
+ """Waits for the indicated event(s). If cond is given, waits until
cond(event) is true. Raises a ShutdownError
+ if the core is shutdown while waiting. This also happens when
'shutdown' is in event_types.
+ """
+ with self.prepare_and_wait_for_event(*event_types, cond=cond):
+ pass
+
+ @contextmanager
+ def prepare_and_wait_for_event(self, *event_types, cond=lambda evt: True):
+ """Context manager that waits for the indicated event(s) like
wait_for_event after running. If cond is given,
+ waits until cond(event) is true. Raises a ShutdownError if the core is
shutdown while waiting. This also happens
+ when 'shutdown' is in event_types.
+
+ Compared to wait_for_event this handles the case where a thread waits
for an event it itself causes in a
+ thread-safe way. An example from the testsuite is:
+
+ with self.m.prepare_and_wait_for_event('client_message'):
+ self.m.keypress(key)
+
+ Using just wait_for_event it would be impossible to ensure the event
is caught since it may already have been
+ handled in the interval between keypress(...) running and a subsequent
wait_for_event(...) call.
+ """
+ sema = threading.Semaphore(value=0)
+
+ @self.event_callback('shutdown')
+ def shutdown_handler(event):
+ sema.release()
+
+ @self.event_callback(*event_types)
+ def target_handler(evt):
+ if cond(evt):
+ sema.release()
+
+ yield
+ sema.acquire()
+
+ self.check_core_alive()
+
+ shutdown_handler.unregister_mpv_events()
+ target_handler.unregister_mpv_events()
+
def __del__(self):
if self.handle:
self.terminate()
@@ -914,13 +1009,16 @@
def terminate(self):
"""Properly terminates this player instance. Preferably use this
instead of relying on python's garbage
collector to cause this to be called from the object's destructor.
+
+ This method will detach the main libmpv handle and wait for mpv to
shut down and the event thread to finish.
"""
self.handle, handle = None, self.handle
if threading.current_thread() is self._event_thread:
- # Handle special case to allow event handle to be detached.
- # This is necessary since otherwise the event thread would
deadlock itself.
- grim_reaper = threading.Thread(target=lambda:
_mpv_terminate_destroy(handle))
- grim_reaper.start()
+ raise UserWarning('terminate() should not be called from event
thread (e.g. from a callback function). If '
+ 'you want to terminate mpv from here, please call quit()
instead, then sync the main thread '
+ 'against the event thread using e.g. wait_for_shutdown(),
then terminate() from the main thread. '
+ 'This call has been transformed into a call to quit().')
+ self.quit()
else:
_mpv_terminate_destroy(handle)
if self._event_thread:
@@ -1229,6 +1327,9 @@
print("It's loud!", volume)
my_handler.unregister_mpv_properties()
+
+ exit_handler is a function taking no arguments that is called when the
underlying mpv handle is terminated (e.g.
+ from calling MPV.terminate() or issuing a "quit" input command).
"""
self._property_handlers[name].append(handler)
_mpv_observe_property(self._event_handle,
hash(name)&0xffffffffffffffff, name.encode('utf-8'), MpvFormat.NODE)
@@ -1342,14 +1443,16 @@
my_handler.unregister_mpv_events()
"""
def register(callback):
- types = [MpvEventID.from_str(t) if isinstance(t, str) else t for t
in event_types] or MpvEventID.ANY
- @wraps(callback)
- def wrapper(event, *args, **kwargs):
- if event['event_id'] in types:
- callback(event, *args, **kwargs)
- self._event_callbacks.append(wrapper)
- wrapper.unregister_mpv_events =
partial(self.unregister_event_callback, wrapper)
- return wrapper
+ with self._event_handler_lock:
+ self.check_core_alive()
+ types = [MpvEventID.from_str(t) if isinstance(t, str) else t
for t in event_types] or MpvEventID.ANY
+ @wraps(callback)
+ def wrapper(event, *args, **kwargs):
+ if event['event_id'] in types:
+ callback(event, *args, **kwargs)
+ self._event_callbacks.append(wrapper)
+ wrapper.unregister_mpv_events =
partial(self.unregister_event_callback, wrapper)
+ return wrapper
return register
@staticmethod
@@ -1645,6 +1748,7 @@
# Property accessors
def _get_property(self, name, decoder=strict_decoder, fmt=MpvFormat.NODE):
+ self.check_core_alive()
out = create_string_buffer(sizeof(MpvNode))
try:
cval = _mpv_get_property(self.handle, name.encode('utf-8'), fmt,
out)
@@ -1661,6 +1765,7 @@
return None
def _set_property(self, name, value):
+ self.check_core_alive()
ename = name.encode('utf-8')
if isinstance(value, (list, set, dict)):
_1, _2, _3, pointer = _make_node_str_list(value)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-mpv-0.4.8/python_mpv.egg-info/PKG-INFO
new/python-mpv-0.5.1/python_mpv.egg-info/PKG-INFO
--- old/python-mpv-0.4.8/python_mpv.egg-info/PKG-INFO 2020-07-16
21:06:35.000000000 +0200
+++ new/python-mpv-0.5.1/python_mpv.egg-info/PKG-INFO 2020-07-20
14:20:52.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: python-mpv
-Version: 0.4.8
+Version: 0.5.1
Summary: A python interface to the mpv media player
Home-page: https://github.com/jaseg/python-mpv
Author: jaseg
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/python-mpv-0.4.8/setup.py
new/python-mpv-0.5.1/setup.py
--- old/python-mpv-0.4.8/setup.py 2020-07-16 21:06:00.000000000 +0200
+++ new/python-mpv-0.5.1/setup.py 2020-07-20 14:20:10.000000000 +0200
@@ -3,7 +3,7 @@
from setuptools import setup
setup(
name = 'python-mpv',
- version = '0.4.8',
+ version = '0.5.1',
py_modules = ['mpv'],
description = 'A python interface to the mpv media player',
url = 'https://github.com/jaseg/python-mpv',