From 3c21d679da191f75e1fc929579a8454eeed039fd Mon Sep 17 00:00:00 2001 From: jaseg Date: Thu, 2 Jan 2020 16:21:47 +0100 Subject: Initial libmpv/render.h mapping For an example using this with GTK see: https://gist.github.com/jaseg/657e8ecca3267c0d82ec85d40f423caa --- mpv.py | 206 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 195 insertions(+), 11 deletions(-) diff --git a/mpv.py b/mpv.py index cd8c8ee..b0d6917 100644 --- a/mpv.py +++ b/mpv.py @@ -56,6 +56,9 @@ else: class MpvHandle(c_void_p): pass +class MpvRenderCtxHandle(c_void_p): + pass + class MpvOpenGLCbContext(c_void_p): pass @@ -125,6 +128,106 @@ class ErrorCode(object): if ex: raise ex(ec, *args) +MpvGlGetProcAddressFn = CFUNCTYPE(c_void_p, c_void_p, c_char_p) +class MpvOpenGLInitParams(Structure): + _fields_ = [('get_proc_address', MpvGlGetProcAddressFn), + ('get_proc_address_ctx', c_void_p), + ('extra_exts', c_void_p)] + + def __init__(self, get_proc_address): + self.get_proc_address = get_proc_address + self.get_proc_address_ctx = None + self.extra_exts = None + +class MpvOpenGLFBO(Structure): + _fields_ = [('fbo', c_int), + ('w', c_int), + ('h', c_int), + ('internal_format', c_int)] + + def __init__(self, w, h, fbo=0, internal_format=0): + self.w, self.h = w, h + self.fbo = fbo + self.internal_format = internal_format + +class MpvRenderFrameInfo(Structure): + _fields_ = [('flags', c_int64), + ('target_time', c_int64)] + + def as_dict(self): + return {'flags': self.flags, + 'target_time': self.target_time} + +class MpvOpenGLDRMParams(Structure): + _fields_ = [('fd', c_int), + ('crtc_id', c_int), + ('connector_id', c_int), + ('atomic_request_ptr', c_void_p), + ('render_fd', c_int)] + +class MpvOpenGLDRMDrawSurfaceSize(Structure): + _fields_ = [('width', c_int), ('height', c_int)] + +class MpvOpenGLDRMParamsV2(Structure): + _fields_ = [('fd', c_int), + ('crtc_id', c_int), + ('connector_id', c_int), + ('atomic_request_ptr', c_void_p), + ('render_fd', c_int)] + + def __init__(self, crtc_id, connector_id, atomic_request_ptr, fd=-1, render_fd=-1): + self.crtc_id, self.connector_id = crtc_id, connector_id + self.atomic_request_ptr = atomic_request_ptr + self.fd, self.render_fd = fd, render_fd + + +class MpvRenderParam(Structure): + _fields_ = [('type_id', c_int), + ('data', c_void_p)] + + # maps human-readable type name to (type_id, argtype) tuple. + # The type IDs come from libmpv/render.h + TYPES = {"invalid" :(0, None), + "api_type" :(1, str), + "opengl_init_params" :(2, MpvOpenGLInitParams), + "opengl_fbo" :(3, MpvOpenGLFBO), + "flip_y" :(4, bool), + "depth" :(5, int), + "icc_profile" :(6, bytes), + "ambient_light" :(7, int), + "x11_display" :(8, c_void_p), + "wl_display" :(9, c_void_p), + "advanced_control" :(10, bool), + "next_frame_info" :(11, MpvRenderFrameInfo), + "block_for_target_time" :(12, bool), + "skip_rendering" :(13, bool), + "drm_display" :(14, MpvOpenGLDRMParams), + "drm_draw_surface_size" :(15, MpvOpenGLDRMDrawSurfaceSize), + "drm_display_v2" :(16, MpvOpenGLDRMParamsV2)} + + def __init__(self, name, value=None): + if name not in self.TYPES: + raise ValueError('unknown render param type "{}"'.format(name)) + self.type_id, cons = self.TYPES[name] + if cons is None: + self.value = None + self.data = c_void_p() + elif cons is str: + self.value = value + self.data = cast(c_char_p(value.encode('utf-8')), c_void_p) + elif cons is bytes: + self.value = MpvByteArray(value) + self.data = cast(pointer(self.value), c_void_p) + elif cons is bool: + self.value = c_int(int(bool(value))) + self.data = cast(pointer(self.value), c_void_p) + else: + self.value = cons(**value) + self.data = cast(pointer(self.value), c_void_p) + +def kwargs_to_render_param_array(kwargs): + t = MpvRenderParam * (len(kwargs)+1) + return t(*kwargs.items(), ('invalid', None)) class MpvFormat(c_int): NONE = 0 @@ -211,6 +314,11 @@ class MpvByteArray(Structure): _fields_ = [('data', c_void_p), ('size', c_size_t)] + def __init__(self, value): + self._value = value + self.data = cast(c_char_p(value), c_void_p) + self.size = len(value) + def bytes_value(self): return cast(self.data, POINTER(c_char))[:self.size] @@ -357,17 +465,30 @@ StreamOpenFn = CFUNCTYPE(c_int, c_void_p, c_char_p, POINTER(StreamCallbackInfo)) WakeupCallback = CFUNCTYPE(None, c_void_p) +RenderUpdateFn = CFUNCTYPE(None, c_void_p) + OpenGlCbUpdateFn = CFUNCTYPE(None, c_void_p) OpenGlCbGetProcAddrFn = CFUNCTYPE(c_void_p, c_void_p, c_char_p) -def _handle_func(name, args, restype, errcheck, ctx=MpvHandle): +def _handle_func(name, args, restype, errcheck, ctx=MpvHandle, deprecated=False): func = getattr(backend, name) func.argtypes = [ctx] + args if ctx else args if restype is not None: func.restype = restype if errcheck is not None: func.errcheck = errcheck - globals()['_'+name] = func + if deprecated: + @wraps(func) + def wrapper(*args, **kwargs): + if not wrapper.warned: # Only warn on first invocation to prevent spamming + warn("Backend C api has been deprecated: " + name, DeprecationWarning, stacklevel=2) + wrapper.warned = True + return func(*args, **kwargs) + wrapper.warned = False + + globals()['_'+name] = wrapper + else: + globals()['_'+name] = func def bytes_free_errcheck(res, func, *args): notnull_errcheck(res, func, *args) @@ -383,8 +504,8 @@ def notnull_errcheck(res, func, *args): ec_errcheck = ErrorCode.raise_for_ec -def _handle_gl_func(name, args=[], restype=None): - _handle_func(name, args, restype, errcheck=None, ctx=MpvOpenGLCbContext) +def _handle_gl_func(name, args=[], restype=None, deprecated=False): + _handle_func(name, args, restype, errcheck=None, ctx=MpvOpenGLCbContext, deprecated=deprecated) backend.mpv_client_api_version.restype = c_ulong def _mpv_client_api_version(): @@ -439,14 +560,26 @@ _handle_func('mpv_get_wakeup_pipe', [], _handle_func('mpv_stream_cb_add_ro', [c_char_p, c_void_p, StreamOpenFn], c_int, ec_errcheck) -_handle_func('mpv_get_sub_api', [MpvSubApi], c_void_p, notnull_errcheck) +_handle_func('mpv_render_context_create', [MpvRenderCtxHandle, MpvHandle, POINTER(MpvRenderParam)], c_int, ec_errcheck, ctx=None) +_handle_func('mpv_render_context_set_parameter', [MpvRenderParam], c_int, ec_errcheck, ctx=MpvRenderCtxHandle) +_handle_func('mpv_render_context_get_info', [MpvRenderParam], c_int, ec_errcheck, ctx=MpvRenderCtxHandle) +_handle_func('mpv_render_context_set_update_callback', [RenderUpdateFn, c_void_p], None, errcheck=None, ctx=MpvRenderCtxHandle) +_handle_func('mpv_render_context_update', [], c_int64, errcheck=None, ctx=MpvRenderCtxHandle) +_handle_func('mpv_render_context_render', [POINTER(MpvRenderParam)], c_int, ec_errcheck, ctx=MpvRenderCtxHandle) +_handle_func('mpv_render_context_report_swap', [], None, errcheck=None, ctx=MpvRenderCtxHandle) +_handle_func('mpv_render_context_free', [], None, errcheck=None, ctx=MpvRenderCtxHandle) + + +# Deprecated in v0.29.0 and may disappear eventually +if hasattr(backend, 'mpv_get_sub_api'): + _handle_func('mpv_get_sub_api', [MpvSubApi], c_void_p, notnull_errcheck, deprecated=True) -_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], deprecated=True) + _handle_gl_func('mpv_opengl_cb_init_gl', [c_char_p, OpenGlCbGetProcAddrFn, c_void_p], c_int, deprecated=True) + _handle_gl_func('mpv_opengl_cb_draw', [c_int, c_int, c_int], c_int, deprecated=True) + _handle_gl_func('mpv_opengl_cb_render', [c_int, c_int], c_int, deprecated=True) + _handle_gl_func('mpv_opengl_cb_report_flip', [c_ulonglong], c_int, deprecated=True) + _handle_gl_func('mpv_opengl_cb_uninit_gl', [], c_int, deprecated=True) def _mpv_coax_proptype(value, proptype=str): @@ -1371,3 +1504,54 @@ class MPV(object): return self._get_property('option-info/'+name) except AttributeError: return None + +class MpvRenderContext: + def __init__(self, mpv, api_type, **kwargs): + self._mpv = mpv + kwargs['api_type'] = api_type + + buf = cast(create_string_buffer(sizeof(MpvRenderCtxHandle)), POINTER(MpvRenderCtxHandle)) + _mpv_render_context_create(buf, mpv.handle, kwargs_to_render_param_array(kwargs)) + self._handle = buf.contents + + def free(self): + _mpv_render_context_free(self._handle) + + def __setattr__(self, name, value): + if name.startswith('_'): + super().__setattr__(name, value) + + elif name == 'update_cb': + func = value if value else (lambda: None) + self._update_cb = value + self._update_fn_wrapper = RenderUpdateFn(lambda _userdata: func()) + _mpv_render_context_set_update_callback(self._handle, self._update_fn_wrapper, None) + + else: + param = MpvRenderParam(name, value) + _mpv_render_context_set_parameter(self._handle, param) + + def __getattr__(self, name): + if name == 'update_cb': + return self._update_cb + + elif name == 'handle': + return self._handle + + param = MpvRenderParam(name) + data_type = type(param.data.contents) + buf = cast(create_string_buffer(sizeof(data_type)), POINTER(data_type)) + param.data = buf + _mpv_render_context_get_info(self._handle, param) + return buf.contents.as_dict() + + def update(self): + """ Calls mpv_render_context_update and returns the MPV_RENDER_UPDATE_FRAME flag (see render.h) """ + return bool(_mpv_render_context_update(self._handle) & 1) + + def render(self, **kwargs): + _mpv_render_context_render(self._handle, kwargs_to_render_param_array(kwargs)) + + def report_swap(self): + _mpv_render_context_report_swap(self._handle) + -- cgit