Alter
Alter

Reputation: 3464

Qt6/PySide6 - how to position graphics elements

I'm new to Qt and trying to figure out how position graphics items?

For example, I want to overlay some text onto a video. However, instead of overlaying them, the text is vertically stacked above the video.

enter image description here

My code is below, I've tried setting the position of the video/text elements (e.g. video.setPos(0,0)) but it didn't work. I also tried using QGraphicsLayout but ran into problems adding the video/text elements.

import sys
from PySide6 import QtWidgets, QtCore, QtMultimedia, QtMultimediaWidgets

class MyWidget(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        
        view = QtWidgets.QGraphicsView(self)
        
        # Create a QT player
        player = QtMultimedia.QMediaPlayer(self)
        player.setSource(QtCore.QUrl("https://archive.org/download/SampleVideo1280x7205mb/SampleVideo_1280x720_5mb.mp4"))
        
        video = QtMultimediaWidgets.QGraphicsVideoItem()
        player.setVideoOutput(video)
        
        text = QtWidgets.QGraphicsTextItem("Hello World")
        
        scene = QtWidgets.QGraphicsScene()
        scene.addItem(video)
        scene.addItem(text)
        
        view.setScene(scene)
        view.setFixedSize(800,800)
        player.play()
        player.setLoops(-1)
        
if __name__ == "__main__":
    app = QtWidgets.QApplication([])

    widget = MyWidget()
    widget.resize(800, 800)
    widget.show()

    sys.exit(app.exec())

Upvotes: 0

Views: 461

Answers (1)

musicamante
musicamante

Reputation: 48529

The problem is caused by two aspects:

  1. QGraphicsVideoItem has a default size of 320x240; for conceptual and optimization reasons, it does not change its size when video content is loaded or changed;
  2. the video you're using has a different aspect ratio: 320x240 is 4:3, while the video is 1280x720, which is the standard widescreen ratio, 16:9;

By default, QGraphicsVideoItem just adapts the video contents to its current size, respecting the aspectRatioMode. The result is that you get some blank space above and below the video, similarly to what was common when showing movies in old TVs ("letterboxing"):

Example of letterboxing from Wikipedia

Since graphics items are always positioned using the top left corner of their coordinate system, you see a shifted image. In fact, if you just print the boundingRect of the video item when playing starts, you'll see that it has a vertical offset:

    player.mediaStatusChanged.connect(lambda: print(video.boundingRect()))

# result:
QtCore.QRectF(0.0, 30.0, 320.0, 180.0)
                   ^^^^ down by 30 pixels!

There are various possibilities to solve this, but it all depends on your needs, and it all resorts on connecting to the nativeSizeChanged signal. Then it's just a matter of properly setting the size, adjust the content to the visible viewport area, or eventually always call fitInView() while adjusting the position of other items (and ignoring transformations).

For instance, the following code will keep the existing size (320x240) but will change the offset based on the adapted size of the video:

        # ...
        self.video = QtMultimediaWidgets.QGraphicsVideoItem()
        # ...
        self.video.nativeSizeChanged.connect(self.updateOffset)

    def updateOffset(self, nativeSize):
        if nativeSize.isNull():
            return
        realSize = self.video.size()
        scaledSize = nativeSize.scaled(realSize, 
            QtCore.Qt.AspectRatioMode.KeepAspectRatio)
        yOffset = (scaledSize.height() - realSize.height()) / 2
        self.video.setOffset(QtCore.QPointF(0, yOffset))

Upvotes: 1

Related Questions