WIP
This commit is contained in:
parent
97d929e27f
commit
4d6c17d342
2 changed files with 228 additions and 213 deletions
62
mpv-test.py
62
mpv-test.py
|
|
@ -17,13 +17,13 @@ MPV_ERRORS = [ l(ec) for ec, l in mpv.ErrorCode.EXCEPTION_DICT.items() if l ]
|
||||||
|
|
||||||
class TestProperties(unittest.TestCase):
|
class TestProperties(unittest.TestCase):
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def swallow_mpv_errors(self):
|
def swallow_mpv_errors(self, exception_exceptions=[]):
|
||||||
try:
|
try:
|
||||||
yield
|
yield
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
for ex in MPV_ERRORS:
|
if any(e.args[:2] == ex.args for ex in MPV_ERRORS):
|
||||||
if e.args[:2] == ex.args:
|
if e.args[1] not in exception_exceptions:
|
||||||
break
|
raise
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
@ -34,7 +34,8 @@ class TestProperties(unittest.TestCase):
|
||||||
for name, (ptype, access) in mpv.ALL_PROPERTIES.items():
|
for name, (ptype, access) in mpv.ALL_PROPERTIES.items():
|
||||||
self.assertTrue('r' in access or 'w' in access)
|
self.assertTrue('r' in access or 'w' in access)
|
||||||
self.assertRegex(name, '^[-0-9a-z]+$')
|
self.assertRegex(name, '^[-0-9a-z]+$')
|
||||||
self.assertIn(ptype, (int, float, str, mpv.ynbool, mpv.commalist))
|
# Types and MpvFormat values
|
||||||
|
self.assertIn(ptype, [bool, int, float, str, bytes, mpv.commalist] + list(range(10)))
|
||||||
|
|
||||||
def test_completeness(self):
|
def test_completeness(self):
|
||||||
ledir = dir(self.m)
|
ledir = dir(self.m)
|
||||||
|
|
@ -54,20 +55,30 @@ class TestProperties(unittest.TestCase):
|
||||||
self.assertTrue(prop in ledir, 'Property {} not found'.format(prop))
|
self.assertTrue(prop in ledir, 'Property {} not found'.format(prop))
|
||||||
|
|
||||||
def test_read(self):
|
def test_read(self):
|
||||||
for name, (ptype, access) in mpv.ALL_PROPERTIES.items():
|
self.m.loop = 'inf'
|
||||||
|
self.m.play(TESTVID)
|
||||||
|
while self.m.core_idle:
|
||||||
|
time.sleep(0.05)
|
||||||
|
for name, (ptype, access) in sorted(mpv.ALL_PROPERTIES.items()):
|
||||||
if 'r' in access:
|
if 'r' in access:
|
||||||
name = name.replace('-', '_')
|
name = name.replace('-', '_')
|
||||||
with self.subTest(property_name=name):
|
with self.subTest(property_name=name):
|
||||||
with self.swallow_mpv_errors():
|
with self.swallow_mpv_errors([
|
||||||
|
mpv.ErrorCode.PROPERTY_UNAVAILABLE,
|
||||||
|
mpv.ErrorCode.PROPERTY_ERROR]):
|
||||||
rv = getattr(self.m, name)
|
rv = getattr(self.m, name)
|
||||||
if rv is not None: # Technically, any property can return None (even if of type e.g. int)
|
if rv is not None and callable(ptype):
|
||||||
self.assertEqual(type(rv), type(ptype()))
|
# Technically, any property can return None (even if of type e.g. int)
|
||||||
|
self.assertEqual(type(rv), type(ptype()))
|
||||||
|
|
||||||
def test_write(self):
|
def test_write(self):
|
||||||
for name, (ptype, access) in mpv.ALL_PROPERTIES.items():
|
for name, (ptype, access) in sorted(mpv.ALL_PROPERTIES.items()):
|
||||||
if 'w' in access:
|
if 'w' in access:
|
||||||
name = name.replace('-', '_')
|
name = name.replace('-', '_')
|
||||||
with self.swallow_mpv_errors():
|
with self.swallow_mpv_errors([
|
||||||
|
mpv.ErrorCode.PROPERTY_UNAVAILABLE,
|
||||||
|
mpv.ErrorCode.PROPERTY_ERROR,
|
||||||
|
mpv.ErrorCode.PROPERTY_FORMAT]): # This is due to a bug with option-mapped properties in mpv 0.18.1
|
||||||
if ptype == int:
|
if ptype == int:
|
||||||
setattr(self.m, name, 0)
|
setattr(self.m, name, 0)
|
||||||
setattr(self.m, name, 1)
|
setattr(self.m, name, 1)
|
||||||
|
|
@ -82,28 +93,13 @@ class TestProperties(unittest.TestCase):
|
||||||
setattr(self.m, name, 'foo')
|
setattr(self.m, name, 'foo')
|
||||||
setattr(self.m, name, '')
|
setattr(self.m, name, '')
|
||||||
setattr(self.m, name, 'bazbazbaz'*1000)
|
setattr(self.m, name, 'bazbazbaz'*1000)
|
||||||
elif ptype == mpv.ynbool:
|
elif ptype == bytes:
|
||||||
if 'r' in access:
|
setattr(self.m, name, b'foo')
|
||||||
setattr(self.m, name, 'yes')
|
setattr(self.m, name, b'')
|
||||||
self.assertTrue(getattr(self.m, name))
|
setattr(self.m, name, b'bazbazbaz'*1000)
|
||||||
self.assertEqual(getattr(self.m, name), True)
|
elif ptype == bool:
|
||||||
setattr(self.m, name, b'yes')
|
setattr(self.m, name, True)
|
||||||
self.assertTrue(getattr(self.m, name))
|
setattr(self.m, name, False)
|
||||||
setattr(self.m, name, True)
|
|
||||||
self.assertTrue(getattr(self.m, name))
|
|
||||||
|
|
||||||
setattr(self.m, name, 'no')
|
|
||||||
self.assertFalse(getattr(self.m, name))
|
|
||||||
self.assertEqual(getattr(self.m, name), False)
|
|
||||||
setattr(self.m, name, b'no')
|
|
||||||
self.assertFalse(getattr(self.m, name))
|
|
||||||
setattr(self.m, name, False)
|
|
||||||
self.assertFalse(getattr(self.m, name))
|
|
||||||
else:
|
|
||||||
setattr(self.m, name, 'yes')
|
|
||||||
setattr(self.m, name, b'yes')
|
|
||||||
setattr(self.m, name, True)
|
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
del self.m
|
del self.m
|
||||||
|
|
||||||
|
|
|
||||||
379
mpv.py
379
mpv.py
|
|
@ -50,9 +50,11 @@ class ErrorCode(object):
|
||||||
-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),
|
# Currently (mpv 0.18.1) there is a bug causing a PROPERTY_FORMAT error to be returned instead of
|
||||||
|
# INVALID_PARAMETER when setting a property-mapped option to an invalid value.
|
||||||
|
-9: lambda *a: TypeError('Tried to get/set mpv property using wrong format, or passed invalid value', *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: RuntimeError('Generic error getting or setting mpv property', *a),
|
||||||
-12: lambda *a: SystemError('Error running mpv command', *a)
|
-12: lambda *a: SystemError('Error running mpv command', *a)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,8 +63,7 @@ class ErrorCode(object):
|
||||||
return ValueError(_mpv_error_string(ec).decode('utf-8'), ec, *args)
|
return ValueError(_mpv_error_string(ec).decode('utf-8'), ec, *args)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def raise_for_ec(kls, func, *args):
|
def raise_for_ec(kls, ec, func, *args):
|
||||||
ec = func(*args)
|
|
||||||
ec = 0 if ec > 0 else ec
|
ec = 0 if ec > 0 else ec
|
||||||
ex = kls.EXCEPTION_DICT.get(ec , kls.default_error_handler)
|
ex = kls.EXCEPTION_DICT.get(ec , kls.default_error_handler)
|
||||||
if ex:
|
if ex:
|
||||||
|
|
@ -79,9 +80,11 @@ class MpvFormat(c_int):
|
||||||
NODE = 6
|
NODE = 6
|
||||||
NODE_ARRAY = 7
|
NODE_ARRAY = 7
|
||||||
NODE_MAP = 8
|
NODE_MAP = 8
|
||||||
|
BYTE_ARRAY = 9
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return ['NONE', 'STRING', 'OSD_STRING', 'FLAG', 'INT64', 'DOUBLE', 'NODE', 'NODE_ARRAY', 'NODE_MAP'][self.value]
|
return ['NONE', 'STRING', 'OSD_STRING', 'FLAG', 'INT64', 'DOUBLE', 'NODE', 'NODE_ARRAY', 'NODE_MAP',
|
||||||
|
'BYTE_ARRAY'][self.value]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -116,6 +119,43 @@ class MpvEventID(c_int):
|
||||||
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 MpvNodeList(Structure):
|
||||||
|
@property
|
||||||
|
def array_value(self):
|
||||||
|
return [ self.values[i].node_value for i in range(self.num) ]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dict_value(self):
|
||||||
|
return { self.keys[i].decode('utf-8'): self.values[i].node_value for i in range(self.num) }
|
||||||
|
|
||||||
|
class MpvNode(Structure):
|
||||||
|
_fields_ = [('val', c_longlong),
|
||||||
|
('format', MpvFormat)]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def node_value(self):
|
||||||
|
return MpvNode.node_cast_value(byref(c_void_p(self.val)), self.format.value)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def node_cast_value(v, fmt):
|
||||||
|
return {
|
||||||
|
MpvFormat.NONE: lambda v: None,
|
||||||
|
MpvFormat.STRING: lambda v: cast(v, POINTER(c_char_p)).contents.value, # We can't decode here as this might contain file names
|
||||||
|
MpvFormat.OSD_STRING: lambda v: cast(v, POINTER(c_char_p)).contents.value.decode('utf-8'),
|
||||||
|
MpvFormat.FLAG: lambda v: bool(cast(v, POINTER(c_int)).contents.value),
|
||||||
|
MpvFormat.INT64: lambda v: cast(v, POINTER(c_longlong)).contents.value,
|
||||||
|
MpvFormat.DOUBLE: lambda v: cast(v, POINTER(c_double)).contents.value,
|
||||||
|
MpvFormat.NODE: lambda v: cast(v, POINTER(MpvNode)).contents.node_value,
|
||||||
|
MpvFormat.NODE_ARRAY: lambda v: cast(v, POINTER(POINTER(MpvNodeList))).contents.contents.array_value,
|
||||||
|
MpvFormat.NODE_MAP: lambda v: cast(v, POINTER(POINTER(MpvNodeList))).contents.contents.dict_value,
|
||||||
|
MpvFormat.BYTE_ARRAY: lambda v: cast(v, POINTER(c_char_p)).contents.value,
|
||||||
|
}[fmt](v)
|
||||||
|
|
||||||
|
MpvNodeList._fields_ = [('num', c_int),
|
||||||
|
('values', POINTER(MpvNode)),
|
||||||
|
('keys', POINTER(c_char_p))]
|
||||||
|
|
||||||
class MpvSubApi(c_int):
|
class MpvSubApi(c_int):
|
||||||
MPV_SUB_API_OPENGL_CB = 1
|
MPV_SUB_API_OPENGL_CB = 1
|
||||||
|
|
||||||
|
|
@ -192,20 +232,31 @@ WakeupCallback = CFUNCTYPE(None, c_void_p)
|
||||||
OpenGlCbUpdateFn = CFUNCTYPE(None, c_void_p)
|
OpenGlCbUpdateFn = CFUNCTYPE(None, c_void_p)
|
||||||
OpenGlCbGetProcAddrFn = CFUNCTYPE(None, c_void_p, c_char_p)
|
OpenGlCbGetProcAddrFn = CFUNCTYPE(None, c_void_p, c_char_p)
|
||||||
|
|
||||||
def _handle_func(name, args=[], res=None, context=MpvHandle):
|
def _handle_func(name, args, restype, errcheck, ctx=MpvHandle):
|
||||||
func = getattr(backend, name)
|
func = getattr(backend, name)
|
||||||
if res is not None:
|
func.argtypes = [ctx] + args if ctx else args
|
||||||
func.restype = res
|
if restype is not None:
|
||||||
func.argtypes = [context] + args
|
func.restype = restype
|
||||||
def wrapper(*args):
|
if errcheck is not None:
|
||||||
if res is not None:
|
func.errcheck = errcheck
|
||||||
return func(*args)
|
globals()['_'+name] = func
|
||||||
else:
|
|
||||||
ErrorCode.raise_for_ec(func, *args)
|
|
||||||
globals()['_'+name] = wrapper
|
|
||||||
|
|
||||||
def _handle_gl_func(name, args=[], res=None):
|
def bytes_free_errcheck(res, func, *args):
|
||||||
_handle_func(name, args, res, MpvOpenGLCbContext)
|
notnull_errcheck(res, func, *args)
|
||||||
|
rv = cast(res, c_void_p).value
|
||||||
|
_mpv_free(res)
|
||||||
|
return rv
|
||||||
|
|
||||||
|
def notnull_errcheck(res, func, *args):
|
||||||
|
if res is None:
|
||||||
|
raise RuntimeError('Underspecified error in MPV when calling {} with args {!r}: NULL pointer returned.'\
|
||||||
|
'Please consult your local debugger.'.format(func.__name__, args))
|
||||||
|
return res
|
||||||
|
|
||||||
|
ec_errcheck = ErrorCode.raise_for_ec
|
||||||
|
|
||||||
|
def _handle_gl_func(name, args=[], restype=None):
|
||||||
|
_handle_func(name, args, restype, errcheck=None, ctx=MpvOpenGLCbContext)
|
||||||
|
|
||||||
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():
|
||||||
|
|
@ -215,81 +266,58 @@ def _mpv_client_api_version():
|
||||||
backend.mpv_free.argtypes = [c_void_p]
|
backend.mpv_free.argtypes = [c_void_p]
|
||||||
_mpv_free = backend.mpv_free
|
_mpv_free = backend.mpv_free
|
||||||
|
|
||||||
|
backend.mpv_free_node_contents.argtypes = [c_void_p]
|
||||||
|
_mpv_free_node_contents = backend.mpv_free_node_contents
|
||||||
|
|
||||||
backend.mpv_create.restype = MpvHandle
|
backend.mpv_create.restype = MpvHandle
|
||||||
_mpv_create = backend.mpv_create
|
_mpv_create = backend.mpv_create
|
||||||
|
|
||||||
_handle_func('mpv_create_client', [c_char_p], MpvHandle)
|
_handle_func('mpv_create_client', [c_char_p], MpvHandle, notnull_errcheck)
|
||||||
_handle_func('mpv_client_name', [], c_char_p)
|
_handle_func('mpv_client_name', [], c_char_p, errcheck=None)
|
||||||
_handle_func('mpv_initialize')
|
_handle_func('mpv_initialize', [], c_int, ec_errcheck)
|
||||||
_handle_func('mpv_detach_destroy', [], c_int)
|
_handle_func('mpv_detach_destroy', [], None, errcheck=None)
|
||||||
_handle_func('mpv_terminate_destroy', [], c_int)
|
_handle_func('mpv_terminate_destroy', [], None, errcheck=None)
|
||||||
_handle_func('mpv_load_config_file', [c_char_p])
|
_handle_func('mpv_load_config_file', [c_char_p], c_int, ec_errcheck)
|
||||||
_handle_func('mpv_suspend', [], c_int)
|
_handle_func('mpv_suspend', [], None, errcheck=None)
|
||||||
_handle_func('mpv_resume', [], c_int)
|
_handle_func('mpv_resume', [], None, errcheck=None)
|
||||||
_handle_func('mpv_get_time_us', [], c_ulonglong)
|
_handle_func('mpv_get_time_us', [], c_ulonglong, errcheck=None)
|
||||||
|
|
||||||
_handle_func('mpv_set_option', [c_char_p, MpvFormat, c_void_p])
|
_handle_func('mpv_set_option', [c_char_p, MpvFormat, c_void_p], c_int, ec_errcheck)
|
||||||
_handle_func('mpv_set_option_string', [c_char_p, c_char_p])
|
_handle_func('mpv_set_option_string', [c_char_p, c_char_p], c_int, ec_errcheck)
|
||||||
|
|
||||||
_handle_func('mpv_command', [POINTER(c_char_p)])
|
_handle_func('mpv_command', [POINTER(c_char_p)], c_int, ec_errcheck)
|
||||||
_handle_func('mpv_command_string', [c_char_p, 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)])
|
_handle_func('mpv_command_async', [c_ulonglong, POINTER(c_char_p)], c_int, ec_errcheck)
|
||||||
|
|
||||||
_handle_func('mpv_set_property', [c_char_p, MpvFormat, c_void_p])
|
_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])
|
_handle_func('mpv_set_property_string', [c_char_p, c_char_p], c_int, ec_errcheck)
|
||||||
_handle_func('mpv_set_property_async', [c_ulonglong, c_char_p, MpvFormat, c_void_p])
|
_handle_func('mpv_set_property_async', [c_ulonglong, c_char_p, MpvFormat,c_void_p],c_int, ec_errcheck)
|
||||||
_handle_func('mpv_get_property', [c_char_p, MpvFormat, c_void_p])
|
_handle_func('mpv_get_property', [c_char_p, MpvFormat, c_void_p], c_int, ec_errcheck)
|
||||||
_handle_func('mpv_get_property_string', [c_char_p], c_char_p)
|
_handle_func('mpv_get_property_string', [c_char_p], c_void_p, bytes_free_errcheck)
|
||||||
_handle_func('mpv_get_property_osd_string', [c_char_p], c_char_p)
|
_handle_func('mpv_get_property_osd_string', [c_char_p], c_void_p, bytes_free_errcheck)
|
||||||
_handle_func('mpv_get_property_async', [c_ulonglong, c_char_p, MpvFormat])
|
_handle_func('mpv_get_property_async', [c_ulonglong, c_char_p, MpvFormat], c_int, ec_errcheck)
|
||||||
_handle_func('mpv_observe_property', [c_ulonglong, c_char_p, MpvFormat])
|
_handle_func('mpv_observe_property', [c_ulonglong, c_char_p, MpvFormat], c_int, ec_errcheck)
|
||||||
_handle_func('mpv_unobserve_property', [c_ulonglong])
|
_handle_func('mpv_unobserve_property', [c_ulonglong], c_int, ec_errcheck)
|
||||||
|
|
||||||
backend.mpv_event_name.restype = c_char_p
|
_handle_func('mpv_event_name', [c_int], c_char_p, errcheck=None, ctx=None)
|
||||||
backend.mpv_event_name.argtypes = [c_int]
|
_handle_func('mpv_error_string', [c_int], c_char_p, errcheck=None, ctx=None)
|
||||||
_mpv_event_name = backend.mpv_event_name
|
|
||||||
|
|
||||||
backend.mpv_error_string.restype = c_char_p
|
_handle_func('mpv_request_event', [MpvEventID, c_int], c_int, ec_errcheck)
|
||||||
backend.mpv_error_string.argtypes = [c_int]
|
_handle_func('mpv_request_log_messages', [c_char_p], c_int, ec_errcheck)
|
||||||
_mpv_error_string = backend.mpv_error_string
|
_handle_func('mpv_wait_event', [c_double], POINTER(MpvEvent), errcheck=None)
|
||||||
|
_handle_func('mpv_wakeup', [], None, errcheck=None)
|
||||||
|
_handle_func('mpv_set_wakeup_callback', [WakeupCallback, c_void_p], None, errcheck=None)
|
||||||
|
_handle_func('mpv_get_wakeup_pipe', [], c_int, errcheck=None)
|
||||||
|
|
||||||
_handle_func('mpv_request_event', [MpvEventID, c_int])
|
_handle_func('mpv_get_sub_api', [MpvSubApi], c_void_p, notnull_errcheck)
|
||||||
_handle_func('mpv_request_log_messages', [c_char_p])
|
|
||||||
_handle_func('mpv_wait_event', [c_double], POINTER(MpvEvent))
|
|
||||||
_handle_func('mpv_wakeup', [], c_int)
|
|
||||||
_handle_func('mpv_set_wakeup_callback', [WakeupCallback, c_void_p], c_int)
|
|
||||||
_handle_func('mpv_get_wakeup_pipe', [], c_int)
|
|
||||||
|
|
||||||
_handle_func('mpv_get_sub_api', [MpvSubApi], c_void_p)
|
_handle_gl_func('mpv_opengl_cb_set_update_callback', [OpenGlCbUpdateFn, c_void_p])
|
||||||
|
_handle_gl_func('mpv_opengl_cb_init_gl', [c_char_p, OpenGlCbGetProcAddrFn, c_void_p], c_int)
|
||||||
|
_handle_gl_func('mpv_opengl_cb_draw', [c_int, c_int, c_int], c_int)
|
||||||
|
_handle_gl_func('mpv_opengl_cb_render', [c_int, c_int], c_int)
|
||||||
|
_handle_gl_func('mpv_opengl_cb_report_flip', [c_ulonglong], c_int)
|
||||||
|
_handle_gl_func('mpv_opengl_cb_uninit_gl', [], c_int)
|
||||||
|
|
||||||
_handle_gl_func('mpv_opengl_cb_set_update_callback', [OpenGlCbUpdateFn, c_void_p])
|
|
||||||
_handle_gl_func('mpv_opengl_cb_init_gl', [c_char_p, OpenGlCbGetProcAddrFn, c_void_p], c_int)
|
|
||||||
_handle_gl_func('mpv_opengl_cb_draw', [c_int, c_int, c_int], c_int);
|
|
||||||
_handle_gl_func('mpv_opengl_cb_render', [c_int, c_int], c_int);
|
|
||||||
_handle_gl_func('mpv_opengl_cb_report_flip', [c_ulonglong], c_int);
|
|
||||||
_handle_gl_func('mpv_opengl_cb_uninit_gl', [], c_int);
|
|
||||||
|
|
||||||
|
|
||||||
def commalist(propval=''):
|
|
||||||
return str(propval).split(',')
|
|
||||||
|
|
||||||
class ynbool(object):
|
|
||||||
def __init__(self, val=False):
|
|
||||||
self.val = bool(val and val not in (b'no', 'no'))
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
return bool(self.val)
|
|
||||||
# Python 2 only:
|
|
||||||
__nonzero__ = __bool__
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return 'yes' if self.val else 'no'
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return str(self.val)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return str(self) == other or bool(self) == other
|
|
||||||
|
|
||||||
def _ensure_encoding(possibly_bytes):
|
def _ensure_encoding(possibly_bytes):
|
||||||
return possibly_bytes.decode('utf-8') if type(possibly_bytes) is bytes else possibly_bytes
|
return possibly_bytes.decode('utf-8') if type(possibly_bytes) is bytes else possibly_bytes
|
||||||
|
|
@ -511,54 +539,6 @@ class MPV(object):
|
||||||
|
|
||||||
# Complex properties
|
# 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/', self._VIDEO_PARAMS_LIST)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def video_out_params(self):
|
|
||||||
return self._get_dict('video-out-params/', self._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)))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def vo_performance(self):
|
|
||||||
return self._get_dict('vo-performance/', [(metric+'-'+value, str)
|
|
||||||
for metric in ('upload', 'render', 'present')
|
|
||||||
for value in ('last', 'avg', 'peak')])
|
|
||||||
|
|
||||||
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('utf-8')))) for name, proptype in props }
|
return { name: proptype(_ensure_encoding(_mpv_get_property_string(self.handle, (prefix+name).encode('utf-8')))) for name, proptype in props }
|
||||||
|
|
||||||
|
|
@ -640,16 +620,21 @@ class MPV(object):
|
||||||
|
|
||||||
# TODO: audio-device-list, decoder-list, encoder-list
|
# TODO: audio-device-list, decoder-list, encoder-list
|
||||||
|
|
||||||
|
def commalist(propval=''):
|
||||||
|
return str(propval).split(',')
|
||||||
|
|
||||||
|
node = MpvFormat.NODE
|
||||||
|
|
||||||
ALL_PROPERTIES = {
|
ALL_PROPERTIES = {
|
||||||
'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': (bytes, 'r'),
|
||||||
'file-size': (int, 'r'),
|
'file-size': (int, 'r'),
|
||||||
'path': (str, 'r'),
|
'path': (bytes, 'r'),
|
||||||
'media-title': (str, 'r'),
|
'media-title': (bytes, 'r'),
|
||||||
'stream-pos': (int, 'rw'),
|
'stream-pos': (int, 'rw'),
|
||||||
'stream-end': (int, 'r'),
|
'stream-end': (int, 'r'),
|
||||||
'length': (float, 'r'), # deprecated for ages now
|
'length': (float, 'r'), # deprecated for ages now
|
||||||
|
|
@ -658,7 +643,7 @@ ALL_PROPERTIES = {
|
||||||
'total-avsync-change': (float, 'r'),
|
'total-avsync-change': (float, 'r'),
|
||||||
'drop-frame-count': (int, 'r'),
|
'drop-frame-count': (int, 'r'),
|
||||||
'percent-pos': (float, 'rw'),
|
'percent-pos': (float, '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'),
|
||||||
|
|
@ -667,29 +652,29 @@ ALL_PROPERTIES = {
|
||||||
'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': (bool, 'r'),
|
||||||
'chapters': (int, 'r'),
|
'chapters': (int, 'r'),
|
||||||
'editions': (int, 'r'),
|
'editions': (int, 'r'),
|
||||||
'angle': (int, 'rw'),
|
'angle': (int, 'rw'),
|
||||||
'pause': (ynbool, 'rw'),
|
'pause': (bool, 'rw'),
|
||||||
'core-idle': (ynbool, 'r'),
|
'core-idle': (bool, 'r'),
|
||||||
'cache': (int, 'r'),
|
'cache': (int, 'r'),
|
||||||
'cache-size': (int, 'rw'),
|
'cache-size': (int, 'rw'),
|
||||||
'cache-free': (int, 'r'),
|
'cache-free': (int, 'r'),
|
||||||
'cache-used': (int, 'r'),
|
'cache-used': (int, 'r'),
|
||||||
'cache-speed': (int, 'r'),
|
'cache-speed': (int, 'r'),
|
||||||
'cache-idle': (ynbool, 'r'),
|
'cache-idle': (bool, 'r'),
|
||||||
'cache-buffering-state': (int, 'r'),
|
'cache-buffering-state': (int, 'r'),
|
||||||
'paused-for-cache': (ynbool, 'r'),
|
'paused-for-cache': (bool, 'r'),
|
||||||
'pause-for-cache': (ynbool, 'r'),
|
# 'pause-for-cache': (bool, 'r'),
|
||||||
'eof-reached': (ynbool, 'r'),
|
'eof-reached': (bool, 'r'),
|
||||||
'pts-association-mode': (str, 'rw'),
|
# 'pts-association-mode': (str, 'rw'),
|
||||||
'hr-seek': (ynbool, 'rw'),
|
'hr-seek': (str, 'rw'),
|
||||||
'volume': (float, 'rw'),
|
'volume': (float, 'rw'),
|
||||||
'volume-max': (float, 'rw'),
|
'volume-max': (int, 'rw'),
|
||||||
'ao-volume': (float, 'rw'),
|
'ao-volume': (float, 'rw'),
|
||||||
'mute': (ynbool, 'rw'),
|
'mute': (bool, 'rw'),
|
||||||
'ao-mute': (ynbool, 'rw'),
|
'ao-mute': (bool, 'rw'),
|
||||||
'audio-speed-correction': (float, 'r'),
|
'audio-speed-correction': (float, 'r'),
|
||||||
'audio-delay': (float, 'rw'),
|
'audio-delay': (float, 'rw'),
|
||||||
'audio-format': (str, 'r'),
|
'audio-format': (str, 'r'),
|
||||||
|
|
@ -701,22 +686,22 @@ ALL_PROPERTIES = {
|
||||||
'audio-channels': (str, 'r'),
|
'audio-channels': (str, 'r'),
|
||||||
'aid': (str, 'rw'),
|
'aid': (str, 'rw'),
|
||||||
'audio': (str, 'rw'), # alias for aid
|
'audio': (str, 'rw'), # alias for aid
|
||||||
'balance': (float, 'rw'),
|
'balance': (int, 'rw'),
|
||||||
'fullscreen': (ynbool, 'rw'),
|
'fullscreen': (bool, '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': (bool, 'rw'),
|
||||||
'border': (ynbool, 'rw'),
|
'border': (bool, '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': (str, 'rw'),
|
||||||
'panscan': (float, 'rw'),
|
'panscan': (float, 'rw'),
|
||||||
'video-format': (str, 'r'),
|
'video-format': (str, 'r'),
|
||||||
'video-codec': (str, 'r'),
|
'video-codec': (str, 'r'),
|
||||||
|
|
@ -740,7 +725,7 @@ ALL_PROPERTIES = {
|
||||||
'video-pan-x': (float, 'rw'),
|
'video-pan-x': (float, 'rw'),
|
||||||
'video-pan-y': (float, 'rw'),
|
'video-pan-y': (float, 'rw'),
|
||||||
'video-zoom': (float, 'rw'),
|
'video-zoom': (float, 'rw'),
|
||||||
'video-unscaled': (ynbool, 'w'),
|
'video-unscaled': (bool, 'w'),
|
||||||
'video-speed-correction': (float, 'r'),
|
'video-speed-correction': (float, 'r'),
|
||||||
'program': (int, 'w'),
|
'program': (int, 'w'),
|
||||||
'sid': (str, 'rw'),
|
'sid': (str, 'rw'),
|
||||||
|
|
@ -748,14 +733,14 @@ ALL_PROPERTIES = {
|
||||||
'secondary-sid': (str, 'rw'),
|
'secondary-sid': (str, 'rw'),
|
||||||
'sub-delay': (float, 'rw'),
|
'sub-delay': (float, 'rw'),
|
||||||
'sub-pos': (int, 'rw'),
|
'sub-pos': (int, 'rw'),
|
||||||
'sub-visibility': (ynbool, 'rw'),
|
'sub-visibility': (bool, 'rw'),
|
||||||
'sub-forced-only': (ynbool, 'rw'),
|
'sub-forced-only': (bool, 'rw'),
|
||||||
'sub-scale': (float, 'rw'),
|
'sub-scale': (float, 'rw'),
|
||||||
'sub-bitrate': (float, 'r'),
|
'sub-bitrate': (float, 'r'),
|
||||||
'packet-sub-bitrate': (float, 'r'),
|
'packet-sub-bitrate': (float, 'r'),
|
||||||
'ass-use-margins': (ynbool, 'rw'),
|
# 'ass-use-margins': (bool, 'rw'),
|
||||||
'ass-vsfilter-aspect-compat': (ynbool, 'rw'),
|
'ass-vsfilter-aspect-compat': (bool, 'rw'),
|
||||||
'ass-style-override': (str, 'rw'),
|
'ass-style-override': (bool, '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'),
|
||||||
|
|
@ -764,11 +749,11 @@ ALL_PROPERTIES = {
|
||||||
'playlist-pos': (int, 'rw'),
|
'playlist-pos': (int, 'rw'),
|
||||||
'playlist-pos-1': (int, 'rw'), # ugh.
|
'playlist-pos-1': (int, 'rw'), # ugh.
|
||||||
'playlist-count': (int, 'r'),
|
'playlist-count': (int, 'r'),
|
||||||
'quvi-format': (str, 'rw'),
|
# 'quvi-format': (str, 'rw'),
|
||||||
'seekable': (ynbool, 'r'),
|
'seekable': (bool, 'r'),
|
||||||
'seeking': (ynbool, 'r'),
|
'seeking': (bool, 'r'),
|
||||||
'partially-seekable': (ynbool, 'r'),
|
'partially-seekable': (bool, 'r'),
|
||||||
'playback-abort': (ynbool, 'r'),
|
'playback-abort': (bool, 'r'),
|
||||||
'cursor-autohide': (str, 'rw'),
|
'cursor-autohide': (str, 'rw'),
|
||||||
'audio-device': (str, 'rw'),
|
'audio-device': (str, 'rw'),
|
||||||
'current-vo': (str, 'r'),
|
'current-vo': (str, 'r'),
|
||||||
|
|
@ -778,8 +763,8 @@ ALL_PROPERTIES = {
|
||||||
'mpv-version': (str, 'r'),
|
'mpv-version': (str, 'r'),
|
||||||
'mpv-configuration': (str, 'r'),
|
'mpv-configuration': (str, 'r'),
|
||||||
'ffmpeg-version': (str, 'r'),
|
'ffmpeg-version': (str, 'r'),
|
||||||
'display-sync-active': (ynbool, 'r'),
|
'display-sync-active': (bool, 'r'),
|
||||||
'stream-open-filename': (str, 'rw'), # Undocumented
|
'stream-open-filename': (bytes, 'rw'), # Undocumented
|
||||||
'file-format': (commalist,'r'), # Be careful with this one.
|
'file-format': (commalist,'r'), # Be careful with this one.
|
||||||
'mistimed-frame-count': (int, 'r'),
|
'mistimed-frame-count': (int, 'r'),
|
||||||
'vsync-ratio': (float, 'r'),
|
'vsync-ratio': (float, 'r'),
|
||||||
|
|
@ -788,45 +773,79 @@ ALL_PROPERTIES = {
|
||||||
'playback-time': (float, 'rw'),
|
'playback-time': (float, 'rw'),
|
||||||
'demuxer-cache-duration': (float, 'r'),
|
'demuxer-cache-duration': (float, 'r'),
|
||||||
'demuxer-cache-time': (float, 'r'),
|
'demuxer-cache-time': (float, 'r'),
|
||||||
'demuxer-cache-idle': (ynbool, 'r'),
|
'demuxer-cache-idle': (bool, 'r'),
|
||||||
'idle': (ynbool, 'r'),
|
'idle': (bool, 'r'),
|
||||||
'disc-title-list': (commalist,'r'),
|
'disc-title-list': (commalist,'r'),
|
||||||
'field-dominance': (str, 'rw'),
|
'field-dominance': (str, 'rw'),
|
||||||
'taskbar-progress': (ynbool, 'rw'),
|
'taskbar-progress': (bool, 'rw'),
|
||||||
'on-all-workspaces': (ynbool, 'rw'),
|
'on-all-workspaces': (bool, 'rw'),
|
||||||
'video-output-levels': (str, 'r'),
|
'video-output-levels': (str, 'r'),
|
||||||
'vo-configured': (ynbool, 'r'),
|
'vo-configured': (bool, 'r'),
|
||||||
'hwdec-current': (str, 'r'),
|
'hwdec-current': (str, 'r'),
|
||||||
'hwdec-interop': (str, 'r'),
|
'hwdec-interop': (str, 'r'),
|
||||||
'estimated-frame-count': (int, 'r'),
|
'estimated-frame-count': (int, 'r'),
|
||||||
'estimated-frame-number': (int, 'r'),
|
'estimated-frame-number': (int, 'r'),
|
||||||
'sub-use-margins': (ynbool, 'rw'),
|
'sub-use-margins': (bool, 'rw'),
|
||||||
'ass-force-margins': (ynbool, 'rw'),
|
'ass-force-margins': (bool, 'rw'),
|
||||||
'video-rotate': (str, 'rw'),
|
'video-rotate': (str, 'rw'),
|
||||||
'video-stereo-mode': (str, 'rw'),
|
'video-stereo-mode': (str, 'rw'),
|
||||||
'ab-loop-a': (str, 'rw'), # What a mess...
|
'ab-loop-a': (str, 'r'), # What a mess...
|
||||||
'ab-loop-b': (str, 'rw'),
|
'ab-loop-b': (str, 'r'),
|
||||||
'dvb-channel': (str, 'w'),
|
'dvb-channel': (str, 'w'),
|
||||||
'dvb-channel-name': (str, 'rw'),
|
'dvb-channel-name': (str, 'rw'),
|
||||||
'window-minimized': (ynbool, 'r'),
|
'window-minimized': (bool, 'r'),
|
||||||
'display-names': (commalist, 'r'),
|
'display-names': (commalist, 'r'),
|
||||||
'display-fps': (float, 'r'), # access apparently misdocumented in the manpage
|
'display-fps': (float, 'r'), # access apparently misdocumented in the manpage
|
||||||
'estimated-display-fps': (float, 'r'),
|
'estimated-display-fps': (float, 'r'),
|
||||||
'vsync-jitter': (float, 'r'),
|
'vsync-jitter': (float, 'r'),
|
||||||
|
'video-params': (node, 'r'),
|
||||||
|
'video-out-params': (node, 'r'),
|
||||||
|
'track-list': (node, 'r'),
|
||||||
|
'playlist': (node, 'r'),
|
||||||
|
'chapter-list': (node, 'r'),
|
||||||
|
'vo-performance': (node, 'r'),
|
||||||
'property-list': (commalist,'r')}
|
'property-list': (commalist,'r')}
|
||||||
|
|
||||||
def bindproperty(MPV, name, proptype, access):
|
def bindproperty(MPV, name, proptype, access):
|
||||||
def getter(self):
|
def getter(self):
|
||||||
cval = _mpv_get_property_string(self.handle, name.encode('utf-8'))
|
fmt = {int: MpvFormat.INT64,
|
||||||
if cval is None:
|
float: MpvFormat.DOUBLE,
|
||||||
return None
|
bool: MpvFormat.FLAG,
|
||||||
rv = proptype(cval.decode('utf-8'))
|
str: MpvFormat.STRING,
|
||||||
# _mpv_free(cval) FIXME
|
bytes: MpvFormat.STRING,
|
||||||
|
commalist: MpvFormat.STRING,
|
||||||
|
MpvFormat.NODE: MpvFormat.NODE}[proptype]
|
||||||
|
|
||||||
|
out = cast(create_string_buffer(sizeof(c_void_p)), c_void_p)
|
||||||
|
outptr = byref(out)
|
||||||
|
cval = _mpv_get_property(self.handle, name.encode('utf-8'), fmt, outptr)
|
||||||
|
rv = MpvNode.node_cast_value(outptr, fmt)
|
||||||
|
if proptype is str:
|
||||||
|
rv = rv.decode('utf-8')
|
||||||
|
elif proptype is commalist:
|
||||||
|
rv = proptype(rv.decode('utf-8'))
|
||||||
|
|
||||||
|
if proptype is str:
|
||||||
|
_mpv_free(out)
|
||||||
|
elif proptype is MpvFormat.NODE:
|
||||||
|
_mpv_free_node_contents(outptr)
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def setter(self, value):
|
def setter(self, value):
|
||||||
_mpv_set_property_string(self.handle, name.encode('utf-8'), str(proptype(value)).encode('utf-8'))
|
ename = name.encode('utf-8')
|
||||||
|
if type(value) is bytes:
|
||||||
|
_mpv_set_property_string(self.handle, ename, value)
|
||||||
|
elif type(value) is bool:
|
||||||
|
_mpv_set_property_string(self.handle, ename, b'yes' if value else b'no')
|
||||||
|
elif proptype in (str, int, float):
|
||||||
|
_mpv_set_property_string(self.handle, ename, str(proptype(value)).encode('utf-8'))
|
||||||
|
else:
|
||||||
|
raise TypeError('Cannot set {} property {} to value of type {}'.format(proptype, name, type(value)))
|
||||||
|
|
||||||
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 ALL_PROPERTIES.items():
|
for name, (proptype, access) in ALL_PROPERTIES.items():
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue