From 739624f4f501b28e000ae2c56fa61f11e191fec6 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 23 Sep 2016 00:57:09 +0900 Subject: [PATCH 01/36] Remove enum values These aren't needed, and risk getting out of sync with the header if provided. --- client.pxd | 70 +++++++++++++++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/client.pxd b/client.pxd index a6509a1..c0b5d87 100644 --- a/client.pxd +++ b/client.pxd @@ -75,26 +75,26 @@ cdef extern from "mpv/client.h": pass cdef enum mpv_error: - MPV_ERROR_SUCCESS = 0 - MPV_ERROR_EVENT_QUEUE_FULL = -1 - MPV_ERROR_NOMEM = -2 - MPV_ERROR_UNINITIALIZED = -3 - MPV_ERROR_INVALID_PARAMETER = -4 - MPV_ERROR_OPTION_NOT_FOUND = -5 - MPV_ERROR_OPTION_FORMAT = -6 - MPV_ERROR_OPTION_ERROR = -7 - MPV_ERROR_PROPERTY_NOT_FOUND = -8 - MPV_ERROR_PROPERTY_FORMAT = -9 - MPV_ERROR_PROPERTY_UNAVAILABLE = -10 - MPV_ERROR_PROPERTY_ERROR = -11 - MPV_ERROR_COMMAND = -12 - MPV_ERROR_LOADING_FAILED = -13 - MPV_ERROR_AO_INIT_FAILED = -14 - MPV_ERROR_VO_INIT_FAILED = -15 - MPV_ERROR_NOTHING_TO_PLAY = -16 - MPV_ERROR_UNKNOWN_FORMAT = -17 - MPV_ERROR_UNSUPPORTED = -18 - MPV_ERROR_NOT_IMPLEMENTED = -19 + MPV_ERROR_SUCCESS + MPV_ERROR_EVENT_QUEUE_FULL + MPV_ERROR_NOMEM + MPV_ERROR_UNINITIALIZED + MPV_ERROR_INVALID_PARAMETER + MPV_ERROR_OPTION_NOT_FOUND + MPV_ERROR_OPTION_FORMAT + MPV_ERROR_OPTION_ERROR + MPV_ERROR_PROPERTY_NOT_FOUND + MPV_ERROR_PROPERTY_FORMAT + MPV_ERROR_PROPERTY_UNAVAILABLE + MPV_ERROR_PROPERTY_ERROR + MPV_ERROR_COMMAND + MPV_ERROR_LOADING_FAILED + MPV_ERROR_AO_INIT_FAILED + MPV_ERROR_VO_INIT_FAILED + MPV_ERROR_NOTHING_TO_PLAY + MPV_ERROR_UNKNOWN_FORMAT + MPV_ERROR_UNSUPPORTED + MPV_ERROR_NOT_IMPLEMENTED const char *mpv_error_string(int error) nogil @@ -186,7 +186,7 @@ cdef extern from "mpv/client.h": int mpv_unobserve_property(mpv_handle *mpv, uint64_t registered_reply_userdata) nogil - cdef enum mpv_event_id: + enum mpv_event_id: MPV_EVENT_NONE MPV_EVENT_SHUTDOWN MPV_EVENT_LOG_MESSAGE @@ -219,15 +219,15 @@ cdef extern from "mpv/client.h": mpv_format format void *data - cdef enum mpv_log_level: - MPV_LOG_LEVEL_NONE = 0 - MPV_LOG_LEVEL_FATAL = 10 - MPV_LOG_LEVEL_ERROR = 20 - MPV_LOG_LEVEL_WARN = 30 - MPV_LOG_LEVEL_INFO = 40 - MPV_LOG_LEVEL_V = 50 - MPV_LOG_LEVEL_DEBUG = 60 - MPV_LOG_LEVEL_TRACE = 70 + enum mpv_log_level: + MPV_LOG_LEVEL_NONE + MPV_LOG_LEVEL_FATAL + MPV_LOG_LEVEL_ERROR + MPV_LOG_LEVEL_WARN + MPV_LOG_LEVEL_INFO + MPV_LOG_LEVEL_V + MPV_LOG_LEVEL_DEBUG + MPV_LOG_LEVEL_TRACE cdef struct mpv_event_log_message: const char *prefix @@ -235,11 +235,11 @@ cdef extern from "mpv/client.h": const char *text int log_level - cdef enum mpv_end_file_reason: - MPV_END_FILE_REASON_EOF = 0 - MPV_END_FILE_REASON_STOP = 2 - MPV_END_FILE_REASON_QUIT = 3 - MPV_END_FILE_REASON_ERROR = 4 + enum mpv_end_file_reason: + MPV_END_FILE_REASON_EOF + MPV_END_FILE_REASON_STOP + MPV_END_FILE_REASON_QUIT + MPV_END_FILE_REASON_ERROR cdef struct mpv_event_end_file: int reason From 91fe73b6009d8c97757322fa851786a9194c5459 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 23 Sep 2016 00:57:37 +0900 Subject: [PATCH 02/36] Fix support for UTF-8 strings in python2 --- mpv.pyx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mpv.pyx b/mpv.pyx index 14c4596..a2eff57 100644 --- a/mpv.pyx +++ b/mpv.pyx @@ -327,8 +327,9 @@ class MPVError(Exception): def __init__(self, e): self.code = e cdef const char* err_c - cdef int e_i = e - if not isinstance(e, str): + cdef int e_i + if not isinstance(e, basestring): + e_i = e with nogil: err_c = mpv_error_string(e_i) e = _strdec(err_c) @@ -439,7 +440,7 @@ cdef class Context(object): return err def _format_for(self, value): - if isinstance(value, str): + if isinstance(value, basestring): return MPV_FORMAT_STRING elif isinstance(value, bool): return MPV_FORMAT_FLAG From ff5ace123bdd15ac1e8e48b3e802b10448bf98d7 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Fri, 23 Sep 2016 00:58:35 +0900 Subject: [PATCH 03/36] Add support for the opengl-cb API --- client.pxd | 27 ++++++++++++++++++++ mpv.pyx | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/client.pxd b/client.pxd index c0b5d87..382b0e7 100644 --- a/client.pxd +++ b/client.pxd @@ -271,4 +271,31 @@ cdef extern from "mpv/client.h": int mpv_get_wakeup_pipe(mpv_handle *ctx) nogil + void mpv_wait_async_requests(mpv_handle *ctx) nogil + enum mpv_sub_api: + MPV_SUB_API_OPENGL_CB + + void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api) nogil + +cdef extern from "mpv/opengl_cb.h": + struct mpv_opengl_cb_context: + pass + + ctypedef void (*mpv_opengl_cb_update_fn)(void *cb_ctx) + ctypedef void *(*mpv_opengl_cb_get_proc_address_fn)(void *fn_ctx, + const char *name) nogil + + void mpv_opengl_cb_set_update_callback(mpv_opengl_cb_context *ctx, + mpv_opengl_cb_update_fn callback, + void *callback_ctx) nogil + + int mpv_opengl_cb_init_gl(mpv_opengl_cb_context *ctx, const char *exts, + mpv_opengl_cb_get_proc_address_fn get_proc_address, + void *get_proc_address_ctx) nogil + + int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int w, int h) nogil + + int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time) nogil + + int mpv_opengl_cb_uninit_gl(mpv_opengl_cb_context *ctx) nogil diff --git a/mpv.pyx b/mpv.pyx index a2eff57..4ba2ced 100644 --- a/mpv.pyx +++ b/mpv.pyx @@ -787,9 +787,83 @@ cdef class Context(object): self.reply_userdata = None self._ctx = NULL + def opengl_cb_api(self): + cdef void *cb + + _ctx = mpv_get_sub_api(self._ctx, MPV_SUB_API_OPENGL_CB) + if not _ctx: + raise MPVError("OpenGL API not available") + + ctx = OpenGLContext() + ctx._ctx = _ctx + + return ctx + def __dealloc__(self): self.shutdown() +cdef void *_c_getprocaddress(void *ctx, const char *name) with gil: + return (ctx)(name) + +cdef void _c_updatecb(void *ctx) with gil: + (ctx)() + +cdef class OpenGLContext(object): + cdef: + mpv_opengl_cb_context *_ctx + bint inited + object update_cb + + def __init__(self): + self.inited = False + + def init_gl(self, exts, get_proc_address): + exts = _strenc(exts) if exts is not None else None + cdef char* extsc = NULL + if exts is not None: + extsc = exts + with nogil: + err = mpv_opengl_cb_init_gl(self._ctx, extsc, &_c_getprocaddress, + get_proc_address) + if err < 0: + raise MPVError(err) + + self.inited = True + + def set_update_callback(self, cb): + self.update_cb = cb + with nogil: + mpv_opengl_cb_set_update_callback(self._ctx, &_c_updatecb, cb) + + def draw(self, fbo, w, h): + cdef: + int fboc = fbo + int wc = w + int hc = h + with nogil: + err = mpv_opengl_cb_draw(self._ctx, fboc, wc, hc) + if err < 0: + raise MPVError(err) + + def report_flip(self, time): + cdef int64_t ctime = time + with nogil: + err = mpv_opengl_cb_report_flip(self._ctx, ctime) + if err < 0: + raise MPVError(err) + + def uninit_gl(self): + if not self.inited: + return + with nogil: + err = mpv_opengl_cb_uninit_gl(self._ctx) + if err < 0: + raise MPVError(err) + self.inited = False + + def __dealloc__(self): + self.uninit_gl() + class CallbackThread(Thread): def __init__(self): Thread.__init__(self) From 060fb304640e7e8dc3e90b3066e6f393ea97587d Mon Sep 17 00:00:00 2001 From: Jakub Turski Date: Mon, 14 Nov 2016 13:35:24 +0000 Subject: [PATCH 04/36] Better setup file. --- setup.py | 43 ++++++++++++++++--------------------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/setup.py b/setup.py index 2e5f62f..c7ec7c6 100644 --- a/setup.py +++ b/setup.py @@ -13,36 +13,25 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import os -from subprocess import call -from distutils.core import setup -from distutils.extension import Extension -from distutils.command.clean import clean -from Cython.Distutils import build_ext -from Cython.Build import cythonize +from setuptools import setup, find_packages +from setuptools.extension import Extension -def tryremove(filename): - if not os.path.isfile(filename): - return - try: - os.remove(filename) - except OSError as e: - print(e) +try: + from Cython.Build import cythonize + USE_CYTHON = True +except ImportError: + USE_CYTHON = False -class Clean(clean): - side_effects = [ - "mpv.c", - ] +ext = 'pyx' if USE_CYTHON else 'c' +extensions=[ + Extension('mpv', ['mpv.%s' % ext], libraries=['mpv']), +] +if USE_CYTHON: + extensions=cythonize(extensions) - def run(self): - for f in self.side_effects: - tryremove(f) - clean.run(self) setup( - cmdclass = { - "build_ext": build_ext, - "clean": Clean, - }, - ext_modules = cythonize([Extension("mpv", ["mpv.pyx"], libraries=['mpv'])]) + name='pympv', + version='0.3.0', + ext_modules=extensions, ) From 60431276e877e8c4e386c570deb4e1a81a77d8e3 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Dec 2016 04:22:37 +0900 Subject: [PATCH 05/36] Add .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..378eac2 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build From 82c735f7eb98f3a6bb2e457ee560f89c70d11c93 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Dec 2016 04:17:40 +0900 Subject: [PATCH 06/36] Bump version to v0.4.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c7ec7c6..7571113 100644 --- a/setup.py +++ b/setup.py @@ -32,6 +32,6 @@ setup( name='pympv', - version='0.3.0', + version='0.4.0', ext_modules=extensions, ) From 4c7da263ad26a0cc3a016a9919d61f95ba754bc1 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Dec 2016 04:21:40 +0900 Subject: [PATCH 07/36] Update changelog for v0.4.0 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27f433a..965cf19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ pympv ===== +## 0.4.0 +- Fix string support in Python2 +- Add opengl-cb support +- Improve packaging/setup.py + ## 0.3.0 - Fix various failures of data and callbacks From 06c8ebc6998b0942ee5c667853a0e20eacde6cd2 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Dec 2016 05:14:02 +0900 Subject: [PATCH 08/36] Improve package metadata --- README.md | 2 +- setup.py | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d3c1f80..82c3846 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ pympv ===== A python wrapper for libmpv. -To use +To use:: import sys import mpv diff --git a/setup.py b/setup.py index 7571113..5c2a4f7 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import os from setuptools import setup, find_packages from setuptools.extension import Extension @@ -29,9 +30,28 @@ if USE_CYTHON: extensions=cythonize(extensions) +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() setup( name='pympv', version='0.4.0', - ext_modules=extensions, + description='Python bindings for the libmpv library', + # This is supposed to be reST. Cheating by using a common subset of + # reST and Markdown... + long_description=read('README.md'), + author='Andre D', + author_email='andre@andred.ca', + maintainer='Hector Martin', + maintainer_email='marcan@marcan.st', + url='https://github.com/marcan/pympv', + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Programming Language :: Cython', + 'Topic :: Multimedia :: Sound/Audio :: Players', + 'Topic :: Multimedia :: Video', + 'Topic :: Software Development :: Libraries', + 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)' + ], + ext_modules=extensions ) From 32380ba561d34c7055f6bf890fe50ff864c43e5c Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Dec 2016 05:14:33 +0900 Subject: [PATCH 09/36] Update .gitignore for setuptools stuff --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 378eac2..d4d1dd3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ build +pympv.egg-info +dist From 2a5401bce7f9c9308e37a39a7c32e86bdd2eaa44 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Dec 2016 05:15:24 +0900 Subject: [PATCH 10/36] Bump to v0.4.1 --- CHANGELOG.md | 4 ++++ setup.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 965cf19..813f041 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ pympv ===== +## 0.4.1 + +- Prepare packaging for PyPI + ## 0.4.0 - Fix string support in Python2 - Add opengl-cb support diff --git a/setup.py b/setup.py index 5c2a4f7..dbe8f67 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ def read(fname): setup( name='pympv', - version='0.4.0', + version='0.4.1', description='Python bindings for the libmpv library', # This is supposed to be reST. Cheating by using a common subset of # reST and Markdown... From d10c048f9073b75bf4702870a198a30a3dde1ad9 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 13 Dec 2016 05:37:54 +0900 Subject: [PATCH 11/36] Add authorship info to README Looks like PyPI takes the maintainer info from setup.py instead of the author info... --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 82c3846..8630554 100644 --- a/README.md +++ b/README.md @@ -43,3 +43,6 @@ To use:: libmpv is a client library for the media player mpv For more info see: https://github.com/mpv-player/mpv/blob/master/libmpv/client.h + +pympv was originally written by Andre D, and the PyPI package is maintained +by Hector Martin. From 07f27d13ddf604f4d80be94384e53883bff15eac Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 3 Sep 2018 01:28:08 +0900 Subject: [PATCH 12/36] Rename async -> asynchronous for python 3.7 compat. This is, unfortunately, a breaking change for users of that keyword argument. --- mpv.pyx | 10 +++++----- setup.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mpv.pyx b/mpv.pyx index 4ba2ced..0d012a1 100644 --- a/mpv.pyx +++ b/mpv.pyx @@ -518,7 +518,7 @@ cdef class Context(object): elif node.format == MPV_FORMAT_STRING: free(node.u.string) - def command(self, *cmdlist, async=False, data=None): + def command(self, *cmdlist, asynchronous=False, data=None): """Send a command to mpv. Non-async success returns the command's response data, otherwise None @@ -527,7 +527,7 @@ cdef class Context(object): Accepts parameters as args Keyword Arguments: - async: True will return right away, status comes in as MPV_EVENT_COMMAND_REPLY + asynchronous: True will return right away, status comes in as MPV_EVENT_COMMAND_REPLY data: Only valid if async, gets sent back as reply_userdata in the Event Wraps: mpv_command_node and mpv_command_node_async @@ -540,7 +540,7 @@ cdef class Context(object): result = None try: data_id = id(data) - if not async: + if not asynchronous: with nogil: err = mpv_command_node(self._ctx, &node, &noderesult) try: @@ -624,7 +624,7 @@ cdef class Context(object): return v @_errors - def set_property(self, prop, value=True, async=False, data=None): + def set_property(self, prop, value=True, asynchronous=False, data=None): """Wraps: mpv_set_property and mpv_set_property_async""" assert self._ctx prop = _strenc(prop) @@ -635,7 +635,7 @@ cdef class Context(object): cdef const char* prop_c try: prop_c = prop - if not async: + if not asynchronous: with nogil: err = mpv_set_property( self._ctx, diff --git a/setup.py b/setup.py index dbe8f67..5dc26a6 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ def read(fname): setup( name='pympv', - version='0.4.1', + version='0.5.0', description='Python bindings for the libmpv library', # This is supposed to be reST. Cheating by using a common subset of # reST and Markdown... From b1674c516023aae06eb3911fa0cf8dea93ad772f Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 3 Sep 2018 01:32:08 +0900 Subject: [PATCH 13/36] setup.py: Force Cython rebuilds if Cython is installed --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5dc26a6..a543fdc 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,7 @@ Extension('mpv', ['mpv.%s' % ext], libraries=['mpv']), ] if USE_CYTHON: - extensions=cythonize(extensions) + extensions=cythonize(extensions, force=True) def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() From cc6c70ab915b11488dbf91e9d5db310b68ff1f77 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Mon, 3 Sep 2018 03:11:00 +0900 Subject: [PATCH 14/36] Bump to 0.5.1 0.5.0 was released without updating the pre-cythonized C file, so fix that. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a543fdc..b696f77 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ def read(fname): setup( name='pympv', - version='0.5.0', + version='0.5.1', description='Python bindings for the libmpv library', # This is supposed to be reST. Cheating by using a common subset of # reST and Markdown... From cbc7193b6695b51359312bdd2ce3963fb5ddcc23 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 5 Sep 2018 02:03:24 +0900 Subject: [PATCH 15/36] Version 0.6.0: Add support for the render API and deprecate opengl-cb --- client.pxd | 97 +++++++++++++++++++- mpv.pyx | 258 ++++++++++++++++++++++++++++++++++++++++++++++++++++- setup.py | 2 +- 3 files changed, 353 insertions(+), 4 deletions(-) diff --git a/client.pxd b/client.pxd index 382b0e7..befe219 100644 --- a/client.pxd +++ b/client.pxd @@ -152,6 +152,10 @@ cdef extern from "mpv/client.h": mpv_node *values char **keys + cdef struct mpv_byte_array: + void *data + size_t size + void mpv_free_node_contents(mpv_node *node) nogil int mpv_set_option(mpv_handle *ctx, const char *name, mpv_format format, void *data) nogil @@ -282,7 +286,7 @@ cdef extern from "mpv/opengl_cb.h": struct mpv_opengl_cb_context: pass - ctypedef void (*mpv_opengl_cb_update_fn)(void *cb_ctx) + ctypedef void (*mpv_opengl_cb_update_fn)(void *cb_ctx) nogil ctypedef void *(*mpv_opengl_cb_get_proc_address_fn)(void *fn_ctx, const char *name) nogil @@ -299,3 +303,94 @@ cdef extern from "mpv/opengl_cb.h": int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time) nogil int mpv_opengl_cb_uninit_gl(mpv_opengl_cb_context *ctx) nogil + + +cdef extern from "mpv/render.h": + struct mpv_render_context: + pass + + enum mpv_render_param_type: + MPV_RENDER_PARAM_INVALID + MPV_RENDER_PARAM_API_TYPE + MPV_RENDER_PARAM_OPENGL_INIT_PARAMS + MPV_RENDER_PARAM_OPENGL_FBO + MPV_RENDER_PARAM_FLIP_Y + MPV_RENDER_PARAM_DEPTH + MPV_RENDER_PARAM_ICC_PROFILE + MPV_RENDER_PARAM_AMBIENT_LIGHT + MPV_RENDER_PARAM_X11_DISPLAY + MPV_RENDER_PARAM_WL_DISPLAY + MPV_RENDER_PARAM_ADVANCED_CONTROL + MPV_RENDER_PARAM_NEXT_FRAME_INFO + MPV_RENDER_PARAM_BLOCK_FOR_TARGET_TIME + MPV_RENDER_PARAM_SKIP_RENDERING + MPV_RENDER_PARAM_DRM_DISPLAY + MPV_RENDER_PARAM_DRM_OSD_SIZE + + char *MPV_RENDER_API_TYPE_OPENGL + + enum mpv_render_frame_info_flag: + MPV_RENDER_FRAME_INFO_PRESENT + MPV_RENDER_FRAME_INFO_REDRAW + MPV_RENDER_FRAME_INFO_REPEAT + MPV_RENDER_FRAME_INFO_BLOCK_VSYNC + + struct mpv_render_param: + mpv_render_param_type type + void *data + + struct mpv_render_frame_info: + uint64_t flags + int64_t target_time + + int mpv_render_context_create(mpv_render_context **res, mpv_handle *mpv, + mpv_render_param *params) nogil + + int mpv_render_context_set_parameter(mpv_render_context *ctx, + mpv_render_param param) nogil + + int mpv_render_context_get_info(mpv_render_context *ctx, + mpv_render_param param) nogil + + ctypedef void (*mpv_render_update_fn)(void *cb_ctx) nogil + + void mpv_render_context_set_update_callback(mpv_render_context *ctx, + mpv_render_update_fn callback, + void *callback_ctx) nogil + + uint64_t mpv_render_context_update(mpv_render_context *ctx) nogil + + enum mpv_render_update_flag: + MPV_RENDER_UPDATE_FRAME + + int mpv_render_context_render(mpv_render_context *ctx, mpv_render_param *params) nogil + + void mpv_render_context_report_swap(mpv_render_context *ctx) nogil + + void mpv_render_context_free(mpv_render_context *ctx) nogil + +cdef extern from "mpv/render_gl.h": + struct mpv_opengl_init_params: + void *(*get_proc_address)(void *ctx, const char *name) + void *get_proc_address_ctx + const char *extra_exts + + struct mpv_opengl_fbo: + int fbo + int w + int h + int internal_format + + struct _drmModeAtomicReq: + pass + + struct mpv_opengl_drm_params: + int fd + int crtc_id + int connector_id + _drmModeAtomicReq **atomic_request_ptr + int render_fd + + struct mpv_opengl_drm_osd_size: + int width + int height diff --git a/mpv.pyx b/mpv.pyx index 0d012a1..471e323 100644 --- a/mpv.pyx +++ b/mpv.pyx @@ -18,10 +18,12 @@ libmpv is a client library for the media player mpv For more info see: https://github.com/mpv-player/mpv/blob/master/libmpv/client.h """ -import sys +import cython +import sys, warnings from threading import Thread, Semaphore from libc.stdlib cimport malloc, free -from libc.string cimport strcpy +from libc.string cimport strcpy, strlen, memset +from cpython.pycapsule cimport PyCapsule_IsValid, PyCapsule_GetPointer from client cimport * @@ -335,6 +337,9 @@ class MPVError(Exception): e = _strdec(err_c) Exception.__init__(self, e) +class PyMPVError(Exception): + pass + cdef _callbacks = dict() cdef _reply_userdatas = dict() @@ -777,6 +782,8 @@ cdef class Context(object): return err def shutdown(self): + if self._ctx == NULL: + return cdef uint64_t ctxid = id(self) with nogil: mpv_terminate_destroy(self._ctx) @@ -816,6 +823,7 @@ cdef class OpenGLContext(object): def __init__(self): self.inited = False + warnings.warn("OpenGLContext is deprecated, please switch to RenderContext", DeprecationWarning) def init_gl(self, exts, get_proc_address): exts = _strenc(exts) if exts is not None else None @@ -864,6 +872,252 @@ cdef class OpenGLContext(object): def __dealloc__(self): self.uninit_gl() +DEF MAX_RENDER_PARAMS = 32 + +cdef class _RenderParams(object): + cdef: + mpv_render_param params[MAX_RENDER_PARAMS + 1] + object owned + + def __init__(self): + self.owned = [] + self.params[0].type = MPV_RENDER_PARAM_INVALID + + cdef add_voidp(self, mpv_render_param_type t, void *p, bint owned=False): + count = len(self.owned) + if count >= MAX_RENDER_PARAMS: + if owned: + free(p) + raise PyMPVError("RenderParams overflow") + + self.params[count].type = t + self.params[count].data = p + self.params[count + 1].type = MPV_RENDER_PARAM_INVALID + self.owned.append(owned) + + cdef add_int(self, mpv_render_param_type t, int val): + cdef int *p = malloc(sizeof(int)) + p[0] = val + self.add_voidp(t, p) + + cdef add_string(self, mpv_render_param_type t, char *s): + cdef char *p = malloc(strlen(s) + 1) + strcpy(p, s) + self.add_voidp(t, p) + + def __dealloc__(self): + for i, j in enumerate(self.owned): + if j: + free(self.params[i].data) + +cdef void *get_pointer(const char *name, object obj): + cdef void *p + if PyCapsule_IsValid(obj, name): + p = PyCapsule_GetPointer(obj, name) + elif isinstance(obj, int) or isinstance(obj, long) and obj: + p = obj + else: + raise PyMPVError("Unknown or invalid pointer object: %r" % obj) + return p + +@cython.internal +cdef class RenderFrameInfo(object): + cdef _from_struct(self, mpv_render_frame_info *info): + self.present = bool(info[0].flags & MPV_RENDER_FRAME_INFO_PRESENT) + self.redraw = bool(info[0].flags & MPV_RENDER_FRAME_INFO_REDRAW) + self.repeat = bool(info[0].flags & MPV_RENDER_FRAME_INFO_REPEAT) + self.block_vsync = bool(info[0].flags & MPV_RENDER_FRAME_INFO_BLOCK_VSYNC) + self.target_time = info[0].target_time + return self + +cdef class RenderContext(object): + API_OPENGL = "opengl" + UPDATE_FRAME = MPV_RENDER_UPDATE_FRAME + + cdef: + Context _mpv + mpv_render_context *_ctx + object update_cb + object _x11_display + object _wl_display + object _get_proc_address + bint inited + + def __init__(self, mpv, + api_type, + opengl_init_params=None, + advanced_control=False, + x11_display=None, + wl_display=None, + drm_display=None, + drm_osd_size=None + ): + + cdef: + mpv_opengl_init_params gl_params + mpv_opengl_drm_params drm_params + mpv_opengl_drm_osd_size _drm_osd_size + + self._mpv = mpv + + memset(&gl_params, 0, sizeof(gl_params)) + memset(&drm_params, 0, sizeof(drm_params)) + memset(&_drm_osd_size, 0, sizeof(_drm_osd_size)) + + params = _RenderParams() + + if api_type == self.API_OPENGL: + params.add_string(MPV_RENDER_PARAM_API_TYPE, MPV_RENDER_API_TYPE_OPENGL) + else: + raise PyMPVError("Unknown api_type %r" % api_type) + + if opengl_init_params is not None: + self._get_proc_address = opengl_init_params["get_proc_address"] + gl_params.get_proc_address = &_c_getprocaddress + gl_params.get_proc_address_ctx = self._get_proc_address + params.add_voidp(MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_params) + if advanced_control: + params.add_int(MPV_RENDER_PARAM_ADVANCED_CONTROL, 1) + if x11_display: + self._x11_display = x11_display + params.add_voidp(MPV_RENDER_PARAM_X11_DISPLAY, get_pointer("Display", x11_display)) + if wl_display: + self._wl_display = wl_display + params.add_voidp(MPV_RENDER_PARAM_WL_DISPLAY, get_pointer("wl_display", wl_display)) + if drm_display: + drm_params.fd = drm_display.get("fd", -1) + drm_params.crtc_id = drm_display.get("crtc_id", -1) + drm_params.connector_id = drm_display.get("connector_id", -1) + arp = drm_display.get("atomic_request_ptr", None) + if arp is not None: + drm_params.atomic_request_ptr = <_drmModeAtomicReq **>get_pointer(arp, "drmModeAtomicReq*") + drm_params.render_fd = drm_display.get("render_fd", -1) + params.add_voidp(MPV_RENDER_PARAM_DRM_DISPLAY, &drm_params) + if drm_osd_size: + _drm_osd_size.width, _drm_osd_size.height = drm_osd_size + params.add_voidp(MPV_RENDER_PARAM_DRM_OSD_SIZE, &_drm_osd_size) + + err = mpv_render_context_create(&self._ctx, self._mpv._ctx, params.params) + if err < 0: + raise MPVError(err) + self.inited = True + + @_errors + def set_icc_profile(self, icc_blob): + cdef: + mpv_render_param param + mpv_byte_array val + int err + + if not isinstance(icc_blob, bytes): + raise PyMPVError("icc_blob should be a bytes instance") + val.data = icc_blob + val.size = len(icc_blob) + + param.type = MPV_RENDER_PARAM_ICC_PROFILE + param.data = &val + + with nogil: + err = mpv_render_context_set_parameter(self._ctx, param) + return err + + @_errors + def set_ambient_light(self, lux): + cdef: + mpv_render_param param + int val + int err + + val = lux + param.type = MPV_RENDER_PARAM_AMBIENT_LIGHT + param.data = &val + + with nogil: + err = mpv_render_context_set_parameter(self._ctx, param) + return err + + def get_next_frame_info(self): + cdef: + mpv_render_frame_info info + mpv_render_param param + + param.type = MPV_RENDER_PARAM_NEXT_FRAME_INFO + param.data = &info + with nogil: + err = mpv_render_context_get_info(self._ctx, param) + if err < 0: + raise MPVError(err) + + return RenderFrameInfo()._from_struct(&info) + + def set_update_callback(self, cb): + with nogil: + mpv_render_context_set_update_callback(self._ctx, &_c_updatecb, cb) + self.update_cb = cb + + def update(self): + cdef uint64_t ret + with nogil: + ret = mpv_render_context_update(self._ctx) + return ret + + @_errors + def render(self, + opengl_fbo=None, + flip_y=False, + depth=None, + block_for_target_time=True, + skip_rendering=False): + + cdef: + mpv_opengl_fbo fbo + + params = _RenderParams() + + if opengl_fbo: + memset(&fbo, 0, sizeof(fbo)) + fbo.fbo = opengl_fbo.get("fbo", 0) or 0 + fbo.w = opengl_fbo["w"] + fbo.h = opengl_fbo["h"] + fbo.internal_format = opengl_fbo.get("internal_format", 0) or 0 + params.add_voidp(MPV_RENDER_PARAM_OPENGL_FBO, &fbo) + if flip_y: + params.add_int(MPV_RENDER_PARAM_FLIP_Y, 1) + if depth is not None: + params.add_int(MPV_RENDER_PARAM_DEPTH, depth) + if not block_for_target_time: + params.add_int(MPV_RENDER_PARAM_BLOCK_FOR_TARGET_TIME, 0) + if skip_rendering: + params.add_int(MPV_RENDER_PARAM_SKIP_RENDERING, 1) + + with nogil: + ret = mpv_render_context_render(self._ctx, params.params) + return ret + + def report_swap(self): + with nogil: + mpv_render_context_report_swap(self._ctx) + + def close(self): + if not self.inited: + return + with nogil: + mpv_render_context_free(self._ctx) + self.inited = False + + def __dealloc__(self): + self.close() + +cdef class OpenGLRenderContext(RenderContext): + def __init__(self, mpv, + get_proc_address, + *args, **kwargs): + init_params = { + "get_proc_address": get_proc_address + } + RenderContext.__init__(self, mpv, RenderContext.API_OPENGL, + init_params, *args, **kwargs) + class CallbackThread(Thread): def __init__(self): Thread.__init__(self) diff --git a/setup.py b/setup.py index b696f77..eac6203 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ def read(fname): setup( name='pympv', - version='0.5.1', + version='0.6.0', description='Python bindings for the libmpv library', # This is supposed to be reST. Cheating by using a common subset of # reST and Markdown... From a258e696e2fc4e8b98a7803723bc196d5aebe458 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 5 Sep 2018 02:05:56 +0900 Subject: [PATCH 16/36] Update CHANGELOG.md --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 813f041..1e23f1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ pympv ===== +## 0.6.0 +- Add support for the render API +- Deprecate the legacy opengl-cb API + +## 0.5.1 +- Rename async -> asynchronous for Python 3.7 compatibility +- Force Cython rebuilds if Cython is installed + ## 0.4.1 - Prepare packaging for PyPI From 8055feb5ce7b5f9122eb1e96d680821b007df878 Mon Sep 17 00:00:00 2001 From: Luni-4 Date: Tue, 30 Jun 2020 07:33:17 +0200 Subject: [PATCH 17/36] Deploy pympv on Windows and automate pypi uploads When a new pympv version is released, all libmpv binaries, in addition to the related .whl, are uploaded alongside with the release. --- .github/workflows/deploy.yml | 98 ++++++++++++++++++++++++++++++++++++ setup.py | 81 +++++++++++++++++++---------- 2 files changed, 152 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..54af549 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,98 @@ +name: deploy + +on: + push: + tags: + - 'v*.*.*' + +jobs: + + create-windows-wheel: + + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Download libmpv + run: | + $VERSION = "libmpv/mpv-dev-x86_64-20200524-git-685bd6a.7z" + $URL = "https://sourceforge.net/projects/mpv-player-windows/files" + curl -L -o libmpv.7z "$URL/$VERSION" + 7z x libmpv.7z + + - name: Set VS environment variables + run: | + $VsPath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer" + echo "::add-path::$VsPath" + + - name: Set MSVC x86_64 linker path + run: | + $LinkGlob = "VC\Tools\MSVC\*\bin\Hostx64\x64" + $LinkPath = vswhere -latest -products * -find "$LinkGlob" | + Select-Object -Last 1 + echo "::add-path::$LinkPath" + + - name: Create the libmpv zip + shell: cmd + run: | + lib /def:mpv.def /machine:x64 /out:mpv.lib + ren mpv-1.dll mpv.dll + ren include mpv + 7z a libmpv.zip mpv/ mpv.dll mpv.lib + + - name: Install Python 3.7 version + uses: actions/setup-python@v1 + with: + python-version: '3.7' + architecture: 'x64' + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel + pip install --upgrade cython + + - name: Create Python wheel + run: | + python setup.py bdist_wheel --plat-name win_amd64 --python-tag cp37 + + - name: Upload Windows wheel + uses: actions/upload-artifact@v2 + with: + name: dist + path: dist + + deploy-binaries: + + needs: create-windows-wheel + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Download Windows wheel + uses: actions/download-artifact@v2 + + - name: Install Python dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools twine + pip install --upgrade cython + + - name: Release source code + run: | + python setup.py sdist + + - name: Twine check + run: | + python -m twine check dist/* + + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.pypi_token }} diff --git a/setup.py b/setup.py index eac6203..46f35b3 100644 --- a/setup.py +++ b/setup.py @@ -14,44 +14,71 @@ # along with this program. If not, see . import os -from setuptools import setup, find_packages -from setuptools.extension import Extension +import sys +from glob import glob +from os.path import join + +from setuptools import Extension, find_packages, setup try: from Cython.Build import cythonize USE_CYTHON = True + extension_src = "mpv.pyx" except ImportError: USE_CYTHON = False + extension_src = "mpv.c" + +extra_data = {} +extensions = [Extension("mpv", [extension_src], libraries=["mpv"])] + +if set(["setup.py", "--version", "-V"]) >= set(sys.argv): + extensions = [] + +if set(["bdist_wheel", "--plat-name", "win_amd64"]) <= set(sys.argv): + extra_data["data_files"] = [ + ("Scripts", ["mpv.dll"]), + ("libs", ["mpv.lib"]), + ("include", glob("mpv/*")), + ] + extensions = [ + Extension( + "mpv", + [extension_src], + libraries=["mpv"], + library_dirs=[os.curdir], + include_dirs=[join(os.curdir, "mpv")], + ) + ] -ext = 'pyx' if USE_CYTHON else 'c' -extensions=[ - Extension('mpv', ['mpv.%s' % ext], libraries=['mpv']), -] if USE_CYTHON: - extensions=cythonize(extensions, force=True) + extensions = cythonize(extensions, force=True) + def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() + setup( - name='pympv', - version='0.6.0', - description='Python bindings for the libmpv library', - # This is supposed to be reST. Cheating by using a common subset of - # reST and Markdown... - long_description=read('README.md'), - author='Andre D', - author_email='andre@andred.ca', - maintainer='Hector Martin', - maintainer_email='marcan@marcan.st', - url='https://github.com/marcan/pympv', - classifiers=[ - 'Development Status :: 3 - Alpha', - 'Programming Language :: Cython', - 'Topic :: Multimedia :: Sound/Audio :: Players', - 'Topic :: Multimedia :: Video', - 'Topic :: Software Development :: Libraries', - 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)' - ], - ext_modules=extensions + name="pympv", + version="0.7.0", + description="Python bindings for the libmpv library", + # This is supposed to be reST. Cheating by using a common subset of + # reST and Markdown... + long_description=read("README.md"), + long_description_content_type="text/markdown", + author="Andre D", + author_email="andre@andred.ca", + maintainer="Hector Martin", + maintainer_email="marcan@marcan.st", + url="https://github.com/marcan/pympv", + classifiers=[ + "Development Status :: 3 - Alpha", + "Programming Language :: Cython", + "Topic :: Multimedia :: Sound/Audio :: Players", + "Topic :: Multimedia :: Video", + "Topic :: Software Development :: Libraries", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + ], + ext_modules=extensions, + **extra_data ) From ca6721b0b51556b969096b453c04a35451b13ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Thu, 2 Jan 2020 12:27:32 +0100 Subject: [PATCH 18/36] Add syntax highlighting on usage sample --- README.md | 73 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 8630554..f018e1a 100644 --- a/README.md +++ b/README.md @@ -3,42 +3,43 @@ pympv A python wrapper for libmpv. To use:: - - import sys - import mpv - - def main(args): - if len(args) != 1: - print('pass a single media file as argument') - return 1 - - try: - m = mpv.Context() - except mpv.MPVError: - print('failed creating context') - return 1 - - m.set_option('input-default-bindings') - m.set_option('osc') - m.set_option('input-vo-keyboard') - m.initialize() - - m.command('loadfile', args[0]) - - while True: - event = m.wait_event(.01) - if event.id == mpv.Events.none: - continue - print(event.name) - if event.id in [mpv.Events.end_file, mpv.Events.shutdown]: - break - - if __name__ == '__main__': - try: - exit(main(sys.argv[1:]) or 0) - except mpv.MPVError as e: - print(str(e)) - exit(1) +```python +import sys +import mpv + +def main(args): + if len(args) != 1: + print('pass a single media file as argument') + return 1 + + try: + m = mpv.Context() + except mpv.MPVError: + print('failed creating context') + return 1 + + m.set_option('input-default-bindings') + m.set_option('osc') + m.set_option('input-vo-keyboard') + m.initialize() + + m.command('loadfile', args[0]) + + while True: + event = m.wait_event(.01) + if event.id == mpv.Events.none: + continue + print(event.name) + if event.id in [mpv.Events.end_file, mpv.Events.shutdown]: + break + +if __name__ == '__main__': + try: + exit(main(sys.argv[1:]) or 0) + except mpv.MPVError as e: + print(str(e)) + exit(1) +``` libmpv is a client library for the media player mpv From 7256f64244e72855b608d80ebe439701689bc2e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Thu, 2 Jan 2020 12:29:36 +0100 Subject: [PATCH 19/36] Add PyGObject usage sample --- README.md | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f018e1a..c24f82b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ pympv ===== A python wrapper for libmpv. -To use:: +#### Basic usage ```python import sys import mpv @@ -41,6 +41,113 @@ if __name__ == '__main__': exit(1) ``` +#### PyGObject usage +```python +#!/usr/bin/env python3 +import ctypes +import sys +from ctypes import CFUNCTYPE, c_char_p, c_void_p + +import gi +import mpv + +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, Gdk + +from OpenGL import GL, GLX + + +class MainClass(Gtk.Window): + + def __init__(self, media): + super(MainClass, self).__init__() + self.media = media + self.set_default_size(600, 400) + self.connect("destroy", self.on_destroy) + + frame = Gtk.Frame() + self.area = OpenGlArea() + self.area.connect("realize", self.play) + frame.add(self.area) + self.add(frame) + self.show_all() + + def on_destroy(self, widget, data=None): + Gtk.main_quit() + + def play(self, arg1): + self.area.play(self.media) + + +class OpenGlArea(Gtk.GLArea): + + def __init__(self, **properties): + super().__init__(**properties) + + self.gl_context: mpv.OpenGLRenderContext = None + + try: + self.mpv = mpv.Context() + self.mpv.initialize() + except mpv.MPVError: + raise RuntimeError('failed creating context') + + # self.mpv.set_property("gpu-context", "wayland") + self.mpv.set_property("terminal", True) + + self.connect("realize", self.on_realize) + self.connect("render", self.on_render) + self.connect("unrealize", self.on_unrealize) + + # noinspection PyUnusedLocal + def on_realize(self, area: Gtk.GLArea): + self.make_current() + self.gl_context = mpv.OpenGLRenderContext(self.mpv, get_process_address) + self.gl_context.set_update_callback(self.queue_render) + + def on_unrealize(self, arg): + if self.gl_context: + self.gl_context.close() + self.mpv.shutdown() + + def on_render(self, arg1, arg2): + factor = self.get_scale_factor() + rect: Gdk.Rectangle = self.get_allocated_size()[0] + + if self.gl_context: + fbo = { + "fbo": GL.glGetIntegerv(GL.GL_DRAW_FRAMEBUFFER_BINDING), + "w": rect.width * factor, + "h": rect.height * factor, + } + self.gl_context.render(opengl_fbo=fbo, flip_y=True) + + return True + + def play(self, media): + self.mpv.command('loadfile', media) + + +@CFUNCTYPE(c_void_p, c_char_p) +def get_process_address(name): + address = GLX.glXGetProcAddress(name.decode("utf-8")) + return ctypes.cast(address, ctypes.c_void_p).value + + +if __name__ == '__main__': + args = sys.argv[1:] + + if len(args) == 1: + import locale + + locale.setlocale(locale.LC_NUMERIC, 'C') + + application = MainClass(args[0]) + Gtk.main() + else: + print('pass a single media file as argument and try again') +``` + libmpv is a client library for the media player mpv For more info see: https://github.com/mpv-player/mpv/blob/master/libmpv/client.h From e10c64069e83e6f2dc69733674cb9fb65784b24a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20M=C3=BCller?= Date: Fri, 19 Jun 2020 10:09:46 +0200 Subject: [PATCH 20/36] Extract examples into its own directory --- README.md | 108 +-------------------------------------- samples/basic.py | 51 +++++++++++++++++++ samples/pygobject.py | 117 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 106 deletions(-) create mode 100644 samples/basic.py create mode 100755 samples/pygobject.py diff --git a/README.md b/README.md index c24f82b..df100d2 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ def main(args): if event.id in [mpv.Events.end_file, mpv.Events.shutdown]: break + if __name__ == '__main__': try: exit(main(sys.argv[1:]) or 0) @@ -41,112 +42,7 @@ if __name__ == '__main__': exit(1) ``` -#### PyGObject usage -```python -#!/usr/bin/env python3 -import ctypes -import sys -from ctypes import CFUNCTYPE, c_char_p, c_void_p - -import gi -import mpv - -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, Gdk - -from OpenGL import GL, GLX - - -class MainClass(Gtk.Window): - - def __init__(self, media): - super(MainClass, self).__init__() - self.media = media - self.set_default_size(600, 400) - self.connect("destroy", self.on_destroy) - - frame = Gtk.Frame() - self.area = OpenGlArea() - self.area.connect("realize", self.play) - frame.add(self.area) - self.add(frame) - self.show_all() - - def on_destroy(self, widget, data=None): - Gtk.main_quit() - - def play(self, arg1): - self.area.play(self.media) - - -class OpenGlArea(Gtk.GLArea): - - def __init__(self, **properties): - super().__init__(**properties) - - self.gl_context: mpv.OpenGLRenderContext = None - - try: - self.mpv = mpv.Context() - self.mpv.initialize() - except mpv.MPVError: - raise RuntimeError('failed creating context') - - # self.mpv.set_property("gpu-context", "wayland") - self.mpv.set_property("terminal", True) - - self.connect("realize", self.on_realize) - self.connect("render", self.on_render) - self.connect("unrealize", self.on_unrealize) - - # noinspection PyUnusedLocal - def on_realize(self, area: Gtk.GLArea): - self.make_current() - self.gl_context = mpv.OpenGLRenderContext(self.mpv, get_process_address) - self.gl_context.set_update_callback(self.queue_render) - - def on_unrealize(self, arg): - if self.gl_context: - self.gl_context.close() - self.mpv.shutdown() - - def on_render(self, arg1, arg2): - factor = self.get_scale_factor() - rect: Gdk.Rectangle = self.get_allocated_size()[0] - - if self.gl_context: - fbo = { - "fbo": GL.glGetIntegerv(GL.GL_DRAW_FRAMEBUFFER_BINDING), - "w": rect.width * factor, - "h": rect.height * factor, - } - self.gl_context.render(opengl_fbo=fbo, flip_y=True) - - return True - - def play(self, media): - self.mpv.command('loadfile', media) - - -@CFUNCTYPE(c_void_p, c_char_p) -def get_process_address(name): - address = GLX.glXGetProcAddress(name.decode("utf-8")) - return ctypes.cast(address, ctypes.c_void_p).value - - -if __name__ == '__main__': - args = sys.argv[1:] - - if len(args) == 1: - import locale - - locale.setlocale(locale.LC_NUMERIC, 'C') - - application = MainClass(args[0]) - Gtk.main() - else: - print('pass a single media file as argument and try again') -``` +More examples can be found in the [samples](samples) directory. libmpv is a client library for the media player mpv diff --git a/samples/basic.py b/samples/basic.py new file mode 100644 index 0000000..3008b4a --- /dev/null +++ b/samples/basic.py @@ -0,0 +1,51 @@ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import sys + +import mpv + + +def main(args): + if len(args) != 1: + print('pass a single media file as argument') + return 1 + + try: + m = mpv.Context() + except mpv.MPVError: + print('failed creating context') + return 1 + + m.set_option('input-default-bindings') + m.set_option('osc') + m.set_option('input-vo-keyboard') + m.initialize() + + m.command('loadfile', args[0]) + + while True: + event = m.wait_event(.01) + if event.id == mpv.Events.none: + continue + print(event.name) + if event.id in [mpv.Events.end_file, mpv.Events.shutdown]: + break + + +if __name__ == '__main__': + try: + exit(main(sys.argv[1:]) or 0) + except mpv.MPVError as e: + print(str(e)) + exit(1) diff --git a/samples/pygobject.py b/samples/pygobject.py new file mode 100755 index 0000000..7b697f0 --- /dev/null +++ b/samples/pygobject.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python3 + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import ctypes +import sys +from ctypes import CFUNCTYPE, c_char_p, c_void_p + +import gi +import mpv + +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, Gdk + +from OpenGL import GL, GLX + + +class MainClass(Gtk.Window): + + def __init__(self, media): + super(MainClass, self).__init__() + self.media = media + self.set_default_size(600, 400) + self.connect("destroy", self.on_destroy) + + frame = Gtk.Frame() + self.area = OpenGlArea() + self.area.connect("realize", self.play) + frame.add(self.area) + self.add(frame) + self.show_all() + + def on_destroy(self, widget, data=None): + Gtk.main_quit() + + def play(self, arg1): + self.area.play(self.media) + + +class OpenGlArea(Gtk.GLArea): + + def __init__(self, **properties): + super().__init__(**properties) + + self.gl_context: mpv.OpenGLRenderContext = None + + try: + self.mpv = mpv.Context() + self.mpv.initialize() + except mpv.MPVError: + raise RuntimeError('failed creating context') + + # self.mpv.set_property("gpu-context", "wayland") + self.mpv.set_property("terminal", True) + + self.connect("realize", self.on_realize) + self.connect("render", self.on_render) + self.connect("unrealize", self.on_unrealize) + + # noinspection PyUnusedLocal + def on_realize(self, area: Gtk.GLArea): + self.make_current() + self.gl_context = mpv.OpenGLRenderContext(self.mpv, get_process_address) + self.gl_context.set_update_callback(self.queue_render) + + def on_unrealize(self, arg): + if self.gl_context: + self.gl_context.close() + self.mpv.shutdown() + + def on_render(self, arg1, arg2): + factor = self.get_scale_factor() + rect: Gdk.Rectangle = self.get_allocated_size()[0] + + if self.gl_context: + fbo = { + "fbo": GL.glGetIntegerv(GL.GL_DRAW_FRAMEBUFFER_BINDING), + "w": rect.width * factor, + "h": rect.height * factor, + } + self.gl_context.render(opengl_fbo=fbo, flip_y=True) + + return True + + def play(self, media): + self.mpv.command('loadfile', media) + + +@CFUNCTYPE(c_void_p, c_char_p) +def get_process_address(name): + address = GLX.glXGetProcAddress(name.decode("utf-8")) + return ctypes.cast(address, ctypes.c_void_p).value + + +if __name__ == '__main__': + args = sys.argv[1:] + + if len(args) == 1: + import locale + + locale.setlocale(locale.LC_NUMERIC, 'C') + + application = MainClass(args[0]) + Gtk.main() + else: + print('pass a single media file as argument and try again') From 6099c67168b4af39297281802d651f33914caf11 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Tue, 30 Jun 2020 15:03:59 +0900 Subject: [PATCH 21/36] deploy: add branch to deploy to TestPyPi --- .github/workflows/deploy.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 54af549..df95f96 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,6 +4,8 @@ on: push: tags: - 'v*.*.*' + branches: + - 'publish-test' jobs: @@ -92,7 +94,16 @@ jobs: python -m twine check dist/* - name: Publish distribution to PyPI + if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@master with: user: __token__ password: ${{ secrets.pypi_token }} + + - name: Publish distribution to TestPyPI + if: github.event_name == 'push' && github.ref == 'refs/heads/publish-test' + uses: pypa/gh-action-pypi-publish@master + with: + user: __token__ + password: ${{ secrets.testpypi_token }} + repository_url: https://test.pypi.org/legacy/ From 6b32a839f777b625635581d806886e473f991b2d Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 22 Jun 2022 12:39:24 +0900 Subject: [PATCH 22/36] setup.py: Don't break pypi builds when Cython is installed We don't ship mpv.pyx, so just check for it. Signed-off-by: Hector Martin --- setup.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 46f35b3..05198f0 100644 --- a/setup.py +++ b/setup.py @@ -20,13 +20,16 @@ from setuptools import Extension, find_packages, setup -try: - from Cython.Build import cythonize - USE_CYTHON = True - extension_src = "mpv.pyx" -except ImportError: - USE_CYTHON = False - extension_src = "mpv.c" +USE_CYTHON = False +extension_src = "mpv.c" + +if os.path.exists("mpv.pyx"): + try: + from Cython.Build import cythonize + USE_CYTHON = True + extension_src = "mpv.pyx" + except ImportError: + pass extra_data = {} extensions = [Extension("mpv", [extension_src], libraries=["mpv"])] From 5afae999ddcb573296392c9c0d355313a46d4200 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 22 Jun 2022 12:41:53 +0900 Subject: [PATCH 23/36] Bump to v0.7.1 Signed-off-by: Hector Martin --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 05198f0..f2876c2 100644 --- a/setup.py +++ b/setup.py @@ -63,7 +63,7 @@ def read(fname): setup( name="pympv", - version="0.7.0", + version="0.7.1", description="Python bindings for the libmpv library", # This is supposed to be reST. Cheating by using a common subset of # reST and Markdown... From fc410554fa89b2264c84c352c119058515f30264 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 22 Jun 2022 12:47:12 +0900 Subject: [PATCH 24/36] deploy.yml: Fix Windows action Signed-off-by: Hector Martin --- .github/workflows/deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index df95f96..6ead75f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -27,14 +27,14 @@ jobs: - name: Set VS environment variables run: | $VsPath = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer" - echo "::add-path::$VsPath" + echo "$VsPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - name: Set MSVC x86_64 linker path run: | $LinkGlob = "VC\Tools\MSVC\*\bin\Hostx64\x64" $LinkPath = vswhere -latest -products * -find "$LinkGlob" | Select-Object -Last 1 - echo "::add-path::$LinkPath" + echo "$LinkPath" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - name: Create the libmpv zip shell: cmd From a680e85001cc54e667d711d9397b614fc96c1b73 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 26 Nov 2020 23:27:10 +0100 Subject: [PATCH 25/36] Simplify string conversions, since python2 is no more --- mpv.pyx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/mpv.pyx b/mpv.pyx index 471e323..8cef63d 100644 --- a/mpv.pyx +++ b/mpv.pyx @@ -49,23 +49,13 @@ if _CAPI_MAJOR != _REQUIRED_CAPI_MAJOR or _CAPI_MINOR < _MIN_CAPI_MINOR: cdef extern from "Python.h": void PyEval_InitThreads() -_is_py3 = sys.version_info >= (3,) -_strdec_err = "surrogateescape" if _is_py3 else "strict" # mpv -> Python def _strdec(s): - try: - return s.decode("utf-8", _strdec_err) - except UnicodeDecodeError: - # In python2, bail to bytes on failure - return bytes(s) + return s.decode("utf-8", "surrogateescape") # Python -> mpv def _strenc(s): - try: - return s.encode("utf-8", _strdec_err) - except UnicodeEncodeError: - # In python2, assume bytes and walk right through - return s + return s.encode("utf-8", "surrogateescape") PyEval_InitThreads() From 86ed671c8efbe4b5c5e51a23cdc207901e7bd014 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 26 Nov 2020 23:57:01 +0100 Subject: [PATCH 26/36] =?UTF-8?q?Give=20str=20its=20Python=C2=A03=20meanin?= =?UTF-8?q?g?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cython still defaults to aliasing it to bytes for compatibility with Python 2, but we don’t need that anymore. --- mpv.pyx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mpv.pyx b/mpv.pyx index 8cef63d..c353493 100644 --- a/mpv.pyx +++ b/mpv.pyx @@ -1,3 +1,5 @@ +# cython: language_level=3 + # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or From 8fe1a917d526b0352857f3f8f127b162aece037c Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 26 Nov 2020 23:36:06 +0100 Subject: [PATCH 27/36] Optimise string conversion functions with Cython MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since these functions don’t have to be exposed to the Python world, we can make them as cheap as a call to the CPython API. --- mpv.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mpv.pyx b/mpv.pyx index c353493..c929234 100644 --- a/mpv.pyx +++ b/mpv.pyx @@ -52,11 +52,11 @@ cdef extern from "Python.h": void PyEval_InitThreads() # mpv -> Python -def _strdec(s): +cdef str _strdec(bytes s): return s.decode("utf-8", "surrogateescape") # Python -> mpv -def _strenc(s): +cdef bytes _strenc(str s): return s.encode("utf-8", "surrogateescape") PyEval_InitThreads() From 2035943490ef4da5769365413d5d5d5594d2b9b8 Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Thu, 26 Nov 2020 23:38:19 +0100 Subject: [PATCH 28/36] Add a note to remove PyEval_InitThreads() after 3.6 is EOL This function is automatically called by Py_Initialize() since 3.7, and will be removed in 3.11. --- mpv.pyx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mpv.pyx b/mpv.pyx index c929234..09e8bde 100644 --- a/mpv.pyx +++ b/mpv.pyx @@ -48,9 +48,6 @@ if _CAPI_MAJOR != _REQUIRED_CAPI_MAJOR or _CAPI_MINOR < _MIN_CAPI_MINOR: (_REQUIRED_CAPI_MAJOR, _MIN_CAPI_MINOR, _CAPI_MAJOR, _CAPI_MINOR) ) -cdef extern from "Python.h": - void PyEval_InitThreads() - # mpv -> Python cdef str _strdec(bytes s): return s.decode("utf-8", "surrogateescape") @@ -59,6 +56,10 @@ cdef str _strdec(bytes s): cdef bytes _strenc(str s): return s.encode("utf-8", "surrogateescape") +# TODO: Remove this call once Python 3.6 is EOL: it is automatically called by +# Py_Initialize() since Python 3.7 and will be removed in Python 3.11. +cdef extern from "Python.h": + void PyEval_InitThreads() PyEval_InitThreads() class Errors: From 20444193bc5e16f1d8f070010b9ead32967368cf Mon Sep 17 00:00:00 2001 From: Emmanuel Gil Peyrot Date: Fri, 27 Nov 2020 00:13:19 +0100 Subject: [PATCH 29/36] Actually type the two dicts --- mpv.pyx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mpv.pyx b/mpv.pyx index 09e8bde..604a088 100644 --- a/mpv.pyx +++ b/mpv.pyx @@ -293,7 +293,7 @@ cdef class Event(object): return _strdec(name_c) cdef _init(self, mpv_event* event, ctx): - cdef uint64_t ctxid = id(ctx) + ctxid = id(ctx) self.id = event.event_id self.data = self._data(event) userdata = _reply_userdatas[ctxid].get(event.reply_userdata, None) @@ -333,8 +333,8 @@ class MPVError(Exception): class PyMPVError(Exception): pass -cdef _callbacks = dict() -cdef _reply_userdatas = dict() +cdef dict _callbacks = {} +cdef dict _reply_userdatas = {} class _ReplyUserData(object): def __init__(self, data): @@ -723,7 +723,7 @@ cdef class Context(object): return pipe def __cinit__(self): - cdef uint64_t ctxid = id(self) + ctxid = id(self) with nogil: self._ctx = mpv_create() if not self._ctx: @@ -777,7 +777,7 @@ cdef class Context(object): def shutdown(self): if self._ctx == NULL: return - cdef uint64_t ctxid = id(self) + ctxid = id(self) with nogil: mpv_terminate_destroy(self._ctx) self.callbackthread.shutdown() From 449f0c738b41252c947aa18566cc5bacc0364b5a Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Wed, 22 Jun 2022 12:53:03 +0900 Subject: [PATCH 30/36] setup.py: Bump to 0.8.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f2876c2..17f0dbe 100644 --- a/setup.py +++ b/setup.py @@ -63,7 +63,7 @@ def read(fname): setup( name="pympv", - version="0.7.1", + version="0.8.0", description="Python bindings for the libmpv library", # This is supposed to be reST. Cheating by using a common subset of # reST and Markdown... From dc2f2f0c94d59d860dd66a65129deb7eea85c4e5 Mon Sep 17 00:00:00 2001 From: Mia Herkt Date: Wed, 14 Dec 2022 23:51:38 +0100 Subject: [PATCH 31/36] Update to libmpv API 2.0 --- client.pxd | 47 ++++----------------- mpv.pyx | 119 ++++------------------------------------------------- 2 files changed, 16 insertions(+), 150 deletions(-) diff --git a/client.pxd b/client.pxd index befe219..08fb4d0 100644 --- a/client.pxd +++ b/client.pxd @@ -106,16 +106,10 @@ cdef extern from "mpv/client.h": int mpv_initialize(mpv_handle *ctx) nogil - void mpv_detach_destroy(mpv_handle *ctx) nogil - void mpv_terminate_destroy(mpv_handle *ctx) nogil int mpv_load_config_file(mpv_handle *ctx, const char *filename) nogil - void mpv_suspend(mpv_handle *ctx) nogil - - void mpv_resume(mpv_handle *ctx) nogil - int64_t mpv_get_time_us(mpv_handle *ctx) nogil cdef enum mpv_format: @@ -200,21 +194,14 @@ cdef extern from "mpv/client.h": MPV_EVENT_START_FILE MPV_EVENT_END_FILE MPV_EVENT_FILE_LOADED - MPV_EVENT_TRACKS_CHANGED - MPV_EVENT_TRACK_SWITCHED MPV_EVENT_IDLE - MPV_EVENT_PAUSE - MPV_EVENT_UNPAUSE MPV_EVENT_TICK - MPV_EVENT_SCRIPT_INPUT_DISPATCH MPV_EVENT_CLIENT_MESSAGE MPV_EVENT_VIDEO_RECONFIG MPV_EVENT_AUDIO_RECONFIG - MPV_EVENT_METADATA_UPDATE MPV_EVENT_SEEK MPV_EVENT_PLAYBACK_RESTART MPV_EVENT_PROPERTY_CHANGE - MPV_EVENT_CHAPTER_CHANGE const char *mpv_event_name(mpv_event_id event) nogil @@ -282,29 +269,6 @@ cdef extern from "mpv/client.h": void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api) nogil -cdef extern from "mpv/opengl_cb.h": - struct mpv_opengl_cb_context: - pass - - ctypedef void (*mpv_opengl_cb_update_fn)(void *cb_ctx) nogil - ctypedef void *(*mpv_opengl_cb_get_proc_address_fn)(void *fn_ctx, - const char *name) nogil - - void mpv_opengl_cb_set_update_callback(mpv_opengl_cb_context *ctx, - mpv_opengl_cb_update_fn callback, - void *callback_ctx) nogil - - int mpv_opengl_cb_init_gl(mpv_opengl_cb_context *ctx, const char *exts, - mpv_opengl_cb_get_proc_address_fn get_proc_address, - void *get_proc_address_ctx) nogil - - int mpv_opengl_cb_draw(mpv_opengl_cb_context *ctx, int fbo, int w, int h) nogil - - int mpv_opengl_cb_report_flip(mpv_opengl_cb_context *ctx, int64_t time) nogil - - int mpv_opengl_cb_uninit_gl(mpv_opengl_cb_context *ctx) nogil - - cdef extern from "mpv/render.h": struct mpv_render_context: pass @@ -325,9 +289,15 @@ cdef extern from "mpv/render.h": MPV_RENDER_PARAM_BLOCK_FOR_TARGET_TIME MPV_RENDER_PARAM_SKIP_RENDERING MPV_RENDER_PARAM_DRM_DISPLAY - MPV_RENDER_PARAM_DRM_OSD_SIZE + MPV_RENDER_PARAM_DRM_DRAW_SURFACE_SIZE + MPV_RENDER_PARAM_DRM_DISPLAY_V2 + MPV_RENDER_PARAM_SW_SIZE + MPV_RENDER_PARAM_SW_FORMAT + MPV_RENDER_PARAM_SW_STRIDE + MPV_RENDER_PARAM_SW_POINTER char *MPV_RENDER_API_TYPE_OPENGL + char *MPV_RENDER_API_TYPE_SW enum mpv_render_frame_info_flag: MPV_RENDER_FRAME_INFO_PRESENT @@ -373,7 +343,6 @@ cdef extern from "mpv/render_gl.h": struct mpv_opengl_init_params: void *(*get_proc_address)(void *ctx, const char *name) void *get_proc_address_ctx - const char *extra_exts struct mpv_opengl_fbo: int fbo @@ -391,6 +360,6 @@ cdef extern from "mpv/render_gl.h": _drmModeAtomicReq **atomic_request_ptr int render_fd - struct mpv_opengl_drm_osd_size: + struct mpv_opengl_drm_draw_surface_size: int width int height diff --git a/mpv.pyx b/mpv.pyx index 604a088..dd4f6c1 100644 --- a/mpv.pyx +++ b/mpv.pyx @@ -32,8 +32,8 @@ from client cimport * __version__ = "0.3.0" __author__ = "Andre D" -_REQUIRED_CAPI_MAJOR = 1 -_MIN_CAPI_MINOR = 9 +_REQUIRED_CAPI_MAJOR = 2 +_MIN_CAPI_MINOR = 0 cdef unsigned long _CAPI_VERSION with nogil: @@ -107,21 +107,14 @@ class Events: start_file = MPV_EVENT_START_FILE end_file = MPV_EVENT_END_FILE file_loaded = MPV_EVENT_FILE_LOADED - tracks_changed = MPV_EVENT_TRACKS_CHANGED - tracks_switched = MPV_EVENT_TRACK_SWITCHED idle = MPV_EVENT_IDLE - pause = MPV_EVENT_PAUSE - unpause = MPV_EVENT_UNPAUSE tick = MPV_EVENT_TICK - script_input_dispatch = MPV_EVENT_SCRIPT_INPUT_DISPATCH client_message = MPV_EVENT_CLIENT_MESSAGE video_reconfig = MPV_EVENT_VIDEO_RECONFIG audio_reconfig = MPV_EVENT_AUDIO_RECONFIG - metadata_update = MPV_EVENT_METADATA_UPDATE seek = MPV_EVENT_SEEK playback_restart = MPV_EVENT_PLAYBACK_RESTART property_change = MPV_EVENT_PROPERTY_CHANGE - chapter_change = MPV_EVENT_CHAPTER_CHANGE class LogLevels: @@ -159,19 +152,6 @@ cdef class EndOfFileReached(object): return self -cdef class InputDispatch(object): - """Data field for MPV_EVENT_SCRIPT_INPUT_DISPATCH events. - - Wraps: mpv_event_script_input_dispatch - """ - cdef public object arg0, type - - cdef _init(self, mpv_event_script_input_dispatch* input): - self.arg0 = input.arg0 - self.type = _strdec(input.type) - return self - - cdef class LogMessage(object): """Data field for MPV_EVENT_LOG_MESSAGE events. @@ -269,8 +249,6 @@ cdef class Event(object): return Property()._init(data) elif self.id == MPV_EVENT_LOG_MESSAGE: return LogMessage()._init(data) - elif self.id == MPV_EVENT_SCRIPT_INPUT_DISPATCH: - return InputDispatch()._init(data) elif self.id == MPV_EVENT_CLIENT_MESSAGE: climsg = data args = [] @@ -385,18 +363,6 @@ cdef class Context(object): time = mpv_get_time_us(self._ctx) return time - def suspend(self): - """Wraps: mpv_suspend""" - assert self._ctx - with nogil: - mpv_suspend(self._ctx) - - def resume(self): - """Wraps: mpv_resume""" - assert self._ctx - with nogil: - mpv_resume(self._ctx) - @_errors def request_event(self, event, enable): """Enable or disable a given event. @@ -787,18 +753,6 @@ cdef class Context(object): self.reply_userdata = None self._ctx = NULL - def opengl_cb_api(self): - cdef void *cb - - _ctx = mpv_get_sub_api(self._ctx, MPV_SUB_API_OPENGL_CB) - if not _ctx: - raise MPVError("OpenGL API not available") - - ctx = OpenGLContext() - ctx._ctx = _ctx - - return ctx - def __dealloc__(self): self.shutdown() @@ -808,63 +762,6 @@ cdef void *_c_getprocaddress(void *ctx, const char *name) with gil: cdef void _c_updatecb(void *ctx) with gil: (ctx)() -cdef class OpenGLContext(object): - cdef: - mpv_opengl_cb_context *_ctx - bint inited - object update_cb - - def __init__(self): - self.inited = False - warnings.warn("OpenGLContext is deprecated, please switch to RenderContext", DeprecationWarning) - - def init_gl(self, exts, get_proc_address): - exts = _strenc(exts) if exts is not None else None - cdef char* extsc = NULL - if exts is not None: - extsc = exts - with nogil: - err = mpv_opengl_cb_init_gl(self._ctx, extsc, &_c_getprocaddress, - get_proc_address) - if err < 0: - raise MPVError(err) - - self.inited = True - - def set_update_callback(self, cb): - self.update_cb = cb - with nogil: - mpv_opengl_cb_set_update_callback(self._ctx, &_c_updatecb, cb) - - def draw(self, fbo, w, h): - cdef: - int fboc = fbo - int wc = w - int hc = h - with nogil: - err = mpv_opengl_cb_draw(self._ctx, fboc, wc, hc) - if err < 0: - raise MPVError(err) - - def report_flip(self, time): - cdef int64_t ctime = time - with nogil: - err = mpv_opengl_cb_report_flip(self._ctx, ctime) - if err < 0: - raise MPVError(err) - - def uninit_gl(self): - if not self.inited: - return - with nogil: - err = mpv_opengl_cb_uninit_gl(self._ctx) - if err < 0: - raise MPVError(err) - self.inited = False - - def __dealloc__(self): - self.uninit_gl() - DEF MAX_RENDER_PARAMS = 32 cdef class _RenderParams(object): @@ -943,19 +840,19 @@ cdef class RenderContext(object): x11_display=None, wl_display=None, drm_display=None, - drm_osd_size=None + drm_draw_surface_size=None ): cdef: mpv_opengl_init_params gl_params mpv_opengl_drm_params drm_params - mpv_opengl_drm_osd_size _drm_osd_size + mpv_opengl_drm_draw_surface_size _drm_draw_surface_size self._mpv = mpv memset(&gl_params, 0, sizeof(gl_params)) memset(&drm_params, 0, sizeof(drm_params)) - memset(&_drm_osd_size, 0, sizeof(_drm_osd_size)) + memset(&_drm_draw_surface_size, 0, sizeof(_drm_draw_surface_size)) params = _RenderParams() @@ -986,9 +883,9 @@ cdef class RenderContext(object): drm_params.atomic_request_ptr = <_drmModeAtomicReq **>get_pointer(arp, "drmModeAtomicReq*") drm_params.render_fd = drm_display.get("render_fd", -1) params.add_voidp(MPV_RENDER_PARAM_DRM_DISPLAY, &drm_params) - if drm_osd_size: - _drm_osd_size.width, _drm_osd_size.height = drm_osd_size - params.add_voidp(MPV_RENDER_PARAM_DRM_OSD_SIZE, &_drm_osd_size) + if drm_draw_surface_size: + _drm_draw_surface_size.width, _drm_draw_surface_size.height = drm_draw_surface_size + params.add_voidp(MPV_RENDER_PARAM_DRM_DRAW_SURFACE_SIZE, &_drm_draw_surface_size) err = mpv_render_context_create(&self._ctx, self._mpv._ctx, params.params) if err < 0: From a971cf477348f1188b0433b250a0aa513eb4db08 Mon Sep 17 00:00:00 2001 From: Mia Herkt Date: Thu, 15 Dec 2022 00:45:58 +0100 Subject: [PATCH 32/36] Update CHANGELOG.md --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e23f1a..33cb710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ pympv ===== +## 0.8.0 +- Update to libmpv API 2.0, removing previously deprecated APIs +- Optimise string conversion functions with Cython +- Give str its Python 3 meaning +- Simplify string conversions, since python2 is no more + +## 0.7.1 +- Extract examples into its own directory + ## 0.6.0 - Add support for the render API - Deprecate the legacy opengl-cb API From 7006e035d348a71d39c2572d45f6a4f73fd58379 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sat, 26 Jul 2025 21:09:16 +0900 Subject: [PATCH 33/36] Bump to 0.9.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 17f0dbe..8401508 100644 --- a/setup.py +++ b/setup.py @@ -63,7 +63,7 @@ def read(fname): setup( name="pympv", - version="0.8.0", + version="0.9.0", description="Python bindings for the libmpv library", # This is supposed to be reST. Cheating by using a common subset of # reST and Markdown... From 6cc3d8f36c2b895c2af099862556c96f7f43b50e Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sat, 26 Jul 2025 21:17:19 +0900 Subject: [PATCH 34/36] mpv.pyx: Add missing noexcept tags --- mpv.pyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mpv.pyx b/mpv.pyx index dd4f6c1..d96e082 100644 --- a/mpv.pyx +++ b/mpv.pyx @@ -756,10 +756,10 @@ cdef class Context(object): def __dealloc__(self): self.shutdown() -cdef void *_c_getprocaddress(void *ctx, const char *name) with gil: +cdef void *_c_getprocaddress(void *ctx, const char *name) noexcept with gil: return (ctx)(name) -cdef void _c_updatecb(void *ctx) with gil: +cdef void _c_updatecb(void *ctx) noexcept with gil: (ctx)() DEF MAX_RENDER_PARAMS = 32 @@ -1039,7 +1039,7 @@ class CallbackThread(Thread): except Exception as e: sys.stderr.write("pympv error during callback: %s\n" % e) -cdef void _c_callback(void* d) with gil: +cdef void _c_callback(void* d) noexcept with gil: cdef uint64_t name = d callback = _callbacks.get(name) callback.call() From 36edc7f416cbd6f980dc92d6bc374494b3435016 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sat, 26 Jul 2025 21:19:22 +0900 Subject: [PATCH 35/36] mpv.pyx: Remove obsolete "long" check --- mpv.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mpv.pyx b/mpv.pyx index d96e082..6f428a9 100644 --- a/mpv.pyx +++ b/mpv.pyx @@ -804,7 +804,7 @@ cdef void *get_pointer(const char *name, object obj): cdef void *p if PyCapsule_IsValid(obj, name): p = PyCapsule_GetPointer(obj, name) - elif isinstance(obj, int) or isinstance(obj, long) and obj: + elif isinstance(obj, int) and obj: p = obj else: raise PyMPVError("Unknown or invalid pointer object: %r" % obj) From 85c77878122d1cd9a72d614adf52ba2be2fa8396 Mon Sep 17 00:00:00 2001 From: Hector Martin Date: Sat, 26 Jul 2025 21:11:05 +0900 Subject: [PATCH 36/36] deploy.yml: Update --- .github/workflows/deploy.yml | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6ead75f..3d13aca 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -44,10 +44,10 @@ jobs: ren include mpv 7z a libmpv.zip mpv/ mpv.dll mpv.lib - - name: Install Python 3.7 version + - name: Install Python 3.9 version uses: actions/setup-python@v1 with: - python-version: '3.7' + python-version: '3.9' architecture: 'x64' - name: Install Python dependencies @@ -61,13 +61,16 @@ jobs: python setup.py bdist_wheel --plat-name win_amd64 --python-tag cp37 - name: Upload Windows wheel - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: dist path: dist deploy-binaries: + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + needs: create-windows-wheel runs-on: ubuntu-latest @@ -77,13 +80,14 @@ jobs: uses: actions/checkout@v2 - name: Download Windows wheel - uses: actions/download-artifact@v2 + uses: actions/download-artifact@v4 - name: Install Python dependencies run: | python -m pip install --upgrade pip pip install setuptools twine pip install --upgrade cython + pip install --upgrade packaging - name: Release source code run: | @@ -95,15 +99,10 @@ jobs: - name: Publish distribution to PyPI if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.pypi_token }} + uses: pypa/gh-action-pypi-publish@release/v1 - name: Publish distribution to TestPyPI if: github.event_name == 'push' && github.ref == 'refs/heads/publish-test' - uses: pypa/gh-action-pypi-publish@master + uses: pypa/gh-action-pypi-publish@release/v1 with: - user: __token__ - password: ${{ secrets.testpypi_token }} repository_url: https://test.pypi.org/legacy/