Added support for yet-unknown error codes

This commit is contained in:
jaseg 2014-08-13 23:51:07 +02:00
parent 2e6516aa32
commit baee4f4e22

871
mpv.py
View file

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