oaubert / python-vlc

Python vlc bindings
GNU Lesser General Public License v2.1
387 stars 111 forks source link

Wrong video size on MacOS #217

Open Neraste opened 2 years ago

Neraste commented 2 years ago

Hello,

When using this lib on MacOS with the simplified code below, inspired from examples/tkvlc.py, the size of the video is not adapted to the size of the window. If the video is too large for the window, it is cropped, if it is too small, it stands in the left bottom corner and is surrounded by black. The size of the video is updated only when the window is resized. Here is a video capture of the problem.

import os
import platform
import sys
import tkinter
from ctypes import c_void_p, cdll
from threading import Thread

import vlc

system = platform.system()

if system == "Darwin":
    # find the accurate Tk lib for Mac
    libtk = "libtk%s.dylib" % (tkinter.TkVersion,)
    if "TK_LIBRARY_PATH" in os.environ:
        libtk = os.path.join(os.environ["TK_LIBRARY_PATH"], libtk)
    else:
        prefix = getattr(sys, "base_prefix", sys.prefix)
        libtk = os.path.join(prefix, "lib", libtk)
    dylib = cdll.LoadLibrary(libtk)
    _GetNSView = dylib.TkMacOSXGetRootControl
    _GetNSView.restype = c_void_p
    _GetNSView.argtypes = (c_void_p,)
    del dylib

class Window(tkinter.Tk):
    def register(self, player):
        id = self.winfo_id()
        print(id)

        if system == "Darwin":
            player.set_nsobject(_GetNSView(id))
        elif system == "Linux":
            player.set_xwindow(id)
        elif system == "Windows":
            player.set_hwnd(id)

def play(instance, player, path):
    media = instance.media_new_path(path)
    player.set_media(media)
    player.play()

if __name__ == "__main__":
    instance = vlc.Instance()
    player = instance.media_player_new()
    window = Window()
    window.register(player)
    thread = Thread(target=play, args=(instance, player, sys.argv[1]))
    thread.start()
    window.mainloop()

This does not occur on Linux or on Window, and occurs with either Tkinter or PyCocoa. I guess the problem stands on the Python-VLC or the VLC side. I tried several instance parameters:

mrJean1 commented 2 years ago

The Window class needs to handle some Tk events: at least add a OnResize method. Following is a modified Window class. This may not be sufficient, more may be needed.

class Window(tkinter.Tk):
    _geometry = ''

    def register(self, player):
        wid = self.winfo_id()
        print(wid)

        if system == "Darwin":
            player.set_nsobject(_GetNSView(wid))
            self.player = player
            self.after(200, self.OnResize)
        elif system == "Linux":
            player.set_xwindow(wid)
        elif system == "Windows":
            player.set_hwnd(wid)

    def OnResize(self, *unused):
        """Adjust the window/frame to the video aspect ratio.
        """
        g = self.geometry()
        if g != self._geometry and self.player:
            u, v = self.player.video_get_size()  # often (0, 0)
            if v > 0 and u > 0:
                # get window size and position
                g, x, y = g.split('+')
                w, h = g.split('x')
                # alternatively, use .winfo_...
                # w = self.winfo_width()
                # h = self.winfo_height()
                # x = self.winfo_x()
                # y = self.winfo_y()
                # use the video aspect ratio ...
                if u > v:  # ... for landscape
                    # adjust the window height
                    h = round(float(w) * v / u)
                else:  # ... for portrait
                    # adjust the window width
                    w = round(float(h) * u / v)
                self.geometry("%sx%s+%s+%s" % (w, h, x, y))
                self._geometry = self.geometry()  # actual
Neraste commented 2 years ago

I saw this "trick" in examples/tkvlc.py, to force resize the window, and thus force the upscale. I don't really need to keep the video aspect ratio, so I tried to fool VLC by setting the same window geometry self.geometry(self.geometry()), but it didn't work.

This leaves me with two questions, however:

mrJean1 commented 2 years ago

Several things changed on macOS, the past few years. To make this issue work like it does on other systems, Tcl/Tk needs some updates for macOS.

Another example is the _ GetNSView kludge to get to the Drawable View. Tcl/Tk should make TkMacOSXDrawableView public.

Conversely, VLC could provide those things consistently across all platforms it supports.

PS) Setting the geometry to the cuurent geometry is a NOP. Try setting the geometry to something else, like the size of the video or half of that.

PPS) Try making the window a subclass of Tk.Frame instead of Tk.

PPPS) Another problem may be running the video in a thread. That is OK for playing the video, but not for events like key strokes, window resizing, window closing, etc.

Neraste commented 2 years ago

I see. So there a possibility this problem will be solved in the long run.

From what I have read from Tk source code, TkMacOSXGetRootControl takes as argument (and Tk.winfo_id() returns) the pointer of the window itself, which is, in my humble experience, very low-level for a Python program.

PS) Setting the geometry to the cuurent geometry is a NOP. Try setting the geometry to something else, like the size of the video or half of that.

I was feeling it'd be too easy.

PPS) Try making the window a subclass of Tk.Frame instead of Tk.

What would it change? My program has a unique window.

PPPS) Another problem may be running the video in a thread. That is OK for playing the video, but not for events like key strokes, window resizing, window closing, etc.

With Cocoa, it seems the window must run in the main thread on Mac. I get systematic errors otherwise.

mrJean1 commented 2 years ago

The point was, if the OnResize method is not getting called, some other changes may be needed. Two examples were the PPS and PPPS above. There may be more or other things.

To avoid those black areas, just add another self.after(200, self.OnResize) call at the end of the OnResize method. That will work in any thread.