summaryrefslogtreecommitdiff
path: root/mpv.py
diff options
context:
space:
mode:
authorjaseg <code@jaseg.net>2014-08-13 23:51:07 +0200
committerjaseg <code@jaseg.net>2014-08-13 23:51:07 +0200
commitbaee4f4e227f60358d0bfc6cbcdd85bb553e1000 (patch)
treeb0036c79550db6ad9c12dd22d1c86ef4c392a399 /mpv.py
parent2e6516aa32efe7ba7090138e7834a4eb89801163 (diff)
downloadpython-mpv-baee4f4e227f60358d0bfc6cbcdd85bb553e1000.tar.gz
python-mpv-baee4f4e227f60358d0bfc6cbcdd85bb553e1000.tar.bz2
python-mpv-baee4f4e227f60358d0bfc6cbcdd85bb553e1000.zip
Added support for yet-unknown error codes
Diffstat (limited to 'mpv.py')
-rw-r--r--mpv.py985
1 files changed, 497 insertions, 488 deletions
diff --git a/mpv.py b/mpv.py
index e015a71..be8a54f 100644
--- a/mpv.py
+++ b/mpv.py
@@ -11,167 +11,172 @@ backend = CDLL('libmpv.so')
class MpvHandle(c_void_p):
- pass
+ pass
class ErrorCode:
- """ For documentation on these, see mpv's libmpv/client.h """
- SUCCESS = 0
- EVENT_QUEUE_FULL = -1
- NOMEM = -2
- UNINITIALIZED = -3
- INVALID_PARAMETER = -4
- OPTION_NOT_FOUND = -5
- OPTION_FORMAT = -6
- OPTION_ERROR = -7
- PROPERTY_NOT_FOUND = -8
- PROPERTY_FORMAT = -9
- PROPERTY_UNAVAILABLE = -10
- PROPERTY_ERROR = -11
- COMMAND = -12
-
- EXCEPTION_DICT = {
- 0: None,
- -1: lambda *a: MemoryError('mpv event queue full', *a),
- -2: lambda *a: MemoryError('mpv cannot allocate memory', *a),
- -3: lambda *a: ValueError('Uninitialized mpv handle used', *a),
- -4: lambda *a: ValueError('Invalid value for mpv parameter', *a),
- -5: lambda *a: AttributeError('mpv option does not exist', *a),
- -6: lambda *a: TypeError('Tried to set mpv option using wrong format', *a),
- -7: lambda *a: ValueError('Invalid value for mpv option', *a),
- -8: lambda *a: AttributeError('mpv property does not exist', *a),
- -9: lambda *a: TypeError('Tried to set mpv property using wrong format', *a),
- -10: lambda *a: AttributeError('mpv property is not available', *a),
- -11: lambda *a: ValueError('Invalid value for mpv property', *a),
- -12: lambda *a: SystemError('Error running mpv command', *a)
- }
-
- @classmethod
- def raise_for_ec(kls, func, *args):
- ec = kls.EXCEPTION_DICT[func(*args)]
- if ec:
- raise ec(*args)
+ """ For documentation on these, see mpv's libmpv/client.h """
+ SUCCESS = 0
+ EVENT_QUEUE_FULL = -1
+ NOMEM = -2
+ UNINITIALIZED = -3
+ INVALID_PARAMETER = -4
+ OPTION_NOT_FOUND = -5
+ OPTION_FORMAT = -6
+ OPTION_ERROR = -7
+ PROPERTY_NOT_FOUND = -8
+ PROPERTY_FORMAT = -9
+ PROPERTY_UNAVAILABLE = -10
+ PROPERTY_ERROR = -11
+ COMMAND = -12
+
+ EXCEPTION_DICT = {
+ 0: None,
+ -1: lambda *a: MemoryError('mpv event queue full', *a),
+ -2: lambda *a: MemoryError('mpv cannot allocate memory', *a),
+ -3: lambda *a: ValueError('Uninitialized mpv handle used', *a),
+ -4: lambda *a: ValueError('Invalid value for mpv parameter', *a),
+ -5: lambda *a: AttributeError('mpv option does not exist', *a),
+ -6: lambda *a: TypeError('Tried to set mpv option using wrong format', *a),
+ -7: lambda *a: ValueError('Invalid value for mpv option', *a),
+ -8: lambda *a: AttributeError('mpv property does not exist', *a),
+ -9: lambda *a: TypeError('Tried to set mpv property using wrong format', *a),
+ -10: lambda *a: AttributeError('mpv property is not available', *a),
+ -11: lambda *a: ValueError('Invalid value for mpv property', *a),
+ -12: lambda *a: SystemError('Error running mpv command', *a)
+ }
+
+ @classmethod
+ def DEFAULT_ERROR_HANDLER(ec, *args):
+ return ValueError(_mpv_error_string(ec).encode('utf-8'), ec, *a)
+
+ @classmethod
+ def raise_for_ec(kls, func, *args):
+ ec = func(*args)
+ ex = kls.EXCEPTION_DICT.get(ec , kls.DEFAULT_ERROR_HANDLER)
+ if ex:
+ raise ex(ec, *args)
class MpvFormat(c_int):
- NONE = 0
- STRING = 1
- OSD_STRING = 2
- FLAG = 3
- INT64 = 4
- DOUBLE = 5
- NODE = 6
- NODE_ARRAY = 7
- NODE_MAP = 8
+ NONE = 0
+ STRING = 1
+ OSD_STRING = 2
+ FLAG = 3
+ INT64 = 4
+ DOUBLE = 5
+ NODE = 6
+ NODE_ARRAY = 7
+ NODE_MAP = 8
class MpvEventID(c_int):
- NONE = 0
- SHUTDOWN = 1
- LOG_MESSAGE = 2
- GET_PROPERTY_REPLY = 3
- SET_PROPERTY_REPLY = 4
- COMMAND_REPLY = 5
- START_FILE = 6
- END_FILE = 7
- FILE_LOADED = 8
- TRACKS_CHANGED = 9
- TRACK_SWITCHED = 10
- IDLE = 11
- PAUSE = 12
- UNPAUSE = 13
- TICK = 14
- SCRIPT_INPUT_DISPATCH = 15
- CLIENT_MESSAGE = 16
- VIDEO_RECONFIG = 17
- AUDIO_RECONFIG = 18
- METADATA_UPDATE = 19
- SEEK = 20
- PLAYBACK_RESTART = 21
- PROPERTY_CHANGE = 22
- CHAPTER_CHANGE = 23
-
- ANY = ( SHUTDOWN, LOG_MESSAGE, GET_PROPERTY_REPLY, SET_PROPERTY_REPLY, COMMAND_REPLY, START_FILE, END_FILE,
- FILE_LOADED, TRACKS_CHANGED, TRACK_SWITCHED, IDLE, PAUSE, UNPAUSE, TICK, SCRIPT_INPUT_DISPATCH,
- CLIENT_MESSAGE, VIDEO_RECONFIG, AUDIO_RECONFIG, METADATA_UPDATE, SEEK, PLAYBACK_RESTART, PROPERTY_CHANGE,
- CHAPTER_CHANGE )
+ NONE = 0
+ SHUTDOWN = 1
+ LOG_MESSAGE = 2
+ GET_PROPERTY_REPLY = 3
+ SET_PROPERTY_REPLY = 4
+ COMMAND_REPLY = 5
+ START_FILE = 6
+ END_FILE = 7
+ FILE_LOADED = 8
+ TRACKS_CHANGED = 9
+ TRACK_SWITCHED = 10
+ IDLE = 11
+ PAUSE = 12
+ UNPAUSE = 13
+ TICK = 14
+ SCRIPT_INPUT_DISPATCH = 15
+ CLIENT_MESSAGE = 16
+ VIDEO_RECONFIG = 17
+ AUDIO_RECONFIG = 18
+ METADATA_UPDATE = 19
+ SEEK = 20
+ PLAYBACK_RESTART = 21
+ PROPERTY_CHANGE = 22
+ CHAPTER_CHANGE = 23
+
+ ANY = ( SHUTDOWN, LOG_MESSAGE, GET_PROPERTY_REPLY, SET_PROPERTY_REPLY, COMMAND_REPLY, START_FILE, END_FILE,
+ FILE_LOADED, TRACKS_CHANGED, TRACK_SWITCHED, IDLE, PAUSE, UNPAUSE, TICK, SCRIPT_INPUT_DISPATCH,
+ CLIENT_MESSAGE, VIDEO_RECONFIG, AUDIO_RECONFIG, METADATA_UPDATE, SEEK, PLAYBACK_RESTART, PROPERTY_CHANGE,
+ CHAPTER_CHANGE )
class MpvEvent(Structure):
- _fields_ = [('event_id', MpvEventID),
- ('error', c_int),
- ('reply_userdata', c_ulonglong),
- ('data', c_void_p)]
-
- def as_dict(self):
- dtype = {MpvEventID.END_FILE: MpvEventEndFile,
- MpvEventID.PROPERTY_CHANGE: MpvEventProperty,
- MpvEventID.GET_PROPERTY_REPLY: MpvEventProperty,
- MpvEventID.LOG_MESSAGE: MpvEventLogMessage,
- MpvEventID.SCRIPT_INPUT_DISPATCH: MpvEventScriptInputDispatch,
- MpvEventID.CLIENT_MESSAGE: MpvEventClientMessage
- }.get(self.event_id.value, None)
- return {'event_id': self.event_id.value,
- 'error': self.error,
- 'reply_userdata': self.reply_userdata,
- 'event': cast(self.data, POINTER(dtype)).contents.as_dict() if dtype else None}
+ _fields_ = [('event_id', MpvEventID),
+ ('error', c_int),
+ ('reply_userdata', c_ulonglong),
+ ('data', c_void_p)]
+
+ def as_dict(self):
+ dtype = {MpvEventID.END_FILE: MpvEventEndFile,
+ MpvEventID.PROPERTY_CHANGE: MpvEventProperty,
+ MpvEventID.GET_PROPERTY_REPLY: MpvEventProperty,
+ MpvEventID.LOG_MESSAGE: MpvEventLogMessage,
+ MpvEventID.SCRIPT_INPUT_DISPATCH: MpvEventScriptInputDispatch,
+ MpvEventID.CLIENT_MESSAGE: MpvEventClientMessage
+ }.get(self.event_id.value, None)
+ return {'event_id': self.event_id.value,
+ 'error': self.error,
+ 'reply_userdata': self.reply_userdata,
+ 'event': cast(self.data, POINTER(dtype)).contents.as_dict() if dtype else None}
class MpvEventProperty(Structure):
- _fields_ = [('name', c_char_p),
- ('format', MpvFormat),
- ('data', c_void_p)]
- def as_dict():
- pass # FIXME
+ _fields_ = [('name', c_char_p),
+ ('format', MpvFormat),
+ ('data', c_void_p)]
+ def as_dict():
+ pass # FIXME
class MpvEventLogMessage(Structure):
- _fields_ = [('prefix', c_char_p),
- ('level', c_char_p),
- ('text', c_char_p)]
+ _fields_ = [('prefix', c_char_p),
+ ('level', c_char_p),
+ ('text', c_char_p)]
- def as_dict(self):
- return { name: getattr(self, name).value for name, _t in _fields_ }
+ def as_dict(self):
+ return { name: getattr(self, name).value for name, _t in _fields_ }
class MpvEventEndFile(c_int):
- EOF_OR_INIT_FAILURE = 0
- RESTARTED = 1
- ABORTED = 2
- QUIT = 3
+ EOF_OR_INIT_FAILURE = 0
+ RESTARTED = 1
+ ABORTED = 2
+ QUIT = 3
- def as_dict(self):
- return {'reason': self.value}
+ def as_dict(self):
+ return {'reason': self.value}
class MpvEventScriptInputDispatch(Structure):
- _fields_ = [('arg0', c_int),
- ('type', c_char_p)]
+ _fields_ = [('arg0', c_int),
+ ('type', c_char_p)]
- def as_dict(self):
- pass # TODO
+ def as_dict(self):
+ pass # TODO
class MpvEventClientMessage(Structure):
- _fields_ = [('num_args', c_int),
- ('args', POINTER(c_char_p))]
+ _fields_ = [('num_args', c_int),
+ ('args', POINTER(c_char_p))]
- def as_dict(self):
- return { 'args': [ self.args[i].value for i in range(self.num_args.value) ] }
+ def as_dict(self):
+ return { 'args': [ self.args[i].value for i in range(self.num_args.value) ] }
WakeupCallback = CFUNCTYPE(None, c_void_p)
def _handle_func(name, args=[], res=None):
- func = getattr(backend, name)
- if res is not None:
- func.restype = res
- func.argtypes = [MpvHandle] + args
- def wrapper(*args):
- if res is not None:
- return func(*args)
- else:
- ErrorCode.raise_for_ec(func, *args)
- globals()['_'+name] = wrapper
+ func = getattr(backend, name)
+ if res is not None:
+ func.restype = res
+ func.argtypes = [MpvHandle] + args
+ def wrapper(*args):
+ if res is not None:
+ return func(*args)
+ else:
+ ErrorCode.raise_for_ec(func, *args)
+ globals()['_'+name] = wrapper
backend.mpv_client_api_version.restype = c_ulong
def _mpv_client_api_version():
- ver = backend.mpv_client_api_version()
- return ver>>16, ver&0xFFFF
+ ver = backend.mpv_client_api_version()
+ return ver>>16, ver&0xFFFF
backend.mpv_free.argtypes = [c_void_p]
_mpv_free = backend.mpv_free
@@ -209,6 +214,10 @@ backend.mpv_event_name.restype = c_char_p
backend.mpv_event_name.argtypes = [c_int]
_mpv_event_name = backend.mpv_event_name
+backend.mpv_error_string.restype = c_char_p
+backend.mpv_error_string.argtypes = [c_int]
+_mpv_error_string = backend.mpv_error_string
+
_handle_func('mpv_request_event', [MpvEventID, c_int])
_handle_func('mpv_request_log_messages', [c_char_p])
_handle_func('mpv_wait_event', [c_double], POINTER(MpvEvent))
@@ -218,31 +227,31 @@ _handle_func('mpv_get_wakeup_pipe', [], c_int)
class ynbool:
- def __init__(self, val=False):
- if not val or val == b'no' or val == 'no':
- self.val = False
- else:
- self.val = True
+ def __init__(self, val=False):
+ if not val or val == b'no' or val == 'no':
+ self.val = False
+ else:
+ self.val = True
- def __nonzero__(self):
- return self.val
+ def __nonzero__(self):
+ return self.val
- def __str__(self):
- return 'yes' if self.val else 'no'
+ def __str__(self):
+ return 'yes' if self.val else 'no'
- def __repr__(self):
- return str(self.val)
+ def __repr__(self):
+ return str(self.val)
def _ensure_encoding(possibly_bytes):
- return possibly_bytes.decode('utf8') if type(possibly_bytes) is bytes else possibly_bytes
+ return possibly_bytes.decode('utf8') if type(possibly_bytes) is bytes else possibly_bytes
def _event_generator(handle):
- while True:
- event = _mpv_wait_event(handle, 0).contents
- if event.event_id.value == MpvEventID.NONE:
- raise StopIteration()
- yield event
+ while True:
+ event = _mpv_wait_event(handle, 0).contents
+ if event.event_id.value == MpvEventID.NONE:
+ raise StopIteration()
+ yield event
def load_lua():
""" Use this function if you intend to use mpv's built-in lua interpreter. This is e.g. needed for playback of
@@ -250,350 +259,350 @@ def load_lua():
CDLL('liblua.so', mode=RTLD_GLOBAL)
class MPV:
- """ See man mpv(1) for the details of the implemented commands. """
- def __init__(self, loop=None, **kwargs):
- self.handle = _mpv_create()
-
- self.event_callbacks = []
- self._event_fd = _mpv_get_wakeup_pipe(self.handle)
- self._playback_cond = threading.Condition()
- def mpv_event_extractor():
- os.read(self._event_fd, 512)
- for event in _event_generator(self.handle):
- devent = event.as_dict() # copy data from ctypes
- if devent['event_id'] in (MpvEventID.SHUTDOWN, MpvEventID.END_FILE, MpvEventID.PAUSE):
- with self._playback_cond:
- self._playback_cond.notify_all()
- for callback in self.event_callbacks:
- callback.call()
- if loop:
- loop.add_reader(self._event_fd, mpv_event_extractor)
- else:
- def loop_runner():
- loop = asyncio.new_event_loop()
- loop.add_reader(self._event_fd, mpv_event_extractor)
- try:
- loop.run_forever()
- finally:
- loop.close()
-
- self._event_thread = threading.Thread(target=loop_runner, daemon=True)
- self._event_thread.start()
-
- _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)
- for k,v in kwargs.items():
- _mpv_set_option_string(self.handle, k.replace('_', '-').encode('utf8'), istr(v).encode('utf8'))
- _mpv_initialize(self.handle)
-
- def wait_for_playback(self):
- with self._playback_cond:
- self._playback_cond.wait()
-
-# def __del__(self):
-# _mpv_terminate_destroy(self.handle)
-
- def command(self, name, *args):
- args = [name.encode('utf8')] + [ str(arg).encode('utf8') for arg in args if arg is not None ] + [None]
- _mpv_command(self.handle, (c_char_p*len(args))(*args))
-
- def seek(self, amount, reference="relative", precision="default-precise"):
- assert reference in ('relative', 'absolute', 'absolute-percent')
- assert precision in ('default-precise', 'exact', 'keyframes')
- self.command('seek', amount, reference, precision)
-
- def revert_seek(self):
- self.command('revert_seek');
-
- def frame_step(self):
- self.command('frame_step')
-
- def frame_back_step(self):
- self.command('frame_back_step')
-
- def _set_property(self, name, value):
- self.command('set_property', name, str(value))
-
- def _add_property(self, name, value=None):
- self.command('add_property', name, value)
-
- def _cycle_property(self, name, direction='up'):
- self.command('cycle_property', name, direction)
-
- def _multiply_property(self, name, factor):
- self.command('multiply_property', name, factor)
-
- def screenshot(self, includes='subtitles', mode='single'):
- assert includes in ('subtitles', 'video', 'window')
- assert mode in ('single', 'each-frame')
- self.command('screenshot', includes, mode)
-
- def screenshot_to_file(self, filename, includes='subtitles'):
- assert includes in ('subtitles', 'video', 'window')
- self.command('screenshot_to_file', filename, includes)
-
- def playlist_next(self, mode='weak'):
- assert mode in ('weak', 'force')
- self.command('playlist_next', mode)
-
- def playlist_prev(self, mode='weak'):
- assert mode in ('weak', 'force')
- self.command('playlist_prev', mode)
-
- def loadfile(self, filename, mode='replace'):
- assert mode in ('replace', 'append', 'append-play')
- self.command('loadfile', filename, mode)
-
- def loadlist(self, playlist, mode='replace'):
- self.command('loadlist', playlist, mode)
-
- def playlist_clear(self):
- self.command('playlist_clear')
-
- def playlist_remove(self, index='current'):
- self.command('playlist_remove', index)
-
- def playlist_move(self, index1, index2):
- self.command('playlist_move', index1, index2)
-
- def run(self, command, *args):
- self.command('run', command, *args)
-
- def quit(self, code=None):
- self.command('quit', code)
-
- def quit_watch_later(self, code=None):
- self.command('quit_watch_later', code)
-
- def sub_add(self, filename):
- self.command('sub_add', filename)
-
- def sub_remove(self, sub_id=None):
- self.command('sub_remove', sub_id)
-
- def sub_reload(self, sub_id=None):
- self.command('sub_reload', sub_id)
-
- def sub_step(self, skip):
- self.command('sub_step', skip)
-
- def sub_seek(self, skip):
- self.command('sub_seek', skip)
-
- def toggle_osd(self):
- self.command('osd')
-
- def show_text(self, string, duration='-', level=None):
- self.command('show_text', string, duration, level)
-
- def show_progress(self):
- self.command('show_progress')
-
- def discnav(self, command):
- assert command in ('up', 'up', 'down', 'down', 'left', 'right', 'left', 'right', 'menu', 'select',
- 'prev', 'mouse', 'mouse_move')
- self.command('discnav', command)
-
- def write_watch_later_config(self):
- self.command('write_watch_later_config')
-
- def overlay_add(self, overlay_id, x, y, file_or_fd, offset, fmt, w, h, stride):
- self.command('overlay_add', overlay_id, x, y, file_or_fd, offset, fmt, w, h, stride)
-
- def overlay_remove(self, overlay_id):
- self.command('overlay_remove', overlay_id)
-
- def script_message(self, *args):
- self.command('script_message', *args)
-
- def script_message_to(self, target, *args):
- self.command('script_message_to', target, *args)
-
- @property
- def metadata(self):
- ...
- def chapter_metadata(self):
- ...
-
- def vf_metadata(self):
- ...
-
- # Convenience functions
- def play(self, filename):
- self.command('loadfile', filename)
-
- # Complex properties
-
- _VIDEO_PARAMS_LIST = (
- ('pixelformat', str),
- ('w', int),
- ('h', int),
- ('dw', int),
- ('dh', int),
- ('aspect', float),
- ('par', float),
- ('colormatrix', str),
- ('colorlevels', str),
- ('chroma-location', str),
- ('rotate', int))
-
- @property
- def video_params(self):
- return self._get_dict('video-params/', _VIDEO_PARAMS_LIST)
-
- @property
- def video_out_params(self):
- return self._get_dict('video-out-params/', _VIDEO_PARAMS_LIST)
-
- @property
- def playlist(self):
- return self._get_list('playlist/', (('filename', str),))
- @property
- def track_list(self):
- return self._get_list('track-list/', (
- ('id', int),
- ('type', str),
- ('src-id', int),
- ('title', str),
- ('lang', str),
- ('albumart', ynbool),
- ('default', ynbool),
- ('external', ynbool),
- ('external-filename', str),
- ('codec', str),
- ('selected', ynbool)))
- @property
- def chapter_list(self):
- return self._get_dict('chapter-list/', (('title', str), ('time', float)))
-
- def _get_dict(self, prefix, props):
- return { name: proptype(_ensure_encoding(_mpv_get_property_string(self.handle, (prefix+name).encode('utf8')))) for name, proptype in props }
-
- def _get_list(self, prefix, props):
- count = int(_ensure_encoding(_mpv_get_property_string(self.handle, (prefix+'count').encode('utf8'))))
- return [ self._get_dict(prefix+str(index)+'/', props) for index in range(count)]
-
- # TODO: af, vf properties
- # TODO: edition-list
- # TODO property-mapped options
-
-
+ """ See man mpv(1) for the details of the implemented commands. """
+ def __init__(self, loop=None, **kwargs):
+ self.handle = _mpv_create()
+
+ self.event_callbacks = []
+ self._event_fd = _mpv_get_wakeup_pipe(self.handle)
+ self._playback_cond = threading.Condition()
+ def mpv_event_extractor():
+ os.read(self._event_fd, 512)
+ for event in _event_generator(self.handle):
+ devent = event.as_dict() # copy data from ctypes
+ if devent['event_id'] in (MpvEventID.SHUTDOWN, MpvEventID.END_FILE, MpvEventID.PAUSE):
+ with self._playback_cond:
+ self._playback_cond.notify_all()
+ for callback in self.event_callbacks:
+ callback.call()
+ if loop:
+ loop.add_reader(self._event_fd, mpv_event_extractor)
+ else:
+ def loop_runner():
+ loop = asyncio.new_event_loop()
+ loop.add_reader(self._event_fd, mpv_event_extractor)
+ try:
+ loop.run_forever()
+ finally:
+ loop.close()
+
+ self._event_thread = threading.Thread(target=loop_runner, daemon=True)
+ self._event_thread.start()
+
+ _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)
+ for k,v in kwargs.items():
+ _mpv_set_option_string(self.handle, k.replace('_', '-').encode('utf8'), istr(v).encode('utf8'))
+ _mpv_initialize(self.handle)
+
+ def wait_for_playback(self):
+ with self._playback_cond:
+ self._playback_cond.wait()
+
+# def __del__(self):
+# _mpv_terminate_destroy(self.handle)
+
+ def command(self, name, *args):
+ args = [name.encode('utf8')] + [ str(arg).encode('utf8') for arg in args if arg is not None ] + [None]
+ _mpv_command(self.handle, (c_char_p*len(args))(*args))
+
+ def seek(self, amount, reference="relative", precision="default-precise"):
+ assert reference in ('relative', 'absolute', 'absolute-percent')
+ assert precision in ('default-precise', 'exact', 'keyframes')
+ self.command('seek', amount, reference, precision)
+
+ def revert_seek(self):
+ self.command('revert_seek');
+
+ def frame_step(self):
+ self.command('frame_step')
+
+ def frame_back_step(self):
+ self.command('frame_back_step')
+
+ def _set_property(self, name, value):
+ self.command('set_property', name, str(value))
+
+ def _add_property(self, name, value=None):
+ self.command('add_property', name, value)
+
+ def _cycle_property(self, name, direction='up'):
+ self.command('cycle_property', name, direction)
+
+ def _multiply_property(self, name, factor):
+ self.command('multiply_property', name, factor)
+
+ def screenshot(self, includes='subtitles', mode='single'):
+ assert includes in ('subtitles', 'video', 'window')
+ assert mode in ('single', 'each-frame')
+ self.command('screenshot', includes, mode)
+
+ def screenshot_to_file(self, filename, includes='subtitles'):
+ assert includes in ('subtitles', 'video', 'window')
+ self.command('screenshot_to_file', filename, includes)
+
+ def playlist_next(self, mode='weak'):
+ assert mode in ('weak', 'force')
+ self.command('playlist_next', mode)
+
+ def playlist_prev(self, mode='weak'):
+ assert mode in ('weak', 'force')
+ self.command('playlist_prev', mode)
+
+ def loadfile(self, filename, mode='replace'):
+ assert mode in ('replace', 'append', 'append-play')
+ self.command('loadfile', filename, mode)
+
+ def loadlist(self, playlist, mode='replace'):
+ self.command('loadlist', playlist, mode)
+
+ def playlist_clear(self):
+ self.command('playlist_clear')
+
+ def playlist_remove(self, index='current'):
+ self.command('playlist_remove', index)
+
+ def playlist_move(self, index1, index2):
+ self.command('playlist_move', index1, index2)
+
+ def run(self, command, *args):
+ self.command('run', command, *args)
+
+ def quit(self, code=None):
+ self.command('quit', code)
+
+ def quit_watch_later(self, code=None):
+ self.command('quit_watch_later', code)
+
+ def sub_add(self, filename):
+ self.command('sub_add', filename)
+
+ def sub_remove(self, sub_id=None):
+ self.command('sub_remove', sub_id)
+
+ def sub_reload(self, sub_id=None):
+ self.command('sub_reload', sub_id)
+
+ def sub_step(self, skip):
+ self.command('sub_step', skip)
+
+ def sub_seek(self, skip):
+ self.command('sub_seek', skip)
+
+ def toggle_osd(self):
+ self.command('osd')
+
+ def show_text(self, string, duration='-', level=None):
+ self.command('show_text', string, duration, level)
+
+ def show_progress(self):
+ self.command('show_progress')
+
+ def discnav(self, command):
+ assert command in ('up', 'up', 'down', 'down', 'left', 'right', 'left', 'right', 'menu', 'select',
+ 'prev', 'mouse', 'mouse_move')
+ self.command('discnav', command)
+
+ def write_watch_later_config(self):
+ self.command('write_watch_later_config')
+
+ def overlay_add(self, overlay_id, x, y, file_or_fd, offset, fmt, w, h, stride):
+ self.command('overlay_add', overlay_id, x, y, file_or_fd, offset, fmt, w, h, stride)
+
+ def overlay_remove(self, overlay_id):
+ self.command('overlay_remove', overlay_id)
+
+ def script_message(self, *args):
+ self.command('script_message', *args)
+
+ def script_message_to(self, target, *args):
+ self.command('script_message_to', target, *args)
+
+ @property
+ def metadata(self):
+ ...
+ def chapter_metadata(self):
+ ...
+
+ def vf_metadata(self):
+ ...
+
+ # Convenience functions
+ def play(self, filename):
+ self.command('loadfile', filename)
+
+ # Complex properties
+
+ _VIDEO_PARAMS_LIST = (
+ ('pixelformat', str),
+ ('w', int),
+ ('h', int),
+ ('dw', int),
+ ('dh', int),
+ ('aspect', float),
+ ('par', float),
+ ('colormatrix', str),
+ ('colorlevels', str),
+ ('chroma-location', str),
+ ('rotate', int))
+
+ @property
+ def video_params(self):
+ return self._get_dict('video-params/', _VIDEO_PARAMS_LIST)
+
+ @property
+ def video_out_params(self):
+ return self._get_dict('video-out-params/', _VIDEO_PARAMS_LIST)
+
+ @property
+ def playlist(self):
+ return self._get_list('playlist/', (('filename', str),))
+ @property
+ def track_list(self):
+ return self._get_list('track-list/', (
+ ('id', int),
+ ('type', str),
+ ('src-id', int),
+ ('title', str),
+ ('lang', str),
+ ('albumart', ynbool),
+ ('default', ynbool),
+ ('external', ynbool),
+ ('external-filename', str),
+ ('codec', str),
+ ('selected', ynbool)))
+ @property
+ def chapter_list(self):
+ return self._get_dict('chapter-list/', (('title', str), ('time', float)))
+
+ def _get_dict(self, prefix, props):
+ return { name: proptype(_ensure_encoding(_mpv_get_property_string(self.handle, (prefix+name).encode('utf8')))) for name, proptype in props }
+
+ def _get_list(self, prefix, props):
+ count = int(_ensure_encoding(_mpv_get_property_string(self.handle, (prefix+'count').encode('utf8'))))
+ return [ self._get_dict(prefix+str(index)+'/', props) for index in range(count)]
+
+ # TODO: af, vf properties
+ # TODO: edition-list
+ # TODO property-mapped options
+
+
def bindproperty(MPV, name, proptype, access):
- def getter(self):
- return proptype(_ensure_encoding(_mpv_get_property_string(self.handle, name.encode('utf8'))))
- def setter(self, value):
- _mpv_set_property_string(self.handle, name.encode('utf8'), str(proptype(value)).encode('utf8'))
- def barf(*args):
- raise NotImplementedError('Access denied')
- setattr(MPV, name.replace('-', '_'), property(getter if 'r' in access else barf, setter if 'w' in access else barf))
+ def getter(self):
+ return proptype(_ensure_encoding(_mpv_get_property_string(self.handle, name.encode('utf8'))))
+ def setter(self, value):
+ _mpv_set_property_string(self.handle, name.encode('utf8'), str(proptype(value)).encode('utf8'))
+ def barf(*args):
+ raise NotImplementedError('Access denied')
+ setattr(MPV, name.replace('-', '_'), property(getter if 'r' in access else barf, setter if 'w' in access else barf))
for name, proptype, access in (
- ('osd-level', int, 'rw'),
- ('osd-scale', float, 'rw'),
- ('loop', str, 'rw'),
- ('loop-file', str, 'rw'),
- ('speed', float, 'rw'),
- ('filename', str, 'r'),
- ('file-size', int, 'r'),
- ('path', str, 'r'),
- ('media-title', str, 'r'),
- ('stream-pos', int, 'rw'),
- ('stream-end', int, 'r'),
- ('length', float, 'r'),
- ('avsync', float, 'r'),
- ('total-avsync-change', float, 'r'),
- ('drop-frame-count', int, 'r'),
- ('percent-pos', int, 'rw'),
- ('ratio-pos', float, 'rw'),
- ('time-pos', float, 'rw'),
- ('time-start', float, 'r'),
- ('time-remaining', float, 'r'),
- ('playtime-remaining', float, 'r'),
- ('chapter', int, 'rw'),
- ('edition', int, 'rw'),
- ('disc-titles', int, 'r'),
- ('disc-title', str, 'rw'),
- ('disc-menu-active', ynbool, 'r'),
- ('chapters', int, 'r'),
- ('editions', int, 'r'),
- ('angle', int, 'rw'),
- ('pause', ynbool, 'rw'),
- ('core-idle', ynbool, 'r'),
- ('cache', int, 'r'),
- ('cache-size' , int, 'rw'),
- ('pause-for-cache', ynbool, 'r'),
- ('eof-reached', ynbool, 'r'),
- ('pts-association-mode', str, 'rw'),
- ('hr-seek', ynbool, 'rw'),
- ('volume', float, 'rw'),
- ('mute', ynbool, 'rw'),
- ('audio-delay', float, 'rw'),
- ('audio-format', str, 'r'),
- ('audio-codec', str, 'r'),
- ('audio-bitrate', float, 'r'),
- ('audio-samplerate', int, 'r'),
- ('audio-channels', str, 'r'),
- ('aid', int, 'rw'),
- ('audio', int, 'rw'),
- ('balance', int, 'rw'),
- ('fullscreen', ynbool, 'rw'),
- ('deinterlace', str, 'rw'),
- ('colormatrix', str, 'rw'),
- ('colormatrix-input-range', str, 'rw'),
- ('colormatrix-output-range', str, 'rw'),
- ('colormatrix-primaries', str, 'rw'),
- ('ontop', ynbool, 'rw'),
- ('border', ynbool, 'rw'),
- ('framedrop', str, 'rw'),
- ('gamma', float, 'rw'),
- ('brightness', int, 'rw'),
- ('contrast', int, 'rw'),
- ('saturation', int, 'rw'),
- ('hue', int, 'rw'),
- ('hwdec', ynbool, 'rw'),
- ('panscan', float, 'rw'),
- ('video-format', str, 'r'),
- ('video-codec', str, 'r'),
- ('video-bitrate', float, 'r'),
- ('width', int, 'r'),
- ('height', int, 'r'),
- ('dwidth', int, 'r'),
- ('dheight', int, 'r'),
- ('fps', float, 'r'),
- ('estimated-vf-fps', float, 'r'),
- ('window-scale', float, 'rw'),
- ('video-aspect', str, 'rw'),
- ('osd-width', int, 'r'),
- ('osd-height', int, 'r'),
- ('osd-par', float, 'r'),
- ('vid', int, 'rw'),
- ('video', int, 'rw'),
- ('video-align-x', float, 'rw'),
- ('video-align-y', float, 'rw'),
- ('video-pan-x', int, 'rw'),
- ('video-pan-y', int, 'rw'),
- ('video-zoom', float, 'rw'),
- ('video-unscaled', ynbool, 'w'),
- ('program', int, 'w'),
- ('sid', int, 'rw'),
- ('secondary-sid', int, 'rw'),
- ('sub', int, 'rw'),
- ('sub-delay', float, 'rw'),
- ('sub-pos', int, 'rw'),
- ('sub-visibility', ynbool, 'rw'),
- ('sub-forced-only', ynbool, 'rw'),
- ('sub-scale', float, 'rw'),
- ('ass-use-margins', ynbool, 'rw'),
- ('ass-vsfilter-aspect-compat', ynbool, 'rw'),
- ('ass-style-override', str, 'rw'),
- ('stream-capture', str, 'rw'),
- ('tv-brightness', int, 'rw'),
- ('tv-contrast', int, 'rw'),
- ('tv-saturation', int, 'rw'),
- ('tv-hue', int, 'rw'),
- ('playlist-pos', int, 'rw'),
- ('playlist-count', int, 'r'),
- ('quvi-format', str, 'rw'),
- ('seekable', ynbool, 'r')):
- bindproperty(MPV, name, proptype, access)
+ ('osd-level', int, 'rw'),
+ ('osd-scale', float, 'rw'),
+ ('loop', str, 'rw'),
+ ('loop-file', str, 'rw'),
+ ('speed', float, 'rw'),
+ ('filename', str, 'r'),
+ ('file-size', int, 'r'),
+ ('path', str, 'r'),
+ ('media-title', str, 'r'),
+ ('stream-pos', int, 'rw'),
+ ('stream-end', int, 'r'),
+ ('length', float, 'r'),
+ ('avsync', float, 'r'),
+ ('total-avsync-change', float, 'r'),
+ ('drop-frame-count', int, 'r'),
+ ('percent-pos', int, 'rw'),
+ ('ratio-pos', float, 'rw'),
+ ('time-pos', float, 'rw'),
+ ('time-start', float, 'r'),
+ ('time-remaining', float, 'r'),
+ ('playtime-remaining', float, 'r'),
+ ('chapter', int, 'rw'),
+ ('edition', int, 'rw'),
+ ('disc-titles', int, 'r'),
+ ('disc-title', str, 'rw'),
+ ('disc-menu-active', ynbool, 'r'),
+ ('chapters', int, 'r'),
+ ('editions', int, 'r'),
+ ('angle', int, 'rw'),
+ ('pause', ynbool, 'rw'),
+ ('core-idle', ynbool, 'r'),
+ ('cache', int, 'r'),
+ ('cache-size' , int, 'rw'),
+ ('pause-for-cache', ynbool, 'r'),
+ ('eof-reached', ynbool, 'r'),
+ ('pts-association-mode', str, 'rw'),
+ ('hr-seek', ynbool, 'rw'),
+ ('volume', float, 'rw'),
+ ('mute', ynbool, 'rw'),
+ ('audio-delay', float, 'rw'),
+ ('audio-format', str, 'r'),
+ ('audio-codec', str, 'r'),
+ ('audio-bitrate', float, 'r'),
+ ('audio-samplerate', int, 'r'),
+ ('audio-channels', str, 'r'),
+ ('aid', int, 'rw'),
+ ('audio', int, 'rw'),
+ ('balance', int, 'rw'),
+ ('fullscreen', ynbool, 'rw'),
+ ('deinterlace', str, 'rw'),
+ ('colormatrix', str, 'rw'),
+ ('colormatrix-input-range', str, 'rw'),
+ ('colormatrix-output-range', str, 'rw'),
+ ('colormatrix-primaries', str, 'rw'),
+ ('ontop', ynbool, 'rw'),
+ ('border', ynbool, 'rw'),
+ ('framedrop', str, 'rw'),
+ ('gamma', float, 'rw'),
+ ('brightness', int, 'rw'),
+ ('contrast', int, 'rw'),
+ ('saturation', int, 'rw'),
+ ('hue', int, 'rw'),
+ ('hwdec', ynbool, 'rw'),
+ ('panscan', float, 'rw'),
+ ('video-format', str, 'r'),
+ ('video-codec', str, 'r'),
+ ('video-bitrate', float, 'r'),
+ ('width', int, 'r'),
+ ('height', int, 'r'),
+ ('dwidth', int, 'r'),
+ ('dheight', int, 'r'),
+ ('fps', float, 'r'),
+ ('estimated-vf-fps', float, 'r'),
+ ('window-scale', float, 'rw'),
+ ('video-aspect', str, 'rw'),
+ ('osd-width', int, 'r'),
+ ('osd-height', int, 'r'),
+ ('osd-par', float, 'r'),
+ ('vid', int, 'rw'),
+ ('video', int, 'rw'),
+ ('video-align-x', float, 'rw'),
+ ('video-align-y', float, 'rw'),
+ ('video-pan-x', int, 'rw'),
+ ('video-pan-y', int, 'rw'),
+ ('video-zoom', float, 'rw'),
+ ('video-unscaled', ynbool, 'w'),
+ ('program', int, 'w'),
+ ('sid', int, 'rw'),
+ ('secondary-sid', int, 'rw'),
+ ('sub', int, 'rw'),
+ ('sub-delay', float, 'rw'),
+ ('sub-pos', int, 'rw'),
+ ('sub-visibility', ynbool, 'rw'),
+ ('sub-forced-only', ynbool, 'rw'),
+ ('sub-scale', float, 'rw'),
+ ('ass-use-margins', ynbool, 'rw'),
+ ('ass-vsfilter-aspect-compat', ynbool, 'rw'),
+ ('ass-style-override', str, 'rw'),
+ ('stream-capture', str, 'rw'),
+ ('tv-brightness', int, 'rw'),
+ ('tv-contrast', int, 'rw'),
+ ('tv-saturation', int, 'rw'),
+ ('tv-hue', int, 'rw'),
+ ('playlist-pos', int, 'rw'),
+ ('playlist-count', int, 'r'),
+ ('quvi-format', str, 'rw'),
+ ('seekable', ynbool, 'r')):
+ bindproperty(MPV, name, proptype, access)