I'm trying to play a video with QMediaPlayer in response to a drag and drop event, and I find that it works or doesn't work depending on where the player is instantiated. Am I doing something obviously wrong here?
Details: Qt 6.4.2, Python 3.11.2, Apple M2 Pro, macOS 13.2.1
(Background: My app allows (a variable number of) videos to be loaded simultaneously when the user drops videos, and I want each to have its own QMediaPlayer. Hence the need to instantiate the player in response to a drop event.)
Here is a minimal example highlighting the issue. This works fine when EARLY = True, and gives a segfault when EARLY = False.
#!/usr/bin/env python3
# encoding: utf-8
import os
import sys
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import QUrl, qVersion
from PySide6.QtWidgets import QMainWindow
from PySide6.QtMultimedia import QMediaPlayer
from PySide6.QtMultimediaWidgets import QVideoWidget
# Drag and drop a video file into the window to play it
# e.g., http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4
#
# Crashes on macOS when EARLY = False; works when EARLY = True
# (Qt 6.4.2, Python 3.11.2, Apple M2 Pro, macOS 13.2.1)
EARLY = True # segfault when `False`
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.resize(800, 600)
self.setWindowTitle('Qt6 Video Player')
self.setAcceptDrops(True)
if EARLY:
self.player = QMediaPlayer()
def dragEnterEvent(self, e):
if e.mimeData().hasUrls():
e.acceptProposedAction()
def dropEvent(self, e):
paths = [os.path.abspath(url.toLocalFile())
for url in e.mimeData().urls()]
if not EARLY:
self.player = QMediaPlayer()
videoWidget = QVideoWidget()
self.setCentralWidget(videoWidget)
self.player.errorOccurred.connect(self.mediaErrorOccured)
self.player.setVideoOutput(videoWidget)
self.player.setSource(QUrl.fromLocalFile(paths[0]))
self.player.play()
def mediaErrorOccured(self, error, message):
print('error:', error, message)
if __name__ == '__main__':
print(f'Qt version {qVersion()}')
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
Related
I am currently exploring the possibilities of displaying and working with PowerPoint presentations in a GUI using PyQt5/PyQt6 and Python. For that I found the most promising solution to be using a QAxWidget. Loading and displaying the pptx-file works fine, but unfortunately I noticed glitches when resizing the window of the GUI.
As a minimal example I used the following tutorial from the official Qt docs:
https://doc.qt.io/qtforpython/examples/example_axcontainer__axviewer.html?highlight=qaxwidget
After the QAxWidget()-initialization i just added the following line:
self.axWidget.setControl(r"C:\path\to\file\presentation.pptx")
Full code (taken from: https://doc.qt.io/qtforpython/examples/example_axcontainer__axviewer.html?highlight=qaxwidget):
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
"""PySide6 Active Qt Viewer example"""
import sys
from PyQt5.QtWidgets import qApp
from PySide6.QtAxContainer import QAxSelect, QAxWidget
from PySide6.QtGui import QAction
from PySide6.QtWidgets import (QApplication, QDialog,
QMainWindow, QMessageBox, QToolBar)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
toolBar = QToolBar()
self.addToolBar(toolBar)
fileMenu = self.menuBar().addMenu("&File")
loadAction = QAction("Load...", self, shortcut="Ctrl+L", triggered=self.load)
fileMenu.addAction(loadAction)
toolBar.addAction(loadAction)
exitAction = QAction("E&xit", self, shortcut="Ctrl+Q", triggered=self.close)
fileMenu.addAction(exitAction)
aboutMenu = self.menuBar().addMenu("&About")
aboutQtAct = QAction("About &Qt", self, triggered=qApp.aboutQt)
aboutMenu.addAction(aboutQtAct)
self.axWidget = QAxWidget()
self.axWidget.setControl(r"C:\path\to\file\presentation.pptx")
self.setCentralWidget(self.axWidget)
def load(self):
axSelect = QAxSelect(self)
if axSelect.exec() == QDialog.Accepted:
clsid = axSelect.clsid()
if not self.axWidget.setControl(clsid):
QMessageBox.warning(self, "AxViewer", f"Unable to load {clsid}.")
if __name__ == '__main__':
app = QApplication(sys.argv)
mainWin = MainWindow()
availableGeometry = mainWin.screen().availableGeometry()
mainWin.resize(availableGeometry.width() / 3, availableGeometry.height() / 2)
mainWin.show()
sys.exit(app.exec())
When resizing the window of the GUI, there appear some glitches underneath:
An animation that shows the result while resizing:
Unfortunately I haven't found many resources I could use where a QAxWidget is used in combination with Python to figure this out myself. That's why I'm here to ask if anyone out there might have a solution for getting rid of those glitches.
I got rid of the glitches by installing an event filter to the QAxWidget using self.axWidget.installEventFilter(self).
This will call the eventFilter()-method of the QMainWindow which I set up like this: (ReportDefinitionTool is the subclass of QMainWindow here.)
def eventFilter(self, widget: QWidget, event: QEvent):
if event.type() == QEvent.Resize and widget is self.pptx_axwidget:
self.pptx_axwidget.setFixedHeight(int(self.pptx_axwidget.width() / 16 * 9))
return super(ReportDefinitionTool, self).eventFilter(widget, event)
Since the PowerPoint-presentation is displayed in a 16:9 format, this will make sure the QAxWidget does only occupy this space. The glitchy space from the initial question came from the unused space of the QAxWidget.
Using PyQt5, I want to implement a two windows displaying one after another automatically, without the user interacting with any window. Something like this:
While True:
Show Window1
wait 2 seconds
Close Window1
Show Window2
wait 2 seconds
Close Window2
The problem I am having is that the main UI thread is stuck in app.exec_() function, so it cannot implement the opening and closing logic.
import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic
class Win1(QMainWindow):
def __init__(self):
super(Win1, self).__init__()
uic.loadUi('win1.ui', self)
self.show()
class Win2(QMainWindow):
def __init__(self):
super(Win2, self).__init__()
uic.loadUi('win2.ui', self)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
while True:
win = Win1()
time.sleep(1)
win.close()
win = Win2()
time.sleep(1)
win.close()
app.exec_() # <--------- Program blocks here
I would appreciate if someone can share a minimal example for this working without blocking. Or please point to the mechanism that should be used.
If you are going to work with Qt then you should forget about sequential logic but you have to implement the logic using events. For example, in your case you want one window to be shown every time T and another to be hidden, so that can be implemented with a QTimer and a flag:
import sys
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5 import uic
class Win1(QMainWindow):
def __init__(self):
super(Win1, self).__init__()
uic.loadUi('win1.ui', self)
self.show()
class Win2(QMainWindow):
def __init__(self):
super(Win2, self).__init__()
uic.loadUi('win2.ui', self)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
timer = QTimer()
timer.setProperty("flag", True)
win1 = Win1()
win2 = Win2()
def on_timeout():
flag = timer.property("flag")
if flag:
win1.show()
win2.close()
else:
win2.show()
win1.close()
timer.setProperty("flag", not flag)
timer.timeout.connect(on_timeout)
timer.start(1 * 1000)
on_timeout()
app.exec_()
You should not use while loop or time.sleep since they block the eventloop in which the GUI lives, that is: they freeze the windows
Using QVideoWidget in PySide2 (although the python part may not be significant). I've set up my hotkeys using QShortcut, and it all works great. When I press 'F' to enter full-screen mode that works too, but then I can't leave. None of my hotkeys or mouse event handlers work. I end up stuck in full-screen mode.
Is there a way to allow it to respond even in full-screen mode? Have I gone about creating my hotkeys the wrong way?
This example demonstrates the issue:
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self._fullscreen = False
self.movie_display = QVideoWidget(self)
self.movie_handler = QMediaPlayer()
self.movie_handler.setVideoOutput(self.movie_display)
layout = QVBoxLayout()
layout.addWidget(self.movie_display)
self.setLayout(layout)
QShortcut(QKeySequence(QtConsts.Key_F), self, self.toggle_fullscreen)
s = 'test.webm'
s = os.path.join(os.path.dirname(__file__), s)
local = QUrl.fromLocalFile(s)
media = QMediaContent(local)
self.movie_handler.setMedia(media)
self.movie_handler.play()
def toggle_fullscreen(self):
self._fullscreen = not self._fullscreen
self.movie_display.setFullScreen(self._fullscreen)
The problem is that the shortcut is set in the window but when full-screen is set in the QVideoWidget 2 windows are created: The original window and the window where the QVideoWidget is in full-screen. One possible solution is to set the QShortcut in the QVideoWidget or establish that the context of the QShortcut is Qt::ApplicationShortcut:
import os
from PyQt5 import QtCore, QtGui, QtWidgets, QtMultimedia, QtMultimediaWidgets
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self._fullscreen = False
self.movie_display = QtMultimediaWidgets.QVideoWidget()
self.movie_handler = QtMultimedia.QMediaPlayer(
self, QtMultimedia.QMediaPlayer.VideoSurface
)
self.movie_handler.setVideoOutput(self.movie_display)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.movie_display)
QtWidgets.QShortcut(
QtGui.QKeySequence(QtCore.Qt.Key_F),
self.movie_display,
self.toggle_fullscreen,
)
# or
"""QtWidgets.QShortcut(
QtGui.QKeySequence(QtCore.Qt.Key_F),
self,
self.toggle_fullscreen,
context=QtCore.Qt.ApplicationShortcut
)"""
file = os.path.join(os.path.dirname(os.path.realpath(__file__)), "test.webm")
media = QtMultimedia.QMediaContent(QtCore.QUrl.fromLocalFile(file))
self.movie_handler.setMedia(media)
self.movie_handler.play()
def toggle_fullscreen(self):
self._fullscreen = not self._fullscreen
self.movie_display.setFullScreen(self._fullscreen)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Here is my code:
import sys
from PyQt5.Qt import QUrl
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QMediaPlaylist
from PyQt5.QtWidgets import QApplication, QWidget
class Demo(QWidget):
def __init__(self):
super(Demo, self).__init__()
self.playlist = QMediaPlaylist(self)
self.player = QMediaPlayer(self)
self.player.setPlaylist(self.playlist)
self.playlist.addMedia(QMediaContent(
QUrl.fromLocalFile('mypath')))
self.playlist.addMedia(QMediaContent(
QUrl.fromLocalFile('mypath')))
self.playlist.setPlaybackMode(QMediaPlaylist.Loop)
self.playlist.setCurrentIndex(1)
self.player.setVolume(80)
self.player.play()
self.player.durationChanged.connect(self.print_durantion)
print(self.player.duration()) # 0
def print_durantion(self, d):
print(d) # never printed
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = Demo()
demo.show()
sys.exit(app.exec_())
I'm trying to get the duration of the file, but always get 0. It says in QtAssistant that the value may not be available when initial playback begins and tells us to use durantionChanged to receive status notifications.
But how will the durationChanged signal be emitted if duration is always 0(the value d is never printed)?
Is there any way to get duration when a media file is played? Any helps would be appreciated. (I tested the above code on MacOS)
You are attaching the durationChanged signal after you select the media. This signal is triggered only once as the media is selected and not after.
If you place it before setCurrentIndex it should work as expected, but you may need to attaching it before linking the playlist.
Here is a working mediaplayer demo application in PyQt5 to confirm the signal works for your current PyQt5 install.
I am using PyQt5 5.5.1 (64-bit) with Python 3.4.0 (64-bit) on Windows 8.1
64-bit.
I am having trouble restoring the position and size (geometry) of my
very simple PyQt app.
Here is minimal working application:
import sys
from PyQt5.QtWidgets import QApplication, QWidget
class myApp(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
view = myApp()
sys.exit(app.exec())
What I read online is that this is the default behavior and we need to
use QSettings to save and retrieve settings from Windows registry,
which is stored in
\\HKEY_CURRENT_USER\Software\{CompanyName}\{AppName}\
Here are some of the links I read.
I could have followed those tutorials but those tutorials/docs were
written for C++ users.
C++ is not my glass of beer, and converting those codes are impossible to me.
Related:
QSettings(): How to save to current working directory
This should do.
import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtCore import QSettings, QPoint, QSize
class myApp(QWidget):
def __init__(self):
super(myApp, self).__init__()
self.settings = QSettings( 'My company', 'myApp')
# Initial window size/pos last saved. Use default values for first time
self.resize(self.settings.value("size", QSize(270, 225)))
self.move(self.settings.value("pos", QPoint(50, 50)))
def closeEvent(self, e):
# Write window size and position to config file
self.settings.setValue("size", self.size())
self.settings.setValue("pos", self.pos())
e.accept()
if __name__ == '__main__':
app = QApplication(sys.argv)
frame = myApp()
frame.show()
app.exec_()
I simplified this example: QSettings(): How to save to current working directory
Similar to #Valentin's response, because I feel settings are being written to registry, which will be issue for cross compatiblity. Here is the relevant startEvent() and closeEvent() for the job.
def startEvent()
self.settings = QSettings(QSettings.IniFormat,QSettings.SystemScope, '__MyBiz', '__settings')
self.settings.setFallbacksEnabled(False) # File only, not registry or or.
# setPath() to try to save to current working directory
self.settings.setPath(QSettings.IniFormat,QSettings.SystemScope, './__settings.ini')
# Initial window size/pos last saved
self.resize(self.settings.value("size", QSize(270, 225)))
self.move(self.settings.value("pos", QPoint(50, 50)))
self.tab = QWidget()
def closeEvent(self, e):
# Write window size and position to config file
self.settings.setValue("size", self.size())
self.settings.setValue("pos", self.pos())
startEvent() should be initiated at startup and closeEvent() should be taken care before quitting the main window.
You should indeed use QSetting for this.
All the Qt examples have been converted to Python. They are included in the source packages of PyQt (or PySide), which you can download here
You can also look online in the github repo, particularly in application.py of mainwindows example.
def readSettings(self):
settings = QSettings("Trolltech", "Application Example")
pos = settings.value("pos", QPoint(200, 200))
size = settings.value("size", QSize(400, 400))
self.resize(size)
self.move(pos)
def writeSettings(self):
settings = QSettings("Trolltech", "Application Example")
settings.setValue("pos", self.pos())
settings.setValue("size", self.size())
Fire writeSettings() before quitting and initiate readSettings() on startup.
In my case I use .ini files to store settings (language, default user, ...). the same code works on both Debian and Windows.
An example:
from PySide.QtCore import QSettings
self.settings = QSettings('settings.ini', QSettings.IniFormat)
...
self.settings.setValue('size', self.size())