I'm trying to make the view be the same size as the video whilst allowing it to resize to fill the available area, I'm unsure if I'm doing it right by setting a maximum size but it kind of works, the issue is that the video doesn't start at the highest possible size and I have to keep resizing the window to get it to be viewable.
A good example on what I'm trying to achieve is a video editor like premiere pro where the video size determines the scene / view size.
import sys
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtGui import QResizeEvent, QShowEvent
from PySide6.QtWidgets import *
from PySide6.QtMultimediaWidgets import QGraphicsVideoItem
from PySide6.QtMultimedia import QMediaPlayer, QAudioOutput, QMediaMetaData
class MyWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.scene = QGraphicsScene()
self.view = QGraphicsView(self.scene)
self.video_widget = QGraphicsVideoItem()
self.scene.addItem(self.video_widget)
self.mediaPlayer = QMediaPlayer()
self.mediaPlayer.setVideoOutput(self.video_widget)
self.mediaPlayer.setSource(QUrl.fromLocalFile("potrait.mp4"))
self.video_widget.setSize(self.mediaPlayer.videoSink().videoSize())
self.text = QGraphicsTextItem("test")
self.text.setFlags(QGraphicsTextItem.ItemIsSelectable | QGraphicsTextItem.ItemIsMovable |
QGraphicsTextItem.ItemIsFocusable)
font = QFont()
font.setPointSize(100)
self.text.setFont(font)
self.scene.addItem(self.text)
layout = QVBoxLayout(self)
layout.addWidget(self.view)
self.setLayout(layout)
self.view.setScene(self.scene)
self.mediaPlayer.play()
def resizeFunc(self):
# Fit the view in the scene while keeping the aspect ratio
self.view.fitInView(self.scene.sceneRect(), Qt.KeepAspectRatio)
# Adjust maximum size of the view based on the size of the
video_item_width_view = self.view.mapFromScene(self.video_widget.sceneBoundingRect().topRight()).x() - self.video_widget.sceneBoundingRect().topLeft().x()
video_item_height_view = self.view.mapFromScene(self.video_widget.sceneBoundingRect().bottomLeft()).y() - self.video_widget.sceneBoundingRect().topLeft().y()
self.view.setMaximumSize(video_item_width_view + 10, video_item_height_view + 10)
print(video_item_width_view, video_item_height_view)
def resizeEvent(self, event):
self.resizeFunc()
def showEvent(self, event):
self.resizeFunc()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
Setting the scene rect doesn't make items disappear if they are outside of it. As the docs explain:
"[the bounding rectangle of the scene] is primarily used by QGraphicsView to determine the view's default scrollable area, and by QGraphicsScene to manage item indexing".
A possibility could be to clip painting of the item, but you need a subclass for it:
class SceneClipTextItem(QGraphicsTextItem):
def paint(self, qp, opt, widget=None):
qp.save()
qp.setClipRect(self.scene().sceneRect())
super().paint(qp, opt, widget)
qp.restore()
If what you want is to completely clip any contents that goes outside a specific region, then an alternative could be to simply paint over the whole view, but clipping out the contents of the scene.
class ClipView(QGraphicsView):
def drawForeground(self, qp, rect):
qp.resetTransform()
viewRect = self.viewport().rect()
sceneRect = self.mapFromScene(self.sceneRect())
qp.setClipRegion(
QRegion(viewRect)
- QRegion(sceneRect.boundingRect())
)
qp.fillRect(viewRect, Qt.black)
Of course, you can replace sceneRect
with anything you want, for instance the scene bounding rect of the video item.
If you want to use the default color, replace Qt.black
with self.palette().base()
.
The only drawback of this approach is that the "viewfinder" that is drawn above can only have opaque colors.
Finally, assuming that the view is part of a layout, you could use a custom layout manager that only contains the graphics view, override its setGeometry()
, call the base implementation, and then manually set the view geometry based on the given rect (adapting the size while keeping the aspect ratio).