Skip to content

Python-mpv

python-mpv는 mpv 미디어 플레이어에 대한 ctypes 기반 파이썬 인터페이스입니다.

lua 인터페이스와 마찬가지로 플레이어의 모든 기능을 거의 완벽하게 제어할 수 있습니다.

GLFW를 통해 OpenGL 내부의 imgui에서 mpv 사용

mpv를 사용하여 비디오를 GLFW 내부의 OpenGL 컨텍스트에서 실행되는 imgui UI 로 렌더링하는 데모 를 작성했습니다.

imgui/OpenGL 과 통합하는 방법과 속성에 액세스하고 MPV 인스턴스의 수명 주기를 관리하는 방법을 알아보려면 데모를 확인하세요.

해당 코드:

# -*- coding: utf-8 -*-
import glfw
import OpenGL.GL as gl

import imgui

from imgui.integrations.glfw import GlfwRenderer

import ctypes
from mpv import MPV, MpvRenderContext, OpenGlCbGetProcAddrFn

class VideoPlayer:

  def terminate(self):
    self.ctx.free()
    self.mpv.terminate()

  def __init__(self,filename):
    self.filename = filename
    self.open = True

    self.fbo = gl.glGenFramebuffers(1)
    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.fbo)

    self.texture = gl.glGenTextures(1)
    gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture)

    gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
    gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)

    gl.glFramebufferTexture2D(gl.GL_FRAMEBUFFER, gl.GL_COLOR_ATTACHMENT0, gl.GL_TEXTURE_2D, self.texture, 0)

    gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGB, 100, 100, 0, gl.GL_RGB, gl.GL_UNSIGNED_BYTE, None);

    gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
    gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)

    self.playbackPos = (0,)
    self.volume = (0,)
    self.loop='inf'

    self.mpv = MPV(log_handler=print, loglevel='debug')

    def get_process_address(_, name):
      print(name)
      address = glfw.get_proc_address(name.decode('utf8'))
      return ctypes.cast(address, ctypes.c_void_p).value

    proc_addr_wrapper = OpenGlCbGetProcAddrFn(get_process_address)

    self.ctx = MpvRenderContext(self.mpv, 'opengl', opengl_init_params={'get_proc_address': proc_addr_wrapper})

    self.mpv.play(self.filename)
    self.mpv.volume=0


  def render(self):

    videowindow, self.open = imgui.begin("Video window {}".format(self.filename), self.open, flags=imgui.WINDOW_NO_SCROLLBAR)

    w,h = imgui.get_window_size()

    if imgui.APPEARING:
        imgui.set_window_size(400, 300)

    w,h = imgui.core.get_content_region_available()
    w=int(max(w,0))
    h=int(max(h-85,0))

    if not self.open:
      imgui.end()
      self.terminate()
      return

    if self.ctx.update() and w>0 and h>0:


      gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.fbo)
      gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture);

      gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGB, w, h, 0, gl.GL_RGB, gl.GL_UNSIGNED_BYTE, None);

      gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
      gl.glBindTexture(gl.GL_TEXTURE_2D, 0);

      self.ctx.render(flip_y=False, opengl_fbo={'w': w, 'h': h, 'fbo': self.fbo})

    try:
      imgui.text("Filename: {} fbo: {} tex: {}".format(self.mpv.filename,self.fbo,self.texture))
    except:
      imgui.text("Filename: {} fbo: {} tex: {}".format(self.mpv.filename,self.fbo,self.texture))

    try:
      imgui.text("{0:.2f}s/{1:.2f}s ({2:.2f}s remaining)".format(self.mpv.time_pos,self.mpv.duration,self.mpv.playtime_remaining))
    except:
      imgui.text("Loading...")

    imgui.image(self.texture, w,h )


    imgui.push_item_width(-1)

    changed, values = imgui.slider_float(
        "##Playback Percentage", *self.playbackPos,
        min_value=0.0, max_value=100.0,
        format="Playback Percentage %.0f",
        power=1.0
    )

    if changed and values:
      try:
        self.mpv.command('seek',values,'absolute-percent')
      except:
        pass
      self.playbackPos = (values,)
    elif self.mpv.percent_pos:
      self.playbackPos = (self.mpv.percent_pos,)

    changed, values = imgui.slider_float(
        "##Volume", *self.volume,
        min_value=0.0, max_value=100.0,
        format="Volume %.0f",
        power=1.0
    )

    if changed:
      self.mpv.volume = values
      self.volume = (values,)
    elif self.mpv.volume:
      self.volume = (self.mpv.volume,)


    imgui.end()

def main():
    imgui.create_context()
    window = impl_glfw_init()
    impl = GlfwRenderer(window)
    _   = gl.glGenFramebuffers(1)

    videoWindows     = []

    def dropFile(window,files):
      for file in files:
        videoWindows.append(VideoPlayer(file))

    glfw.set_drop_callback(window, dropFile)

    while not glfw.window_should_close(window):

        glfw.poll_events()
        impl.process_inputs()

        imgui.new_frame()

        videoWindows = [x for x in videoWindows if x.open]
        if len(videoWindows) > 0:
          for videoWindow in videoWindows:
            videoWindow.render()
        else:
          imgui.core.set_next_window_position(10,10)
          winw,winh = glfw.get_window_size(window)
          imgui.core.set_next_window_size(winw-20,10)

          imgui.begin("##Message", True, flags=imgui.WINDOW_NO_SCROLLBAR|imgui.WINDOW_NO_RESIZE|imgui.WINDOW_NO_TITLE_BAR|imgui.WINDOW_NO_MOVE )
          imgui.text("Drop one or more video files onto this window to play")
          imgui.end()

        gl.glClearColor(0., 0., 0., 1.)
        gl.glClear(gl.GL_COLOR_BUFFER_BIT)

        imgui.render()
        impl.render(imgui.get_draw_data())
        glfw.swap_buffers(window)


    for videoWindow in videoWindows:
      videoWindow.terminate()
    impl.shutdown()
    glfw.terminate()


def impl_glfw_init():
    width, height = 1280, 720
    window_name = "minimal ImGui/GLFW3/pythonMPV example"

    if not glfw.init():
        print("Could not initialize OpenGL context")
        exit(1)

    # OS X supports only forward-compatible core profiles from 3.2
    glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 3)
    glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 3)
    glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)

    glfw.window_hint(glfw.OPENGL_FORWARD_COMPAT, gl.GL_TRUE)

    # Create a windowed mode window and its OpenGL context
    window = glfw.create_window(
        int(width), int(height), window_name, None, None
    )
    glfw.make_context_current(window)

    if not window:
        glfw.terminate()
        print("Could not initialize Window")
        exit(1)

    return window

if __name__ == "__main__":
    main()

pygame 으로 적용하기

import os
os.environ["SDL_VIDEO_X11_FORCE_EGL"] = "1"

from imgui.integrations.pygame import PygameRenderer
import OpenGL.GL as gl
import pygame
import imgui
import sys
import ctypes
import mpv

DLL = None

def get_process_address(_, name):
    print(f"get_process_address({_}, {name})")
    name = str(name, encoding="utf-8")
    address = getattr(DLL, name, None)
    if address is None:
        return 0
    print(f"get_process_address->result-> {type(address)} {address}")
    # return address
    c_addr = ctypes.cast(address, ctypes.c_void_p).value
    return c_addr




class VideoPlayer:

    def terminate(self):
        self.ctx.free()
        self.mpv.terminate()

    def __init__(self, filename):
        self.filename = filename
        self.open = True

        self.fbo = gl.glGenFramebuffers(1)
        gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.fbo)

        self.texture = gl.glGenTextures(1)
        gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture)

        gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
        gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)

        gl.glFramebufferTexture2D(
            gl.GL_FRAMEBUFFER,
            gl.GL_COLOR_ATTACHMENT0,
            gl.GL_TEXTURE_2D,
            self.texture,
            0,
        )

        gl.glTexImage2D(
            gl.GL_TEXTURE_2D,
            0,
            gl.GL_RGB,
            100,
            100,
            0,
            gl.GL_RGB,
            gl.GL_UNSIGNED_BYTE,
            None,
        )

        gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
        gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)

        self.playbackPos = (0,)
        self.volume = (0,)
        self.loop = "inf"

        self.mpv = mpv.MPV(log_handler=print, loglevel="debug")

        proc_addr_wrapper = mpv.MpvGlGetProcAddressFn(get_process_address)

        self.ctx = mpv.MpvRenderContext(
            self.mpv,
            "opengl",
            opengl_init_params={"get_proc_address": proc_addr_wrapper},
        )

        self.mpv.play(self.filename)
        self.mpv.volume = 0

    def render(self):
        videowindow, self.open = imgui.begin(
            "Video window {}".format(self.filename),
            self.open,
            flags=imgui.WINDOW_NO_SCROLLBAR,
        )

        w, h = imgui.get_window_size()

        if imgui.APPEARING:
            imgui.set_window_size(400, 300)

        w, h = imgui.core.get_content_region_available()
        w = int(max(w, 0))
        h = int(max(h - 85, 0))

        if not self.open:
            imgui.end()
            self.terminate()
            return

        if self.ctx.update() and w > 0 and h > 0:

            gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self.fbo)
            gl.glBindTexture(gl.GL_TEXTURE_2D, self.texture)

            gl.glTexImage2D(
                gl.GL_TEXTURE_2D,
                0,
                gl.GL_RGB,
                w,
                h,
                0,
                gl.GL_RGB,
                gl.GL_UNSIGNED_BYTE,
                None,
            )

            gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
            gl.glBindTexture(gl.GL_TEXTURE_2D, 0)

            self.ctx.render(flip_y=False, opengl_fbo={"w": w, "h": h, "fbo": self.fbo})

        try:
            imgui.text(
                "Filename: {} fbo: {} tex: {}".format(
                    self.mpv.filename, self.fbo, self.texture
                )
            )
        except:
            imgui.text(
                "Filename: {} fbo: {} tex: {}".format(
                    self.mpv.filename, self.fbo, self.texture
                )
            )

        try:
            imgui.text(
                "{0:.2f}s/{1:.2f}s ({2:.2f}s remaining)".format(
                    self.mpv.time_pos, self.mpv.duration, self.mpv.playtime_remaining
                )
            )
        except:
            imgui.text("Loading...")

        imgui.image(self.texture, w, h)

        imgui.push_item_width(-1)

        changed, values = imgui.slider_float(
            "##Playback Percentage",
            *self.playbackPos,
            min_value=0.0,
            max_value=100.0,
            format="Playback Percentage %.0f",
            power=1.0
        )

        if changed and values:
            try:
                self.mpv.command("seek", values, "absolute-percent")
            except:
                pass
            self.playbackPos = (values,)
        elif self.mpv.percent_pos:
            self.playbackPos = (self.mpv.percent_pos,)

        changed, values = imgui.slider_float(
            "##Volume",
            *self.volume,
            min_value=0.0,
            max_value=100.0,
            format="Volume %.0f",
            power=1.0
        )

        if changed:
            self.mpv.volume = values
            self.volume = (values,)
        elif self.mpv.volume:
            self.volume = (self.mpv.volume,)

        imgui.end()


def main():
    pygame.init()
    size = 800, 600

    pygame.display.set_mode(size, pygame.DOUBLEBUF | pygame.OPENGL | pygame.RESIZABLE)

    imgui.create_context()
    impl = PygameRenderer()

    io = imgui.get_io()
    io.display_size = size

    show_custom_window = True

    global DLL
    DLL = gl.glGetString.DLL
    videoWindows = [VideoPlayer("/home/your/Downloads/test.mp4")]

    while 1:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit(0)
            impl.process_event(event)
        impl.process_inputs()

        imgui.new_frame()

        if imgui.begin_main_menu_bar():
            if imgui.begin_menu("File", True):

                clicked_quit, selected_quit = imgui.menu_item(
                    "Quit", "Cmd+Q", False, True
                )

                if clicked_quit:
                    sys.exit(0)

                imgui.end_menu()
            imgui.end_main_menu_bar()

        imgui.show_test_window()

        if show_custom_window:
            is_expand, show_custom_window = imgui.begin("Custom window", True)
            if is_expand:
                imgui.text("Bar")
                imgui.text_colored("Eggs", 0.2, 1.0, 0.0)
            imgui.end()

        videoWindows = [x for x in videoWindows if x.open]

        for videoWindow in videoWindows:
            videoWindow.render()

        # note: cannot use screen.fill((1, 1, 1)) because pygame's screen
        #       does not support fill() on OpenGL sufraces
        gl.glClearColor(1, 1, 1, 1)
        gl.glClear(gl.GL_COLOR_BUFFER_BIT)
        imgui.render()
        impl.render(imgui.get_draw_data())

        pygame.display.flip()

    for videoWindow in videoWindows:
        videoWindow.terminate()


if __name__ == "__main__":
    main()

multiple instances

See also

Favorite site