Skip to content

Instantly share code, notes, and snippets.

@trin94
Last active October 13, 2025 11:14
Show Gist options
  • Save trin94/3381395adc8b2c3fea81a38b9a385369 to your computer and use it in GitHub Desktop.
Save trin94/3381395adc8b2c3fea81a38b9a385369 to your computer and use it in GitHub Desktop.
python-mpv + GTK 4

Instructions

  1. Set up a new Python project based on the following pyproject.toml:
    [project]
    name = "python-mpv-gtk4"
    version = "0.1.0"
    description = "Description"
    readme = "README.md"
    requires-python = ">=3.13"
    dependencies = [
        "mpv>=1.0.8",
        "numpy>=2.3.3",
        "PyGObject>=3.54.3",
        "PyOpenGl>=3.1.10",
    ]
  2. Prepare a video
  3. Copy the Python code and change the line pointing to the video
  4. Run the script

Additional Notes

  • If installing PyGObject fails make sure to have cairo dependencies installed:
    • On Fedora: sudo dnf install cairo-devel cairo-gobject-devel
import ctypes
import sys
import gi
gi.require_version("Gtk", "4.0")
from gi.repository import GLib, Gtk
from mpv import MPV, MpvGlGetProcAddressFn, MpvRenderContext
from OpenGL import GL
class MyApplication(Gtk.Application):
def __init__(self):
super().__init__(application_id="org.example.App")
self.renderer = MyRenderer()
self.renderer.connect("realize", self.on_renderer_ready)
def on_renderer_ready(self, *_):
self.renderer.play("test.webm")
def do_activate(self):
win = self.props.active_window
if not win:
win = Gtk.ApplicationWindow(application=self)
win.set_default_size(1280, 720)
win.set_child(self.renderer)
win.present()
class MyRenderer(Gtk.GLArea):
def __init__(self, **properties):
super().__init__(**properties)
self.set_auto_render(False)
self.connect("realize", self.on_realize)
self._mpv = MPV(vo="libmpv", keep_open="yes")
self._ctx = None
self._ctx_opengl_params = {"get_proc_address": MpvGlGetProcAddressFn(get_proc_address_wrapper())}
def on_realize(self, *_):
self.make_current()
self._ctx = MpvRenderContext(self._mpv, "opengl", opengl_init_params=self._ctx_opengl_params)
self._ctx.update_cb = self.on_mpv_callback
def on_mpv_callback(self):
GLib.idle_add(self.call_frame_ready, None, GLib.PRIORITY_HIGH)
def call_frame_ready(self, *_):
if self._ctx.update():
self.queue_render()
def do_render(self, *_):
if not self._ctx:
return False
factor = self.get_scale_factor()
width = self.get_width() * factor
height = self.get_height() * factor
fbo = GL.glGetIntegerv(GL.GL_DRAW_FRAMEBUFFER_BINDING)
self._ctx.render(flip_y=True, opengl_fbo={"w": width, "h": height, "fbo": fbo})
return True
def play(self, file):
self._mpv.play(file)
def get_proc_address_wrapper():
def glx_impl(name: bytes):
from OpenGL import GLX
return GLX.glXGetProcAddress(name.decode("utf-8"))
def egl_impl(name: bytes):
from OpenGL import EGL
return EGL.eglGetProcAddress(name.decode("utf-8"))
platform_func = None
try:
from OpenGL import GLX
platform_func = glx_impl
except (AttributeError, ImportError):
pass
if platform_func is None:
try:
from OpenGL import EGL
platform_func = egl_impl
except (AttributeError, ImportError):
pass
if platform_func is None:
raise RuntimeError("Cannot initialize OpenGL")
def wrapper(_, name: bytes):
address = platform_func(name)
return ctypes.cast(address, ctypes.c_void_p).value
return wrapper
if __name__ == "__main__":
sys.exit(MyApplication().run(sys.argv))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment