Home > Back-end >  How to adjust video to widget size in QT python?
How to adjust video to widget size in QT python?

Time:10-20

I just started checking out QT for Python. I wanna build an application that has a video kind of as a background. I've just spent hours trying to get this to work in QT but I just can't wrap my head around it. I have a frame self.MainFrame = QtWidgets.QFrame(self.centralwidget) to control the size I want. I want to have a video centered in that frame, filling it completely. I don't want any bars around it, so it should just be the video (cropped and zoomed). Now I have tried to do this with both QVideoWidget and QGraphicsView but neither seem to work for me.

Here is the current code snippet (This is an absolute mess I've been trying all sorts of methods to get this to work)

        self.graphicsView = QtWidgets.QGraphicsView(self.MainFrame)
        self.graphicsView.setFocusPolicy(QtCore.Qt.NoFocus)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        self.graphicsView.setSizePolicy(sizePolicy)
        file = os.path.join(os.path.dirname(__file__), "small.avi")
        self.item = QGraphicsVideoItem()
        self.graphiScene = QGraphicsScene(self.graphicsView)
        self.graphicsView.setScene(self.graphiScene)
        
        self.player = QMediaPlayer()
      
        sizer = QSizeF(1920.0,1080.0)
        
        self.item.setAspectRatioMode(1)
        self.item.setSize(sizer)
       
        self.player.setVideoOutput(self.item)
        
        self.graphiScene.addItem(self.item)
        self.graphicsView.show()
        self.player.setMedia(QMediaContent(QUrl.fromLocalFile(file))) #.QtMultimedia.QMediaContent(QtCore.

I want to make this dynamic aswell, so it can be displayed in a 1920x1080 and 1080x1920 orientation and still have the same thing (obv different crops and visible parts)

I would really appreciate any help with this, I feel like I am loosing my mind, Cheers

PS: I did see there was a method to crop and zoom in QT 4 or smth, unfortunately it seems like that was removed.

CodePudding user response:

When a widget is simply created with a parent, it doesn't resize itself on its own (that's what layout managers do).

Also, setting the size policy is completely useless, since those policies are only considered when a layout manages the widgets that have been added to it.

In order to achieve that, you must explicitly set the widget geometry, which is normally done by overriding the resizeEvent of the parent.

After that, you also need to ensure that the view shows the correct contents, which is achieved by calling fitInView() with the correct aspect ratio: for what you need (cropping), the value is KeepAspectRatioByExpanding.

Finally, you will probably want to show the video centered on the window, so you also need to call centerOn().

To simplify things, I'd suggest to subclass what would be the central widget (QFrame, in your case) and implement the "video background" in it.

from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtMultimedia import *
from PyQt5.QtMultimediaWidgets import *

class FrameWithVideo(QFrame):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.background = QGraphicsView(self)
        self.background.setBackgroundBrush(Qt.black)
        self.background.setFrameShape(0) # no borders
        self.background.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.background.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

        self.player = QMediaPlayer()
        # for Qt6 you can use self.player.setActiveAudioTrack(-1)
        # but in reality you should probably use a video-only file
        self.player.setMuted(True)

        scene = QGraphicsScene()
        self.background.setScene(scene)
        self.videoItem = QGraphicsVideoItem()
        scene.addItem(self.videoItem)
        self.player.setVideoOutput(self.videoItem)

        # Qt5 requires a playlist to play content in loop;
        # for Qt6 you can use QMediaPlayer.setLoops(-1)
        playlist = QMediaPlaylist(self)
        playlist.setPlaybackMode(playlist.Loop)
        playlist.addMedia(QMediaContent(QUrl.fromLocalFile('/tmp/Armonatrix.mp4')))
        self.player.setMedia(QMediaContent(playlist))

        self.videoItem.nativeSizeChanged.connect(self.resizeBackground)

    def resizeBackground(self):
        # ensure that the view is always below any other child
        self.background.lower()
        # make the view as big as the parent
        self.background.resize(self.size())
        # resize the item to the video size
        self.videoItem.setSize(self.videoItem.nativeSize())
        # fit the whole viewable area to the item and crop exceeding margins
        self.background.fitInView(
            self.videoItem, Qt.KeepAspectRatioByExpanding)
        # scroll the view to the center of the item
        self.background.centerOn(self.videoItem)

    def hideEvent(self, event):
        super().hideEvent(event)
        self.player.pause()

    def showEvent(self, event):
        super().showEvent(event)
        self.resizeBackground()
        self.player.play()

    def resizeEvent(self, event):
        super().resizeEvent(event)
        self.resizeBackground()


class Test(QMainWindow):
    def __init__(self):
        super().__init__()
        self.mainFrame = FrameWithVideo()
        self.setCentralWidget(self.mainFrame)
        layout = QGridLayout(self.mainFrame)
        for r in range(4):
            for c in range(4):
                layout.addWidget(QPushButton('{} {}'.format(r 1, c 1)), r, c)

    def sizeHint(self):
        return QApplication.primaryScreen().size() * 2 / 3


if __name__ == '__main__':
    import sys
    app = QApplication(sys.argv)
    w = Test()
    w.show()
    sys.exit(app.exec())

Note that if the source video has a high resolution (720p or more) you should consider delaying the resizeBackground call using a QTimer set as singleShot: most modern systems use opaque resizing, meaning that the content of the window is dynamically updated when the user resizes the window; layout management is quite demanding and should always have priority, since the function calls in resizeBackground might temporarily block those computations, a slight delay might improve performance at the small cost of some visible video resizing issues.

  • Related