Finally add node handling, fix ALL THE THINGS
* New node handling * Add remaining properties * Improve property type handling (no more ynbool!) * Add pröper option access * Add a whole bunch of tests
This commit is contained in:
parent
4d6c17d342
commit
de7b671103
|
@ -32,9 +32,12 @@ def my_log(loglevel, component, message):
|
||||||
|
|
||||||
player = mpv.MPV(log_handler=my_log, ytdl=True)
|
player = mpv.MPV(log_handler=my_log, ytdl=True)
|
||||||
|
|
||||||
|
# Property access, these can be changed at runtime
|
||||||
player.observe_property('time-pos', lambda _property, pos: print('Now playing at {:.2f}s'.format(pos)))
|
player.observe_property('time-pos', lambda _property, pos: print('Now playing at {:.2f}s'.format(pos)))
|
||||||
player.fullscreen = True
|
player.fullscreen = True
|
||||||
player.loop = 'inf'
|
player.loop = 'inf'
|
||||||
|
# Option access, in general these require the core to reinitialize
|
||||||
|
player['vo'] = 'opengl'
|
||||||
|
|
||||||
player.play('https://youtu.be/DLzxrzFCyOs')
|
player.play('https://youtu.be/DLzxrzFCyOs')
|
||||||
player.wait_for_playback()
|
player.wait_for_playback()
|
||||||
|
|
77
mpv-test.py
77
mpv-test.py
|
@ -31,7 +31,7 @@ class TestProperties(unittest.TestCase):
|
||||||
self.m = mpv.MPV()
|
self.m = mpv.MPV()
|
||||||
|
|
||||||
def test_sanity(self):
|
def test_sanity(self):
|
||||||
for name, (ptype, access) in mpv.ALL_PROPERTIES.items():
|
for name, (ptype, access, *_args) 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]+$')
|
||||||
# Types and MpvFormat values
|
# Types and MpvFormat values
|
||||||
|
@ -59,7 +59,7 @@ class TestProperties(unittest.TestCase):
|
||||||
self.m.play(TESTVID)
|
self.m.play(TESTVID)
|
||||||
while self.m.core_idle:
|
while self.m.core_idle:
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
for name, (ptype, access) in sorted(mpv.ALL_PROPERTIES.items()):
|
for name, (ptype, access, *_args) 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):
|
||||||
|
@ -72,34 +72,54 @@ class TestProperties(unittest.TestCase):
|
||||||
self.assertEqual(type(rv), type(ptype()))
|
self.assertEqual(type(rv), type(ptype()))
|
||||||
|
|
||||||
def test_write(self):
|
def test_write(self):
|
||||||
for name, (ptype, access) in sorted(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, *_args) in sorted(mpv.ALL_PROPERTIES.items()):
|
||||||
if 'w' in access:
|
if 'w' in access:
|
||||||
name = name.replace('-', '_')
|
name = name.replace('-', '_')
|
||||||
|
with self.subTest(property_name=name):
|
||||||
|
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:
|
||||||
|
setattr(self.m, name, 0)
|
||||||
|
setattr(self.m, name, 1)
|
||||||
|
setattr(self.m, name, -1)
|
||||||
|
elif ptype == float:
|
||||||
|
setattr(self.m, name, 0.0)
|
||||||
|
setattr(self.m, name, 1)
|
||||||
|
setattr(self.m, name, 1.0)
|
||||||
|
setattr(self.m, name, -1.0)
|
||||||
|
setattr(self.m, name, math.nan)
|
||||||
|
elif ptype == str:
|
||||||
|
setattr(self.m, name, 'foo')
|
||||||
|
setattr(self.m, name, '')
|
||||||
|
setattr(self.m, name, 'bazbazbaz'*1000)
|
||||||
|
elif ptype == bytes:
|
||||||
|
setattr(self.m, name, b'foo')
|
||||||
|
setattr(self.m, name, b'')
|
||||||
|
setattr(self.m, name, b'bazbazbaz'*1000)
|
||||||
|
elif ptype == bool:
|
||||||
|
setattr(self.m, name, True)
|
||||||
|
setattr(self.m, name, False)
|
||||||
|
|
||||||
|
def test_option_read(self):
|
||||||
|
self.m.loop = 'inf'
|
||||||
|
self.m.play(TESTVID)
|
||||||
|
while self.m.core_idle:
|
||||||
|
time.sleep(0.05)
|
||||||
|
for name in sorted(self.m):
|
||||||
|
with self.subTest(option_name=name):
|
||||||
with self.swallow_mpv_errors([
|
with self.swallow_mpv_errors([
|
||||||
mpv.ErrorCode.PROPERTY_UNAVAILABLE,
|
mpv.ErrorCode.PROPERTY_UNAVAILABLE,
|
||||||
mpv.ErrorCode.PROPERTY_ERROR,
|
mpv.ErrorCode.PROPERTY_NOT_FOUND,
|
||||||
mpv.ErrorCode.PROPERTY_FORMAT]): # This is due to a bug with option-mapped properties in mpv 0.18.1
|
mpv.ErrorCode.PROPERTY_ERROR]):
|
||||||
if ptype == int:
|
self.m[name]
|
||||||
setattr(self.m, name, 0)
|
|
||||||
setattr(self.m, name, 1)
|
|
||||||
setattr(self.m, name, -1)
|
|
||||||
elif ptype == float:
|
|
||||||
setattr(self.m, name, 0.0)
|
|
||||||
setattr(self.m, name, 1)
|
|
||||||
setattr(self.m, name, 1.0)
|
|
||||||
setattr(self.m, name, -1.0)
|
|
||||||
setattr(self.m, name, math.nan)
|
|
||||||
elif ptype == str:
|
|
||||||
setattr(self.m, name, 'foo')
|
|
||||||
setattr(self.m, name, '')
|
|
||||||
setattr(self.m, name, 'bazbazbaz'*1000)
|
|
||||||
elif ptype == bytes:
|
|
||||||
setattr(self.m, name, b'foo')
|
|
||||||
setattr(self.m, name, b'')
|
|
||||||
setattr(self.m, name, b'bazbazbaz'*1000)
|
|
||||||
elif ptype == bool:
|
|
||||||
setattr(self.m, name, True)
|
|
||||||
setattr(self.m, name, False)
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
del self.m
|
del self.m
|
||||||
|
|
||||||
|
@ -166,7 +186,7 @@ class TestLifecycle(unittest.TestCase):
|
||||||
mock.call({'reply_userdata': 0, 'error': 0, 'event_id': 11, 'event': None}),
|
mock.call({'reply_userdata': 0, 'error': 0, 'event_id': 11, 'event': None}),
|
||||||
mock.call({'reply_userdata': 0, 'error': 0, 'event_id': 1, 'event': None})
|
mock.call({'reply_userdata': 0, 'error': 0, 'event_id': 1, 'event': None})
|
||||||
], any_order=True)
|
], any_order=True)
|
||||||
|
|
||||||
def test_log_handler(self):
|
def test_log_handler(self):
|
||||||
handler = mock.Mock()
|
handler = mock.Mock()
|
||||||
m = mpv.MPV('no-video', log_handler=handler)
|
m = mpv.MPV('no-video', log_handler=handler)
|
||||||
|
@ -176,8 +196,7 @@ class TestLifecycle(unittest.TestCase):
|
||||||
handler.assert_has_calls([
|
handler.assert_has_calls([
|
||||||
mock.call('info', 'cplayer', 'Playing: test.webm'),
|
mock.call('info', 'cplayer', 'Playing: test.webm'),
|
||||||
mock.call('info', 'cplayer', ' Video --vid=1 (*) (vp8)'),
|
mock.call('info', 'cplayer', ' Video --vid=1 (*) (vp8)'),
|
||||||
mock.call('fatal', 'cplayer', 'No video or audio streams selected.'),
|
mock.call('fatal', 'cplayer', 'No video or audio streams selected.')])
|
||||||
mock.call('info', 'cplayer', '')])
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
546
mpv.py
546
mpv.py
|
@ -55,8 +55,7 @@ class ErrorCode(object):
|
||||||
-9: lambda *a: TypeError('Tried to get/set mpv property using wrong format, or passed invalid value', *a),
|
-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: RuntimeError('Generic error getting or setting 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) }
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def default_error_handler(ec, *args):
|
def default_error_handler(ec, *args):
|
||||||
|
@ -121,34 +120,32 @@ class MpvEventID(c_int):
|
||||||
|
|
||||||
|
|
||||||
class MpvNodeList(Structure):
|
class MpvNodeList(Structure):
|
||||||
@property
|
def array_value(self, decode_str=False):
|
||||||
def array_value(self):
|
return [ self.values[i].node_value(decode_str) for i in range(self.num) ]
|
||||||
return [ self.values[i].node_value for i in range(self.num) ]
|
|
||||||
|
|
||||||
@property
|
def dict_value(self, decode_str=False):
|
||||||
def dict_value(self):
|
return { self.keys[i].decode('utf-8'): self.values[i].node_value(decode_str) for i in range(self.num) }
|
||||||
return { self.keys[i].decode('utf-8'): self.values[i].node_value for i in range(self.num) }
|
|
||||||
|
|
||||||
class MpvNode(Structure):
|
class MpvNode(Structure):
|
||||||
_fields_ = [('val', c_longlong),
|
_fields_ = [('val', c_longlong),
|
||||||
('format', MpvFormat)]
|
('format', MpvFormat)]
|
||||||
|
|
||||||
@property
|
def node_value(self, decode_str=False):
|
||||||
def node_value(self):
|
return MpvNode.node_cast_value(byref(c_void_p(self.val)), self.format.value, decode_str)
|
||||||
return MpvNode.node_cast_value(byref(c_void_p(self.val)), self.format.value)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def node_cast_value(v, fmt):
|
def node_cast_value(v, fmt, decode_str=False):
|
||||||
|
dwrap = lambda s: s.decode('utf-8') if decode_str else s
|
||||||
return {
|
return {
|
||||||
MpvFormat.NONE: lambda v: None,
|
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.STRING: lambda v: dwrap(cast(v, POINTER(c_char_p)).contents.value),
|
||||||
MpvFormat.OSD_STRING: lambda v: cast(v, POINTER(c_char_p)).contents.value.decode('utf-8'),
|
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.FLAG: lambda v: bool(cast(v, POINTER(c_int)).contents.value),
|
||||||
MpvFormat.INT64: lambda v: cast(v, POINTER(c_longlong)).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.DOUBLE: lambda v: cast(v, POINTER(c_double)).contents.value,
|
||||||
MpvFormat.NODE: lambda v: cast(v, POINTER(MpvNode)).contents.node_value,
|
MpvFormat.NODE: lambda v: cast(v, POINTER(MpvNode)).contents.node_value(decode_str),
|
||||||
MpvFormat.NODE_ARRAY: lambda v: cast(v, POINTER(POINTER(MpvNodeList))).contents.contents.array_value,
|
MpvFormat.NODE_ARRAY: lambda v: cast(v, POINTER(POINTER(MpvNodeList))).contents.contents.array_value(decode_str),
|
||||||
MpvFormat.NODE_MAP: lambda v: cast(v, POINTER(POINTER(MpvNodeList))).contents.contents.dict_value,
|
MpvFormat.NODE_MAP: lambda v: cast(v, POINTER(POINTER(MpvNodeList))).contents.contents.dict_value(decode_str),
|
||||||
MpvFormat.BYTE_ARRAY: lambda v: cast(v, POINTER(c_char_p)).contents.value,
|
MpvFormat.BYTE_ARRAY: lambda v: cast(v, POINTER(c_char_p)).contents.value,
|
||||||
}[fmt](v)
|
}[fmt](v)
|
||||||
|
|
||||||
|
@ -432,9 +429,6 @@ class MPV(object):
|
||||||
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):
|
|
||||||
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)
|
||||||
|
|
||||||
|
@ -537,277 +531,8 @@ class MPV(object):
|
||||||
def play(self, filename):
|
def play(self, filename):
|
||||||
self.loadfile(filename)
|
self.loadfile(filename)
|
||||||
|
|
||||||
# Complex properties
|
# Property accessors
|
||||||
|
def _get_property(self, name, proptype=str, decode_str=False):
|
||||||
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 }
|
|
||||||
|
|
||||||
def _get_list(self, prefix, props):
|
|
||||||
count = int(_ensure_encoding(_mpv_get_property_string(self.handle, (prefix+'count').encode('utf-8'))))
|
|
||||||
return [ self._get_dict(prefix+str(index)+'/', props) for index in range(count)]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def filtered_metadata(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def metadata(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def chapter_metadata(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def vf_metadata(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def af_metadata(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def edition_list(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _disc_titles(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def audio_params(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def audio_out_params(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def audio_device_list(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def video_frame_info(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def vf(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def af(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def decoder_list(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def encoder_list(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def options(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def file_local_options(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
|
||||||
def option_info(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# TODO: audio-device-list, decoder-list, encoder-list
|
|
||||||
|
|
||||||
def commalist(propval=''):
|
|
||||||
return str(propval).split(',')
|
|
||||||
|
|
||||||
node = MpvFormat.NODE
|
|
||||||
|
|
||||||
ALL_PROPERTIES = {
|
|
||||||
'osd-level': (int, 'rw'),
|
|
||||||
'osd-scale': (float, 'rw'),
|
|
||||||
'loop': (str, 'rw'),
|
|
||||||
'loop-file': (str, 'rw'),
|
|
||||||
'speed': (float, 'rw'),
|
|
||||||
'filename': (bytes, 'r'),
|
|
||||||
'file-size': (int, 'r'),
|
|
||||||
'path': (bytes, 'r'),
|
|
||||||
'media-title': (bytes, 'r'),
|
|
||||||
'stream-pos': (int, 'rw'),
|
|
||||||
'stream-end': (int, 'r'),
|
|
||||||
'length': (float, 'r'), # deprecated for ages now
|
|
||||||
'duration': (float, 'r'),
|
|
||||||
'avsync': (float, 'r'),
|
|
||||||
'total-avsync-change': (float, 'r'),
|
|
||||||
'drop-frame-count': (int, 'r'),
|
|
||||||
'percent-pos': (float, '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': (bool, 'r'),
|
|
||||||
'chapters': (int, 'r'),
|
|
||||||
'editions': (int, 'r'),
|
|
||||||
'angle': (int, 'rw'),
|
|
||||||
'pause': (bool, 'rw'),
|
|
||||||
'core-idle': (bool, 'r'),
|
|
||||||
'cache': (int, 'r'),
|
|
||||||
'cache-size': (int, 'rw'),
|
|
||||||
'cache-free': (int, 'r'),
|
|
||||||
'cache-used': (int, 'r'),
|
|
||||||
'cache-speed': (int, 'r'),
|
|
||||||
'cache-idle': (bool, 'r'),
|
|
||||||
'cache-buffering-state': (int, 'r'),
|
|
||||||
'paused-for-cache': (bool, 'r'),
|
|
||||||
# 'pause-for-cache': (bool, 'r'),
|
|
||||||
'eof-reached': (bool, 'r'),
|
|
||||||
# 'pts-association-mode': (str, 'rw'),
|
|
||||||
'hr-seek': (str, 'rw'),
|
|
||||||
'volume': (float, 'rw'),
|
|
||||||
'volume-max': (int, 'rw'),
|
|
||||||
'ao-volume': (float, 'rw'),
|
|
||||||
'mute': (bool, 'rw'),
|
|
||||||
'ao-mute': (bool, 'rw'),
|
|
||||||
'audio-speed-correction': (float, 'r'),
|
|
||||||
'audio-delay': (float, 'rw'),
|
|
||||||
'audio-format': (str, 'r'),
|
|
||||||
'audio-codec': (str, 'r'),
|
|
||||||
'audio-codec-name': (str, 'r'),
|
|
||||||
'audio-bitrate': (float, 'r'),
|
|
||||||
'packet-audio-bitrate': (float, 'r'),
|
|
||||||
'audio-samplerate': (int, 'r'),
|
|
||||||
'audio-channels': (str, 'r'),
|
|
||||||
'aid': (str, 'rw'),
|
|
||||||
'audio': (str, 'rw'), # alias for aid
|
|
||||||
'balance': (int, 'rw'),
|
|
||||||
'fullscreen': (bool, 'rw'),
|
|
||||||
'deinterlace': (str, 'rw'),
|
|
||||||
'colormatrix': (str, 'rw'),
|
|
||||||
'colormatrix-input-range': (str, 'rw'),
|
|
||||||
# 'colormatrix-output-range': (str, 'rw'),
|
|
||||||
'colormatrix-primaries': (str, 'rw'),
|
|
||||||
'ontop': (bool, 'rw'),
|
|
||||||
'border': (bool, 'rw'),
|
|
||||||
'framedrop': (str, 'rw'),
|
|
||||||
'gamma': (float, 'rw'),
|
|
||||||
'brightness': (int, 'rw'),
|
|
||||||
'contrast': (int, 'rw'),
|
|
||||||
'saturation': (int, 'rw'),
|
|
||||||
'hue': (int, 'rw'),
|
|
||||||
'hwdec': (str, 'rw'),
|
|
||||||
'panscan': (float, 'rw'),
|
|
||||||
'video-format': (str, 'r'),
|
|
||||||
'video-codec': (str, 'r'),
|
|
||||||
'video-bitrate': (float, 'r'),
|
|
||||||
'packet-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': (str, 'rw'),
|
|
||||||
'video': (str, 'rw'), # alias for vid
|
|
||||||
'video-align-x': (float, 'rw'),
|
|
||||||
'video-align-y': (float, 'rw'),
|
|
||||||
'video-pan-x': (float, 'rw'),
|
|
||||||
'video-pan-y': (float, 'rw'),
|
|
||||||
'video-zoom': (float, 'rw'),
|
|
||||||
'video-unscaled': (bool, 'w'),
|
|
||||||
'video-speed-correction': (float, 'r'),
|
|
||||||
'program': (int, 'w'),
|
|
||||||
'sid': (str, 'rw'),
|
|
||||||
'sub': (str, 'rw'), # alias for sid
|
|
||||||
'secondary-sid': (str, 'rw'),
|
|
||||||
'sub-delay': (float, 'rw'),
|
|
||||||
'sub-pos': (int, 'rw'),
|
|
||||||
'sub-visibility': (bool, 'rw'),
|
|
||||||
'sub-forced-only': (bool, 'rw'),
|
|
||||||
'sub-scale': (float, 'rw'),
|
|
||||||
'sub-bitrate': (float, 'r'),
|
|
||||||
'packet-sub-bitrate': (float, 'r'),
|
|
||||||
# 'ass-use-margins': (bool, 'rw'),
|
|
||||||
'ass-vsfilter-aspect-compat': (bool, 'rw'),
|
|
||||||
'ass-style-override': (bool, '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-pos-1': (int, 'rw'), # ugh.
|
|
||||||
'playlist-count': (int, 'r'),
|
|
||||||
# 'quvi-format': (str, 'rw'),
|
|
||||||
'seekable': (bool, 'r'),
|
|
||||||
'seeking': (bool, 'r'),
|
|
||||||
'partially-seekable': (bool, 'r'),
|
|
||||||
'playback-abort': (bool, 'r'),
|
|
||||||
'cursor-autohide': (str, 'rw'),
|
|
||||||
'audio-device': (str, 'rw'),
|
|
||||||
'current-vo': (str, 'r'),
|
|
||||||
'current-ao': (str, 'r'),
|
|
||||||
'audio-out-detected-device': (str, 'r'),
|
|
||||||
'protocol-list': (str, 'r'),
|
|
||||||
'mpv-version': (str, 'r'),
|
|
||||||
'mpv-configuration': (str, 'r'),
|
|
||||||
'ffmpeg-version': (str, 'r'),
|
|
||||||
'display-sync-active': (bool, 'r'),
|
|
||||||
'stream-open-filename': (bytes, 'rw'), # Undocumented
|
|
||||||
'file-format': (commalist,'r'), # Be careful with this one.
|
|
||||||
'mistimed-frame-count': (int, 'r'),
|
|
||||||
'vsync-ratio': (float, 'r'),
|
|
||||||
'vo-drop-frame-count': (int, 'r'),
|
|
||||||
'vo-delayed-frame-count': (int, 'r'),
|
|
||||||
'playback-time': (float, 'rw'),
|
|
||||||
'demuxer-cache-duration': (float, 'r'),
|
|
||||||
'demuxer-cache-time': (float, 'r'),
|
|
||||||
'demuxer-cache-idle': (bool, 'r'),
|
|
||||||
'idle': (bool, 'r'),
|
|
||||||
'disc-title-list': (commalist,'r'),
|
|
||||||
'field-dominance': (str, 'rw'),
|
|
||||||
'taskbar-progress': (bool, 'rw'),
|
|
||||||
'on-all-workspaces': (bool, 'rw'),
|
|
||||||
'video-output-levels': (str, 'r'),
|
|
||||||
'vo-configured': (bool, 'r'),
|
|
||||||
'hwdec-current': (str, 'r'),
|
|
||||||
'hwdec-interop': (str, 'r'),
|
|
||||||
'estimated-frame-count': (int, 'r'),
|
|
||||||
'estimated-frame-number': (int, 'r'),
|
|
||||||
'sub-use-margins': (bool, 'rw'),
|
|
||||||
'ass-force-margins': (bool, 'rw'),
|
|
||||||
'video-rotate': (str, 'rw'),
|
|
||||||
'video-stereo-mode': (str, 'rw'),
|
|
||||||
'ab-loop-a': (str, 'r'), # What a mess...
|
|
||||||
'ab-loop-b': (str, 'r'),
|
|
||||||
'dvb-channel': (str, 'w'),
|
|
||||||
'dvb-channel-name': (str, 'rw'),
|
|
||||||
'window-minimized': (bool, 'r'),
|
|
||||||
'display-names': (commalist, 'r'),
|
|
||||||
'display-fps': (float, 'r'), # access apparently misdocumented in the manpage
|
|
||||||
'estimated-display-fps': (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')}
|
|
||||||
|
|
||||||
def bindproperty(MPV, name, proptype, access):
|
|
||||||
def getter(self):
|
|
||||||
fmt = {int: MpvFormat.INT64,
|
fmt = {int: MpvFormat.INT64,
|
||||||
float: MpvFormat.DOUBLE,
|
float: MpvFormat.DOUBLE,
|
||||||
bool: MpvFormat.FLAG,
|
bool: MpvFormat.FLAG,
|
||||||
|
@ -819,11 +544,10 @@ def bindproperty(MPV, name, proptype, access):
|
||||||
out = cast(create_string_buffer(sizeof(c_void_p)), c_void_p)
|
out = cast(create_string_buffer(sizeof(c_void_p)), c_void_p)
|
||||||
outptr = byref(out)
|
outptr = byref(out)
|
||||||
cval = _mpv_get_property(self.handle, name.encode('utf-8'), fmt, outptr)
|
cval = _mpv_get_property(self.handle, name.encode('utf-8'), fmt, outptr)
|
||||||
rv = MpvNode.node_cast_value(outptr, fmt)
|
rv = MpvNode.node_cast_value(outptr, fmt, decode_str or proptype in (str, commalist))
|
||||||
if proptype is str:
|
|
||||||
rv = rv.decode('utf-8')
|
if proptype is commalist:
|
||||||
elif proptype is commalist:
|
rv = proptype(rv)
|
||||||
rv = proptype(rv.decode('utf-8'))
|
|
||||||
|
|
||||||
if proptype is str:
|
if proptype is str:
|
||||||
_mpv_free(out)
|
_mpv_free(out)
|
||||||
|
@ -832,7 +556,7 @@ def bindproperty(MPV, name, proptype, access):
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
def setter(self, value):
|
def _set_property(self, name, value, proptype=str):
|
||||||
ename = name.encode('utf-8')
|
ename = name.encode('utf-8')
|
||||||
if type(value) is bytes:
|
if type(value) is bytes:
|
||||||
_mpv_set_property_string(self.handle, ename, value)
|
_mpv_set_property_string(self.handle, ename, value)
|
||||||
|
@ -843,11 +567,235 @@ def bindproperty(MPV, name, proptype, access):
|
||||||
else:
|
else:
|
||||||
raise TypeError('Cannot set {} property {} to value of type {}'.format(proptype, name, type(value)))
|
raise TypeError('Cannot set {} property {} to value of type {}'.format(proptype, name, type(value)))
|
||||||
|
|
||||||
|
# Dict-like option access
|
||||||
|
def __getitem__(self, name, file_local=False):
|
||||||
|
""" Get an option value """
|
||||||
|
prefix = 'file-local-options/' if file_local else 'options/'
|
||||||
|
return self._get_property(prefix+name)
|
||||||
|
|
||||||
|
def __setitem__(self, name, value, file_local=False):
|
||||||
|
""" Get an option value """
|
||||||
|
prefix = 'file-local-options/' if file_local else 'options/'
|
||||||
|
return self._set_property(prefix+name, value)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.options)
|
||||||
|
|
||||||
|
def option_info(self, name):
|
||||||
|
return self._get_property('option-info/'+name)
|
||||||
|
|
||||||
|
def commalist(propval=''):
|
||||||
|
return str(propval).split(',')
|
||||||
|
|
||||||
|
node = MpvFormat.NODE
|
||||||
|
|
||||||
|
ALL_PROPERTIES = {
|
||||||
|
'osd-level': (int, 'rw'),
|
||||||
|
'osd-scale': (float, 'rw'),
|
||||||
|
'loop': (str, 'rw'),
|
||||||
|
'loop-file': (str, 'rw'),
|
||||||
|
'speed': (float, 'rw'),
|
||||||
|
'filename': (bytes, 'r'),
|
||||||
|
'file-size': (int, 'r'),
|
||||||
|
'path': (bytes, 'r'),
|
||||||
|
'media-title': (bytes, 'r'),
|
||||||
|
'stream-pos': (int, 'rw'),
|
||||||
|
'stream-end': (int, 'r'),
|
||||||
|
'length': (float, 'r'), # deprecated for ages now
|
||||||
|
'duration': (float, 'r'),
|
||||||
|
'avsync': (float, 'r'),
|
||||||
|
'total-avsync-change': (float, 'r'),
|
||||||
|
'drop-frame-count': (int, 'r'),
|
||||||
|
'percent-pos': (float, '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': (bool, 'r'),
|
||||||
|
'chapters': (int, 'r'),
|
||||||
|
'editions': (int, 'r'),
|
||||||
|
'angle': (int, 'rw'),
|
||||||
|
'pause': (bool, 'rw'),
|
||||||
|
'core-idle': (bool, 'r'),
|
||||||
|
'cache': (int, 'r'),
|
||||||
|
'cache-size': (int, 'rw'),
|
||||||
|
'cache-free': (int, 'r'),
|
||||||
|
'cache-used': (int, 'r'),
|
||||||
|
'cache-speed': (int, 'r'),
|
||||||
|
'cache-idle': (bool, 'r'),
|
||||||
|
'cache-buffering-state': (int, 'r'),
|
||||||
|
'paused-for-cache': (bool, 'r'),
|
||||||
|
# 'pause-for-cache': (bool, 'r'),
|
||||||
|
'eof-reached': (bool, 'r'),
|
||||||
|
# 'pts-association-mode': (str, 'rw'),
|
||||||
|
'hr-seek': (str, 'rw'),
|
||||||
|
'volume': (float, 'rw'),
|
||||||
|
'volume-max': (int, 'rw'),
|
||||||
|
'ao-volume': (float, 'rw'),
|
||||||
|
'mute': (bool, 'rw'),
|
||||||
|
'ao-mute': (bool, 'rw'),
|
||||||
|
'audio-speed-correction': (float, 'r'),
|
||||||
|
'audio-delay': (float, 'rw'),
|
||||||
|
'audio-format': (str, 'r'),
|
||||||
|
'audio-codec': (str, 'r'),
|
||||||
|
'audio-codec-name': (str, 'r'),
|
||||||
|
'audio-bitrate': (float, 'r'),
|
||||||
|
'packet-audio-bitrate': (float, 'r'),
|
||||||
|
'audio-samplerate': (int, 'r'),
|
||||||
|
'audio-channels': (str, 'r'),
|
||||||
|
'aid': (str, 'rw'),
|
||||||
|
'audio': (str, 'rw'), # alias for aid
|
||||||
|
'balance': (int, 'rw'),
|
||||||
|
'fullscreen': (bool, 'rw'),
|
||||||
|
'deinterlace': (str, 'rw'),
|
||||||
|
'colormatrix': (str, 'rw'),
|
||||||
|
'colormatrix-input-range': (str, 'rw'),
|
||||||
|
# 'colormatrix-output-range': (str, 'rw'),
|
||||||
|
'colormatrix-primaries': (str, 'rw'),
|
||||||
|
'ontop': (bool, 'rw'),
|
||||||
|
'border': (bool, 'rw'),
|
||||||
|
'framedrop': (str, 'rw'),
|
||||||
|
'gamma': (float, 'rw'),
|
||||||
|
'brightness': (int, 'rw'),
|
||||||
|
'contrast': (int, 'rw'),
|
||||||
|
'saturation': (int, 'rw'),
|
||||||
|
'hue': (int, 'rw'),
|
||||||
|
'hwdec': (str, 'rw'),
|
||||||
|
'panscan': (float, 'rw'),
|
||||||
|
'video-format': (str, 'r'),
|
||||||
|
'video-codec': (str, 'r'),
|
||||||
|
'video-bitrate': (float, 'r'),
|
||||||
|
'packet-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': (str, 'rw'),
|
||||||
|
'video': (str, 'rw'), # alias for vid
|
||||||
|
'video-align-x': (float, 'rw'),
|
||||||
|
'video-align-y': (float, 'rw'),
|
||||||
|
'video-pan-x': (float, 'rw'),
|
||||||
|
'video-pan-y': (float, 'rw'),
|
||||||
|
'video-zoom': (float, 'rw'),
|
||||||
|
'video-unscaled': (bool, 'w'),
|
||||||
|
'video-speed-correction': (float, 'r'),
|
||||||
|
'program': (int, 'w'),
|
||||||
|
'sid': (str, 'rw'),
|
||||||
|
'sub': (str, 'rw'), # alias for sid
|
||||||
|
'secondary-sid': (str, 'rw'),
|
||||||
|
'sub-delay': (float, 'rw'),
|
||||||
|
'sub-pos': (int, 'rw'),
|
||||||
|
'sub-visibility': (bool, 'rw'),
|
||||||
|
'sub-forced-only': (bool, 'rw'),
|
||||||
|
'sub-scale': (float, 'rw'),
|
||||||
|
'sub-bitrate': (float, 'r'),
|
||||||
|
'packet-sub-bitrate': (float, 'r'),
|
||||||
|
# 'ass-use-margins': (bool, 'rw'),
|
||||||
|
'ass-vsfilter-aspect-compat': (bool, 'rw'),
|
||||||
|
'ass-style-override': (bool, '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-pos-1': (int, 'rw'), # ugh.
|
||||||
|
'playlist-count': (int, 'r'),
|
||||||
|
# 'quvi-format': (str, 'rw'),
|
||||||
|
'seekable': (bool, 'r'),
|
||||||
|
'seeking': (bool, 'r'),
|
||||||
|
'partially-seekable': (bool, 'r'),
|
||||||
|
'playback-abort': (bool, 'r'),
|
||||||
|
'cursor-autohide': (str, 'rw'),
|
||||||
|
'audio-device': (str, 'rw'),
|
||||||
|
'current-vo': (str, 'r'),
|
||||||
|
'current-ao': (str, 'r'),
|
||||||
|
'audio-out-detected-device': (str, 'r'),
|
||||||
|
'protocol-list': (str, 'r'),
|
||||||
|
'mpv-version': (str, 'r'),
|
||||||
|
'mpv-configuration': (str, 'r'),
|
||||||
|
'ffmpeg-version': (str, 'r'),
|
||||||
|
'display-sync-active': (bool, 'r'),
|
||||||
|
'stream-open-filename': (bytes, 'rw'), # Undocumented
|
||||||
|
'file-format': (commalist,'r'), # Be careful with this one.
|
||||||
|
'mistimed-frame-count': (int, 'r'),
|
||||||
|
'vsync-ratio': (float, 'r'),
|
||||||
|
'vo-drop-frame-count': (int, 'r'),
|
||||||
|
'vo-delayed-frame-count': (int, 'r'),
|
||||||
|
'playback-time': (float, 'rw'),
|
||||||
|
'demuxer-cache-duration': (float, 'r'),
|
||||||
|
'demuxer-cache-time': (float, 'r'),
|
||||||
|
'demuxer-cache-idle': (bool, 'r'),
|
||||||
|
'idle': (bool, 'r'),
|
||||||
|
'disc-title-list': (commalist,'r'),
|
||||||
|
'field-dominance': (str, 'rw'),
|
||||||
|
'taskbar-progress': (bool, 'rw'),
|
||||||
|
'on-all-workspaces': (bool, 'rw'),
|
||||||
|
'video-output-levels': (str, 'r'),
|
||||||
|
'vo-configured': (bool, 'r'),
|
||||||
|
'hwdec-current': (str, 'r'),
|
||||||
|
'hwdec-interop': (str, 'r'),
|
||||||
|
'estimated-frame-count': (int, 'r'),
|
||||||
|
'estimated-frame-number': (int, 'r'),
|
||||||
|
'sub-use-margins': (bool, 'rw'),
|
||||||
|
'ass-force-margins': (bool, 'rw'),
|
||||||
|
'video-rotate': (str, 'rw'),
|
||||||
|
'video-stereo-mode': (str, 'rw'),
|
||||||
|
'ab-loop-a': (str, 'r'), # What a mess...
|
||||||
|
'ab-loop-b': (str, 'r'),
|
||||||
|
'dvb-channel': (str, 'w'),
|
||||||
|
'dvb-channel-name': (str, 'rw'),
|
||||||
|
'window-minimized': (bool, 'r'),
|
||||||
|
'display-names': (commalist, 'r'),
|
||||||
|
'display-fps': (float, 'r'), # access apparently misdocumented in the manpage
|
||||||
|
'estimated-display-fps': (float, 'r'),
|
||||||
|
'vsync-jitter': (float, 'r'),
|
||||||
|
'video-params': (node, 'r', True),
|
||||||
|
'video-out-params': (node, 'r', True),
|
||||||
|
'track-list': (node, 'r', False),
|
||||||
|
'playlist': (node, 'r', False),
|
||||||
|
'chapter-list': (node, 'r', False),
|
||||||
|
'vo-performance': (node, 'r', True),
|
||||||
|
'filtered-metadata': (node, 'r', False),
|
||||||
|
'metadata': (node, 'r', False),
|
||||||
|
'chapter-metadata': (node, 'r', False),
|
||||||
|
'vf-metadata': (node, 'r', False),
|
||||||
|
'af-metadata': (node, 'r', False),
|
||||||
|
'edition-list': (node, 'r', False),
|
||||||
|
'disc-titles': (node, 'r', False),
|
||||||
|
'audio-params': (node, 'r', True),
|
||||||
|
'audio-out-params': (node, 'r', True),
|
||||||
|
'audio-device-list': (node, 'r', True),
|
||||||
|
'video-frame-info': (node, 'r', True),
|
||||||
|
'decoder-list': (node, 'r', True),
|
||||||
|
'encoder-list': (node, 'r', True),
|
||||||
|
'vf': (node, 'r', True),
|
||||||
|
'af': (node, 'r', True),
|
||||||
|
'options': (node, 'r', True),
|
||||||
|
'file-local-options': (node, 'r', True),
|
||||||
|
'property-list': (commalist,'r')}
|
||||||
|
|
||||||
|
def bindproperty(MPV, name, proptype, access, decode_str=False):
|
||||||
|
getter = lambda self: self._get_property(name, proptype, decode_str)
|
||||||
|
setter = lambda self, value: self._set_property(name, value, proptype)
|
||||||
|
|
||||||
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, *args) in ALL_PROPERTIES.items():
|
||||||
bindproperty(MPV, name, proptype, access)
|
bindproperty(MPV, name, proptype, access, *args)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue