QSplitter and resizing of QLabel with video frames does not work properly
-
Hi all (again!). I have now the following situation: I want to have a viewer with several video streams (just frames visualized one after other, not actual video player). I want to have some logs under it. I want it to be resizable so that video streams & logs adapt to moving of the QSplitter. However, it seems that (I tried different pieces of code) I can never make the part with the video smaller once the video is loaded. I understand that this happens because I resize the frame to the size of the QLabel, but I do not know how I can do it some other way. Sometimes it even happens (with some workarounds I tried to apply) that QLabel and frames inside it keep increasing by themselves in some sort of feedback loop. While I approximately understand why this is happening, I am not sure what to do about it. I managed to assemble the following MRE:
import sys import cv2 import logging from PySide2.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QSplitter, QPushButton, QLabel, QTextEdit, QFileDialog, QSizePolicy, ) from PySide2.QtCore import Qt, QTimer from PySide2.QtGui import QImage, QPixmap class VideoViewer(QWidget): def __init__(self): super().__init__() self.cap = None self.paused = True # Layout for the video viewer layout = QVBoxLayout(self) # Video display label self.video_label = QLabel("Video stream will be displayed here") self.video_label.setAlignment(Qt.AlignCenter) self.video_label.setStyleSheet("background-color: black; color: white") self.video_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) layout.addWidget(self.video_label) # Control buttons controls_layout = QHBoxLayout() self.load_button = QPushButton("Load Video") self.load_button.clicked.connect(self.load_video) controls_layout.addWidget(self.load_button) self.play_pause_button = QPushButton("Play") self.play_pause_button.clicked.connect(self.play_pause_video) controls_layout.addWidget(self.play_pause_button) layout.addLayout(controls_layout) # Timer for video frame updates self.timer = QTimer() self.timer.timeout.connect(self.update_frame) def load_video(self): video_path, _ = QFileDialog.getOpenFileName(self, "Select Video File") if video_path: self.cap = cv2.VideoCapture(video_path) self.paused = True self.play_pause_button.setText("Play") logging.info(f"Loaded video: {video_path}") def play_pause_video(self): if self.cap is None: return if self.paused: self.paused = False self.play_pause_button.setText("Pause") self.timer.start(30) # Update every 30 ms logging.info("Video playback started.") else: self.paused = True self.play_pause_button.setText("Play") self.timer.stop() logging.info("Video playback paused.") def update_frame(self): if self.cap is not None and not self.paused: ret, frame = self.cap.read() if ret: frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) label_size = self.video_label.size() frame = cv2.resize(frame, (label_size.width(), label_size.height())) height, width, _ = frame.shape q_image = QImage(frame.data, width, height, QImage.Format_RGB888) self.video_label.setPixmap(QPixmap.fromImage(q_image)) else: self.timer.stop() self.cap.release() self.cap = None self.play_pause_button.setText("Play") logging.info("Video playback finished.") class LogHandler(logging.Handler): def __init__(self, text_edit): super().__init__() self.text_edit = text_edit def emit(self, record): msg = self.format(record) self.text_edit.append(msg) class MainUI(QWidget): def __init__(self): super().__init__() # Main layout main_layout = QVBoxLayout(self) # Splitter to divide video viewer and log panel self.splitter = QSplitter(Qt.Vertical) main_layout.addWidget(self.splitter) # Video viewer panel self.viewer_panel = VideoViewer() self.splitter.addWidget(self.viewer_panel) # Log panel self.log_text_edit = QTextEdit() self.log_text_edit.setReadOnly(True) self.log_text_edit.setStyleSheet("background-color: black; color: white;") self.splitter.addWidget(self.log_text_edit) # Set initial splitter sizes self.splitter.setSizes([800, 200]) # Configure logging log_handler = LogHandler(self.log_text_edit) log_handler.setFormatter( logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") ) logging.getLogger().addHandler(log_handler) logging.getLogger().setLevel(logging.INFO) logging.info("Application started.") self.setLayout(main_layout) self.setWindowTitle("Video Player with Log Panel") self.resize(800, 600) if __name__ == "__main__": app = QApplication(sys.argv) window = MainUI() window.show() sys.exit(app.exec_())
What should I change about this code, so I can freely move QSplitter in the way that video can also get smaller, not only bigger, and that of course no feedback loop with random video growing happens? My goal is to have multiple videos but I think the same strategy as for one video in this MRE would work. I expect video to also adapt automatically if the user resizes window. It used to work till I introduced QSplitter, not it freezes and/or crashes in the MRE I provided above.
Thanks in advance! -
Hi all (again!). I have now the following situation: I want to have a viewer with several video streams (just frames visualized one after other, not actual video player). I want to have some logs under it. I want it to be resizable so that video streams & logs adapt to moving of the QSplitter. However, it seems that (I tried different pieces of code) I can never make the part with the video smaller once the video is loaded. I understand that this happens because I resize the frame to the size of the QLabel, but I do not know how I can do it some other way. Sometimes it even happens (with some workarounds I tried to apply) that QLabel and frames inside it keep increasing by themselves in some sort of feedback loop. While I approximately understand why this is happening, I am not sure what to do about it. I managed to assemble the following MRE:
import sys import cv2 import logging from PySide2.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QSplitter, QPushButton, QLabel, QTextEdit, QFileDialog, QSizePolicy, ) from PySide2.QtCore import Qt, QTimer from PySide2.QtGui import QImage, QPixmap class VideoViewer(QWidget): def __init__(self): super().__init__() self.cap = None self.paused = True # Layout for the video viewer layout = QVBoxLayout(self) # Video display label self.video_label = QLabel("Video stream will be displayed here") self.video_label.setAlignment(Qt.AlignCenter) self.video_label.setStyleSheet("background-color: black; color: white") self.video_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) layout.addWidget(self.video_label) # Control buttons controls_layout = QHBoxLayout() self.load_button = QPushButton("Load Video") self.load_button.clicked.connect(self.load_video) controls_layout.addWidget(self.load_button) self.play_pause_button = QPushButton("Play") self.play_pause_button.clicked.connect(self.play_pause_video) controls_layout.addWidget(self.play_pause_button) layout.addLayout(controls_layout) # Timer for video frame updates self.timer = QTimer() self.timer.timeout.connect(self.update_frame) def load_video(self): video_path, _ = QFileDialog.getOpenFileName(self, "Select Video File") if video_path: self.cap = cv2.VideoCapture(video_path) self.paused = True self.play_pause_button.setText("Play") logging.info(f"Loaded video: {video_path}") def play_pause_video(self): if self.cap is None: return if self.paused: self.paused = False self.play_pause_button.setText("Pause") self.timer.start(30) # Update every 30 ms logging.info("Video playback started.") else: self.paused = True self.play_pause_button.setText("Play") self.timer.stop() logging.info("Video playback paused.") def update_frame(self): if self.cap is not None and not self.paused: ret, frame = self.cap.read() if ret: frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) label_size = self.video_label.size() frame = cv2.resize(frame, (label_size.width(), label_size.height())) height, width, _ = frame.shape q_image = QImage(frame.data, width, height, QImage.Format_RGB888) self.video_label.setPixmap(QPixmap.fromImage(q_image)) else: self.timer.stop() self.cap.release() self.cap = None self.play_pause_button.setText("Play") logging.info("Video playback finished.") class LogHandler(logging.Handler): def __init__(self, text_edit): super().__init__() self.text_edit = text_edit def emit(self, record): msg = self.format(record) self.text_edit.append(msg) class MainUI(QWidget): def __init__(self): super().__init__() # Main layout main_layout = QVBoxLayout(self) # Splitter to divide video viewer and log panel self.splitter = QSplitter(Qt.Vertical) main_layout.addWidget(self.splitter) # Video viewer panel self.viewer_panel = VideoViewer() self.splitter.addWidget(self.viewer_panel) # Log panel self.log_text_edit = QTextEdit() self.log_text_edit.setReadOnly(True) self.log_text_edit.setStyleSheet("background-color: black; color: white;") self.splitter.addWidget(self.log_text_edit) # Set initial splitter sizes self.splitter.setSizes([800, 200]) # Configure logging log_handler = LogHandler(self.log_text_edit) log_handler.setFormatter( logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") ) logging.getLogger().addHandler(log_handler) logging.getLogger().setLevel(logging.INFO) logging.info("Application started.") self.setLayout(main_layout) self.setWindowTitle("Video Player with Log Panel") self.resize(800, 600) if __name__ == "__main__": app = QApplication(sys.argv) window = MainUI() window.show() sys.exit(app.exec_())
What should I change about this code, so I can freely move QSplitter in the way that video can also get smaller, not only bigger, and that of course no feedback loop with random video growing happens? My goal is to have multiple videos but I think the same strategy as for one video in this MRE would work. I expect video to also adapt automatically if the user resizes window. It used to work till I introduced QSplitter, not it freezes and/or crashes in the MRE I provided above.
Thanks in advance!@sapvi said in QSplitter and resizing of QLabel with video frames does not work properly:
I want to have a viewer with several video streams (just frames visualized one after other, not actual video player)
What do you think a video is and what a video player does? :)
Videos are just consequencial frames (with or without some additional encoding)@sapvi said in QSplitter and resizing of QLabel with video frames does not work properly:
What should I change about this code, so I can freely move QSplitter in the way that video can also get smaller, not only bigger, and that of course no feedback loop with random video growing happens?
A splitter is expanding by default. This in combination with the other widget's size policies lets everything populate more space but does not automatically shrink again.
Modify your widget's and layout's sizeHints and policies. -
@sapvi said in QSplitter and resizing of QLabel with video frames does not work properly:
I want to have a viewer with several video streams (just frames visualized one after other, not actual video player)
What do you think a video is and what a video player does? :)
Videos are just consequencial frames (with or without some additional encoding)@sapvi said in QSplitter and resizing of QLabel with video frames does not work properly:
What should I change about this code, so I can freely move QSplitter in the way that video can also get smaller, not only bigger, and that of course no feedback loop with random video growing happens?
A splitter is expanding by default. This in combination with the other widget's size policies lets everything populate more space but does not automatically shrink again.
Modify your widget's and layout's sizeHints and policies.@Pl45m4 sorry, I am new to Qt and not sure in which direction to explore with this advice.
I have tried using
self.video_label.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
instead of Expanding policy. Then:- In MRE, I can resize several times, but artefacts are happening during the resize (loss of color etc), and after ~3rd resize all app just crashes.
- If I try it in my actual bigger app, I do not even see videos, they get automatically minimal size of (0,0) in 2x3 grid which I use. And I want them to automatically expand to the currently-available size within the corresponding section of the QSplitter. Just to not block QSplitter from reducing this section :(
-
@Pl45m4 sorry, I am new to Qt and not sure in which direction to explore with this advice.
I have tried using
self.video_label.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored)
instead of Expanding policy. Then:- In MRE, I can resize several times, but artefacts are happening during the resize (loss of color etc), and after ~3rd resize all app just crashes.
- If I try it in my actual bigger app, I do not even see videos, they get automatically minimal size of (0,0) in 2x3 grid which I use. And I want them to automatically expand to the currently-available size within the corresponding section of the QSplitter. Just to not block QSplitter from reducing this section :(
Haven't run your example because I don't use OpenCV for Python (you can't expect everyone to have that installed to run your example). But for Python it's the same as for C++ in most of the cases.
Also when you experience continuous growth of your label/layout it might be related to the fact that you scale your image to your label size... the content shouldn't be exact the same size as the "frame" (QLabel
is aQFrame
) around it as it might resize itself then to contain the image properly. -
Haven't run your example because I don't use OpenCV for Python (you can't expect everyone to have that installed to run your example). But for Python it's the same as for C++ in most of the cases.
Also when you experience continuous growth of your label/layout it might be related to the fact that you scale your image to your label size... the content shouldn't be exact the same size as the "frame" (QLabel
is aQFrame
) around it as it might resize itself then to contain the image properly.@Pl45m4 good point, did not think about it. I changed example to generate random
QImage
. Issues are same: namely problems with resizing & crashing. In my actual app I do not get crash, surprisingly, but same resize issues.import sys import logging import numpy as np from PySide2.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QSplitter, QPushButton, QLabel, QTextEdit, QSizePolicy, ) from PySide2.QtCore import Qt, QTimer from PySide2.QtGui import QImage, QPixmap class VideoViewer(QWidget): def __init__(self): super().__init__() # Layout for the video viewer layout = QVBoxLayout(self) # Create QLabel as Fake Video Display self.video_label = QLabel() self.video_label.setAlignment(Qt.AlignCenter) self.video_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.video_label.setStyleSheet("background-color: black;") layout.addWidget(self.video_label) # Control buttons controls_layout = QHBoxLayout() self.play_pause_button = QPushButton("Play Fake Video") self.play_pause_button.clicked.connect(self.toggle_fake_video) controls_layout.addWidget(self.play_pause_button) layout.addLayout(controls_layout) self.setLayout(layout) # Timer for updating the fake video self.timer = QTimer() self.timer.timeout.connect(self.update_fake_video) self.running = False def toggle_fake_video(self): """Start or stop fake video playback""" if self.running: self.timer.stop() self.play_pause_button.setText("Play Fake Video") logging.info("Fake video stopped.") else: self.timer.start(30) # Update every 30 ms self.play_pause_button.setText("Stop Fake Video") logging.info("Fake video started.") self.running = not self.running def update_fake_video(self): """Generate a random-colored QImage and display it in QLabel""" width = self.video_label.width() height = self.video_label.height() if width <= 0 or height <= 0: return # Avoid errors when widget is hidden or uninitialized # Generate random pixel values (3 channels for RGB) random_image = np.random.randint(0, 256, (height, width, 3), dtype=np.uint8) # Create QImage from numpy array q_image = QImage(random_image.data, width, height, QImage.Format_RGB888) # Convert QImage to QPixmap and display it self.video_label.setPixmap(QPixmap.fromImage(q_image)) class LogHandler(logging.Handler): def __init__(self, text_edit): super().__init__() self.text_edit = text_edit def emit(self, record): msg = self.format(record) self.text_edit.append(msg) class MainUI(QWidget): def __init__(self): super().__init__() # Main layout main_layout = QVBoxLayout(self) # Splitter to divide video viewer and log panel self.splitter = QSplitter(Qt.Vertical) main_layout.addWidget(self.splitter) # Video viewer panel self.viewer_panel = VideoViewer() self.splitter.addWidget(self.viewer_panel) self.viewer_panel.setMinimumSize(0, 0) # Log panel self.log_text_edit = QTextEdit() self.log_text_edit.setReadOnly(True) self.log_text_edit.setStyleSheet("background-color: black; color: white;") self.log_text_edit.setMinimumSize(0, 0) self.splitter.addWidget(self.log_text_edit) # Allow collapsing of both sections self.splitter.setCollapsible(0, True) self.splitter.setCollapsible(1, True) # Set initial splitter sizes self.splitter.setSizes([800, 200]) # Configure logging log_handler = LogHandler(self.log_text_edit) log_handler.setFormatter( logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") ) logging.getLogger().addHandler(log_handler) logging.getLogger().setLevel(logging.INFO) logging.info("Application started.") self.setLayout(main_layout) self.setWindowTitle("Fake Video Player with Log Panel") self.resize(800, 600) if __name__ == "__main__": app = QApplication(sys.argv) window = MainUI() window.show() sys.exit(app.exec_())
-
Hi,
Use "QScrollArea" in your "QSplitter" and not "QLabel", then create "QVideoWidget" and use the object as follows:
QSplitter *pcoYourSplitter = new QSplitter(Qt::Orientation::Horizontal);
pcoYourLayout->addWidget(pcoYourSplitter);QScrollArea *pcoYourScrollArea = new QScrollArea();
QVideoWidget *pcoYourVideoWidget = new QVideoWidget(pcoYourScrollArea);// Add widget or replace widgt - replaceWidget(0, pcoYourVideoWidget)
pcoYourSplitter->addWidget(pcoYourVideoWidget);and then start your video.
For me, this works fine in C++ and Python (also in C++ MDI area).