0

I'm trying to create a video player similar to the looks of the default GUI for mpv. I'm using a QGraphicsVideoItem inside a QGraphicsView along with a custom ControlBar widget as the OSC.
I want the OSC to be 100px high and video.width()px wide, and always flush with the bottom edge of the QGraphicsView widget. I can't seem to do either of those requirements.
MRE:

from PySide6 import QtWidgets as qtw
from PySide6 import QtGui as qtg
from PySide6 import QtCore as qtc
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw

class ControlBar(qtw.QWidget):
    def __init__(self, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        self.setStyleSheet("background: red")

class View(qtw.QGraphicsView):
    def __init__(self) -> None:
        super().__init__()
        self.setMouseTracking(True)
        self.setRenderHints(qtg.QPainter.RenderHint.SmoothPixmapTransform | qtg.QPainter.RenderHint.Antialiasing)
        self.setViewportMargins(-2, -2, -2, -2) # `QGraphicsView` has hard coded margins.
        self.setFrameStyle(qtw.QFrame.Shape.NoFrame)

        self._scene = qtw.QGraphicsScene()
        self._video_item = qtmw.QGraphicsVideoItem()
        self._control_bar = ControlBar()
        self._media_player = qtm.QMediaPlayer()

        self._scene.addItem(self._video_item)
        self._proxy_control_bar = self._scene.addWidget(self._control_bar)
        self._proxy_control_bar.setFlag(qtw.QGraphicsItem.GraphicsItemFlag.ItemIgnoresTransformations)
        self.setScene(self._scene)

        self._media_player.setVideoOutput(self._video_item)
        self._media_player.setSource("video")
        self._media_player.mediaStatusChanged.connect(self._media_player.play)

    def showEvent(self, event) -> None:
        qtc.QTimer.singleShot(100, lambda: self.fitInView(self._video_item, qtc.Qt.AspectRatioMode.KeepAspectRatio))

    def resizeEvent(self, event) -> None:
        self._proxy_control_bar.setGeometry(0, 0, self.viewport().width(), 100)
        pos = qtc.QPoint(0, self.height() - self._proxy_control_bar.size().height())
        self._proxy_control_bar.setPos(0, self.mapToScene(pos).y())
        self.fitInView(self._video_item, qtc.Qt.AspectRatioMode.KeepAspectRatio)

app = qtw.QApplication()
view = View()
view.show()
app.exec()

I've been able to set the height of the widget to 100px, but using control_area.setGeometry(..., ..., self.viewport().width(), ...) sets the width to be a bit more than the video's width. And, for some reason, adding self._control_bar to the scene creates all this extra empty space around the two items, I have no idea why.

My questions are,

  1. is there no way to get the actual size (specifically the width) of the video item after a fitInView call?
    Because calling item.size() even after a fitInView call just returns the original size of the item, which I guess makes sense since only the view's view of the item was "fit in view" and the item itself is still the same.
  2. How do I set the position of the control_bar to be where I want it to?
    As seen in one of the videos below, the way I'm doing it right now does not accomplish it at all.
  3. What's up with all the extra empty space?

How it looks:

  1. Video with self._proxy_control_bar lines left in.
  2. Video with self._proxy_control_bar lines commented out.
Neat
  • 61
  • 1
  • 5
  • why did you chose QGraphicsView for this task? – mugiseyebrows Oct 06 '22 at 12:32
  • @mugiseyebrows I tried doing this with `QVideoWidget` but according to the 5th comment in [this](https://stackoverflow.com/questions/73853232/qt-cant-create-floating-widget-with-qvideowidget-as-parent) other post I made (and my personal experience with the widget), `QVideoWidget` will always be at the top, hence the OSC (or any OSD) could not appear over the playing video. `QGraphicsView` so I can place any on-screen stuff on top of the playing video "easily"; evidently not as easy as I had hoped. – Neat Oct 06 '22 at 12:39
  • 1
    you can draw over video by implementing `QAbstractVideoSurface`, check this answer https://stackoverflow.com/a/71784149/2079189 – mugiseyebrows Oct 06 '22 at 12:52
  • @mugiseyebrows that looks like exactly what I'm looking for. I'll try implementing it, thank you. Though, could you clarify why you said "I dont think `QGraphicsVideoItem` is good for this task.", please? – Neat Oct 06 '22 at 12:56
  • @mugiseyebrows `QAbstractVideoSurface` does not exist in `Qt6` it seems, I'll see if I can implement this without it. – Neat Oct 06 '22 at 13:03
  • 1
    I believe QGraphicsView is suitable for drawing maps or plots, for something zoomable and panable, to use it for video player one needs to override most of its behaviour. – mugiseyebrows Oct 06 '22 at 13:04
  • There's nothing special about `QGraphicsVideoItem`. It will behave the same as any other graphics item. Pinning one item on top of another is quite simple. In the example, remove the `setFlag` line, and add `self._video_item.setSize(self.viewport().size())` to the bottom of `__init__`. Then change the geometry calculation to `rect = self._video_item.boundingRect(); self._proxy_control_bar.setGeometry(0, rect.height() - 100, rect.width(), 100)`. – ekhumoro Oct 06 '22 at 19:20
  • @mugiseyebrows those are (partially) wrong assumptions. 1. the Graphics View framework is perfectly suitable for anything requiring advanced graphics/item-based features that basic Qt widgets can't easily provide (without the need for QML); 2. using it for video playing is quite common (especially in python, considering that it avoids the overhead of implementing the video surface on the python side when required), it natively allows overlaid objects and does **not** need to "override most of its behavior" (in fact, properly using the item's [native]size and/or `fitInView` is usually enough). – musicamante Oct 07 '22 at 02:33
  • @ekhumoro I tried your suggestion, it still doesn't work. The `control_bar` sits at the middle of the window, and the excess empty space still exists. I also don't want the `control_bar` to adjust it's size with the `fitInView` call, which is why I set the flag. Though, thanks to your `self._proxy_control_bar.setGeometry(0, rect.height() - 100, rect.width(), 100)`, I was able to fix a problem I was having trying to implement mugiseyebrows's suggestion. – Neat Oct 07 '22 at 08:50
  • @mugiseyebrows while `QAbstractVideoSurface` is not available in Qt6, I was able to do it with `QVideoSink` (which I think is a replacement for `QAbstractVideoSurface`?). [Here's what I ended up with](https://paste.debian.net/hidden/be805d90/). Though, do you think I can make any improvements to the `target` calculation in `paintEvent`? – Neat Oct 07 '22 at 08:53
  • 1
    I'd use QTransform instead of calculating rectangles and adjustments and scaling image implicitly – mugiseyebrows Oct 07 '22 at 10:17
  • @mugiseyebrows I see, I'll have a look at it but unfortunately I don't think I have the minimum required understanding of matrix maths to make use of `QTransform`. Thank you for the help. – Neat Oct 07 '22 at 10:24
  • @Neat Your solution doesn't match the behaviour of mpv at all. It allows the control bar to completely obscure the video, which seems very weird. My solution works perfectly, and only requires three lines of code. I cannot reproduce the behaviour you mention, which seems unrelated to the solution I gave. There is a completely separate issue of maintaining the correct aspect ratio when resizing the window (which is what mpv/mplayer does). Solving that will eliminate the blank areas around the video and also pin the control bar to the bottom of the window. – ekhumoro Oct 07 '22 at 12:33

0 Answers0