I would like to have video preview in my Qt GUI and I was very happy to see that PyQt5 supports QMediaPlayer.
I found several basic examples here on SO, this one here below is just one:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtMultimedia import *
from PyQt5.QtMultimediaWidgets import *
class VideoPlayer(QWidget):
def __init__(self, parent=None):
super(VideoPlayer, self).__init__(parent)
videoItem = QGraphicsVideoItem()
videoItem.setSize(QSizeF(640, 480))
scene = QGraphicsScene(self)
scene.addItem(videoItem)
graphicsView = QGraphicsView(scene)
layout = QVBoxLayout()
layout.addWidget(graphicsView)
self.setLayout(layout)
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.mediaPlayer.setVideoOutput(videoItem)
def keyPressEvent(self, e):
if e.key() == Qt.Key_L:
print('loading')
self.load()
if e.key() == Qt.Key_P:
print('playing')
self.mediaPlayer.play()
print('state: ' + str(self.mediaPlayer.state()))
print('mediaStatus: ' + str(self.mediaPlayer.mediaStatus()))
print('error: ' + str(self.mediaPlayer.error()))
print('------------------------')
def load(self):
# H264 MPEG4 AVC not working
file = 'C:/Users/Antonio/Videos/test.wmv'
local = QUrl.fromLocalFile(file)
media = QMediaContent(local)
self.mediaPlayer.setMedia(media)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
player = VideoPlayer()
player.show()
sys.exit(app.exec_())
The videos I would like to preview are generally encoded with H264 MPEG4 AVC and I can watch them on the pc using VLC for example. But when I try to open the same video with the script above, I get an InvalidMedia as error message.
I tried to convert the video in WMV+WMA using VLC and then it works as expected.
Reading a bit on the Qt Documentation (see here) I have the impression that on windows only WMF files are supported. Is this true?
Is there a possibility to extend QMediaPlayer to a larger family of formats via the installation of a codec bundle?
If yes, how can I make aware my pyqt5 installation where to find the relevant codecs?
Thanks in advance
cheers
Qt uses the DirectShow backend on Windows, which, by defauilt, only supports the proprietary MS formats. For non-native formats, users must always install extra codecs, unless the applications they're using bundle them themselves. Qt completely relies on the capabilities of the platform backend, so you'll almost certainly have to install a third-party codec pack if you want to support a wider range of popular formats on Windows.
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.
I'm embedding another window into a Qt widget using PySide2.QtGui.QWindow.fromWinId(windowId). It works well, but it does not fire an event when the the original X11 window destroys it.
If I run the file below with mousepad & python3 embed.py and press Ctrl+Q, no event fires and I'm left with an empty widget.
How can I detect when the X11 window imported by QWindow.fromWinId is destroyed by its creator?
#!/usr/bin/env python
# sudo apt install python3-pip
# pip3 install PySide2
import sys, subprocess, PySide2
from PySide2 import QtGui, QtWidgets, QtCore
class MyApp(QtCore.QObject):
def __init__(self):
super(MyApp, self).__init__()
# Get some external window's windowID
print("Click on a window to embed it")
windowIdStr = subprocess.check_output(['sh', '-c', """xwininfo -int | sed -ne 's/^.*Window id: \\([0-9]\\+\\).*$/\\1/p'"""]).decode('utf-8')
windowId = int(windowIdStr)
print("Embedding window with windowId=" + repr(windowId))
# Create a simple window frame
self.app = QtWidgets.QApplication(sys.argv)
self.mainWindow = QtWidgets.QMainWindow()
self.mainWindow.show()
# Grab the external window and put it inside our window frame
self.externalWindow = QtGui.QWindow.fromWinId(windowId)
self.externalWindow.setFlags(QtGui.Qt.FramelessWindowHint)
self.container = QtWidgets.QWidget.createWindowContainer(self.externalWindow)
self.mainWindow.setCentralWidget(self.container)
# Install event filters on all Qt objects
self.externalWindow.installEventFilter(self)
self.container.installEventFilter(self)
self.mainWindow.installEventFilter(self)
self.app.installEventFilter(self)
self.app.exec_()
def eventFilter(self, obj, event):
# Lots of events fire, but no the Close one
print(str(event.type()))
if event.type() == QtCore.QEvent.Close:
mainWindow.close()
return False
prevent_garbage_collection = MyApp()
Below is a simple demo script that shows how to detect when an embedded external window closes. The script is only intended to work on Linux/X11. To run it, you must have wmctrl installed. The solution itself doesn't rely on wmctrl at all: it's merely used to get the window ID from the process ID; I only used it in my demo script because its output is very easy to parse.
The actual solution relies on QProcess. This is used to start the external program, and its finished signal then notifies the main window that the program has closed. The intention is that this mechanism should replace your current approach of using subprocess and polling. The main limitation of both these approaches is they will not work with programs that run themselves as background tasks. However, I tested my script with a number applications on my Arch Linux system - including Inkscape, GIMP, GPicView, SciTE, Konsole and SMPlayer - and they all behaved as expected (i.e. they closed the container window when exiting).
NB: for the demo script to work properly, it may be necessary to disable splash-screens and such like in some programs so they can embed themselves correctly. For example, GIMP must be run like this:
$ python demo_script.py gimp -s
If the script complains that it can't find the program ID, that probably means the program launched itself as a background task, so you will have to try to find some way to force it into the foreground.
Disclaimer: The above solution may work on other platforms, but I have not tested it there, and so cannot offer any guarantees. I also cannot guarantee that it will work with all programs on Linux/X11.
I should also point out that embedding external, third-party windows is not officially supported by Qt. The createWindowContainer function is only intended to work with Qt window IDs, so the behaviour with foreign window IDs is strictly undefined (see: QTBUG-44404). The various issues are documentented in this wiki article: Qt and foreign windows. In particular, it states:
A larger issue with our current APIs, that hasn't been discussed yet,
is the fact that QWindow::fromWinId() returns a QWindow pointer, which
from an API contract point of view should support any operation that
any other QWindow supports, including using setters to manipulate the
window, and connecting to signals to observe changes to the window.
This contract is not adhered to in practice by any of our platforms,
and the documentation for QWindow::fromWinId() doesn't mention
anything about the situation.
The reasons for this undefined/platform specific behaviour largely
boils down to our platforms relying on having full control of the
native window handle, and the native window handle often being a
subclass of the native window handle type, where we implement
callbacks and other logic. When replacing the native window handle
with an instance we don't control, and which doesn't implement our
callback logic, the behaviour becomes undefined and full of holes
compared to a regular QWindow.
So, please bear all that in mind when designing an application that relies on this functionality, and adjust your expectations accordingly...
The Demo script:
import sys, os, shutil
from PySide2.QtCore import (
Qt, QProcess, QTimer,
)
from PySide2.QtGui import (
QWindow,
)
from PySide2.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QMessageBox,
)
class Window(QWidget):
def __init__(self, program, arguments):
super().__init__()
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(layout)
self.external = QProcess(self)
self.external.start(program, arguments)
self.wmctrl = QProcess()
self.wmctrl.setProgram('wmctrl')
self.wmctrl.setArguments(['-lpx'])
self.wmctrl.readyReadStandardOutput.connect(self.handleReadStdOut)
self.timer = QTimer(self)
self.timer.setSingleShot(True)
self.timer.setInterval(25)
self.timer.timeout.connect(self.wmctrl.start)
self.timer.start()
self._tries = 0
def closeEvent(self, event):
for process in self.external, self.wmctrl:
process.terminate()
process.waitForFinished(1000)
def embedWindow(self, wid):
window = QWindow.fromWinId(wid)
widget = QWidget.createWindowContainer(
window, self, Qt.FramelessWindowHint)
self.layout().addWidget(widget)
def handleReadStdOut(self):
pid = self.external.processId()
if pid > 0:
windows = {}
for line in bytes(self.wmctrl.readAll()).decode().splitlines():
columns = line.split(maxsplit=5)
# print(columns)
# wid, desktop, pid, wmclass, client, title
windows[int(columns[2])] = int(columns[0], 16)
if pid in windows:
self.embedWindow(windows[pid])
# this is where the magic happens...
self.external.finished.connect(self.close)
elif self._tries < 100:
self._tries += 1
self.timer.start()
else:
QMessageBox.warning(self, 'Error',
'Could not find WID for PID: %s' % pid)
else:
QMessageBox.warning(self, 'Error',
'Could not find PID for: %r' % self.external.program())
if __name__ == '__main__':
if len(sys.argv) > 1:
if shutil.which(sys.argv[1]):
app = QApplication(sys.argv)
window = Window(sys.argv[1], sys.argv[2:])
window.setGeometry(100, 100, 800, 600)
window.show()
sys.exit(app.exec_())
else:
print('could not find program: %r' % sys.argv[1])
else:
print('usage: python %s <external-program-name> [args]' %
os.path.basename(__file__))
I'm trying to save an image to the system clipboard, so I wrote some code like this:
#!/usr/bin/python3
from PyQt5.Qt import QApplication
from PyQt5.QtWidgets import QWidget, QPushButton
from PyQt5.Qt import QImage
import sys
class MyWidget(QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.button = QPushButton(self)
self.button.clicked.connect(self.copyPicToClip)
def copyPicToClip(self):
image = QImage('./test.jpg')
QApplication.clipboard().setImage(image)
self.close()
if __name__ == '__main__':
a = QApplication(sys.argv)
myW = MyWidget()
myW.show()
a.exec()
Sadly, I found it doesn't work at all. Then I tried to find a solution. The first thing I tried was this:
def copyPicToClip(self):
image = QImage('./test.jpg')
QApplication.clipboard().setImage(image)
# self.close()
After this, I just found that it worked, but the window does not close automatically.
Then I tried with copying text:
#!/usr/bin/python3
from PyQt5.Qt import QApplication, QClipboard
from PyQt5.QtWidgets import QWidget, QPushButton
from PyQt5.Qt import QImage
import sys
class MyWidget(QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.button = QPushButton(self)
self.button.clicked.connect(self.copyPicToClip)
QApplication.clipboard().dataChanged.connect(self.testFunc)
def copyPicToClip(self):
image = QImage('./test.jpg')
QApplication.clipboard().setImage(image)
def testFunc(self):
print('Here')
self.close()
if __name__ == '__main__':
a = QApplication(sys.argv)
myW = MyWidget()
myW.show()
a.exec()
Sadly, it failed again.
So, it seems that if I close the application to early, the image won't be saved to the clipboard. But I want to close it after copying the image to the clipboard.
Any suggestions?
(PyQt5, ubuntu 16.10, if helps).
Unfortunately for you, this is "normal" behaviour on Linux. By default, clipboard data is not persisted when an application closes. The usual work-around for this problem is to install a clipboard manager. For Ubuntu, see this wiki article for more details:
Ubuntu Wiki: Clipboard Persistence
(NB: I have not actually tested any of the suggested solutions myself, so I don't know whether any of them will work with PyQt).
The basic problem is that on Linux, the clipboard only stores a reference to the underlying data. This is very efficient in terms of storage, because the data is only copied when the client program actually requests it. But of course if the source application closes, the reference will be invalidated, and the clipboard will become empty.
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())
I am trying a couple of sample programs that are not working in the last image of Debian for BBB. They work in a regular Xubuntu 13.10 distribution ad on windows but I have not been able to identify why Qpixmap is not working on this image. The regular widgets work Ok but the Qpixmap is not showing the image. The pyqt version installed is the 4.9.
One of the examples that I am using is the following.
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
from PyQt4 import QtGui, QtCore
class Imagen(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
hbox = QtGui.QHBoxLayout(self)
pixmap = QtGui.QPixmap("test.png")
lbl = QtGui.QLabel(self)
lbl.setPixmap(pixmap)
hbox.addWidget(lbl)
self.setLayout(hbox)
self.move(300, 200)
self.setWindowTitle('Test')
self.show()
def main():
app = QtGui.QApplication(sys.argv)
ex = Imagen()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Assuming that your png and python files are in the same directory add the following code:
Import os.path
import os.path as osp
add the following code to initUI()
...
path = osp.join(osp.direname(__file__), 'test.png')
pixmap = QtGui.QPixmap(path)
...
Relative paths are relative to the current directory, which is not necessarily the same as the directory the script itself is in. So either use an absolute pathname, or cd to the directory the image file is in before running the script.
However, if you're interested in solving this general issue properly, the best approach is to learn how to use the Qt Resource System and the pyrcc tool. This allows you to embed icons (or any files you like) directly in your application, and thus completely side-steps any potential problems with locating files.