PyQt5 -- Unable to playback video from stream - python

I've seen a number of other questions like this floating around but it seems many of them are unresolved or unrelated to my situation, so here goes.
I'm attempting to play back a video stored as serialized data (through pickle) on a mongodb collection.
Here's the code:
binary_file = my_database_entry['binary video']
unpickle = pickle.dumps(binary_file)
outByteArray = QByteArray(unpickle)
mediaStream = QBuffer()
mediaStream.setBuffer(outByteArray)
mediaStream.open(QIODevice.ReadWrite)
mediaPlayer.setMedia(QMediaContent(), mediaStream)
mediaPlayer.play()
where 'my_database_entry' is the mongoDB entry and 'binary video' is the dictionary key for the pickled video entry.
This also assumes that mediaPlayer is properly created and initialized within my user interface i.e.
mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
videoPlayer = QVideoWidget()
mediaPlayer.setVideoOutput(videoPlayer)
I also tried initializing mediaPlayer with a 'QMediaPlayer.StreamPlayback' flag but again, nothing.
It crashes when I try it on windows and it's just a black screen when I try it on mac. No error logs or anything (nothing enlightening at any rate).
Has anyone gotten this to successfully work for them and if so, how did you do it?
Thanks!
-Mark

You need to keep a reference to both the buffer and the underlying data, otherwise they will just be garbage-collected after the player starts.
And note that in your example, it is utterly pointless pickling the video data, as it is just bytes and so there's nothing worth serializing. Pickle is only useful for structured python objects, such as a list or dict.
Below is a demo script with a complete video player. It initally gets the video resource from the file-system, but it would work just the same if it came from a database:
from PyQt5 import QtCore, QtWidgets
from PyQt5 import QtMultimedia, QtMultimediaWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.player = QtMultimedia.QMediaPlayer(self)
self.viewer = QtMultimediaWidgets.QVideoWidget(self)
self.player.setVideoOutput(self.viewer)
self.player.stateChanged.connect(self.handleStateChanged)
self.button1 = QtWidgets.QPushButton('Play', self)
self.button2 = QtWidgets.QPushButton('Stop', self)
self.button1.clicked.connect(self.handleButton)
self.button2.clicked.connect(self.player.stop)
self.button2.setEnabled(False)
layout = QtWidgets.QGridLayout(self)
layout.addWidget(self.viewer, 0, 0, 1, 2)
layout.addWidget(self.button1, 1, 0)
layout.addWidget(self.button2, 1, 1)
self._buffer = QtCore.QBuffer(self)
self._data = None
def handleButton(self):
path = QtWidgets.QFileDialog.getOpenFileName(self)[0]
if path:
self.button1.setEnabled(False)
self.button2.setEnabled(True)
with open(path, 'rb') as stream:
self._data = stream.read()
self._buffer.setData(self._data)
self._buffer.open(QtCore.QIODevice.ReadOnly)
self.player.setMedia(
QtMultimedia.QMediaContent(), self._buffer)
self.player.play()
def handleStateChanged(self, state):
if state == QtMultimedia.QMediaPlayer.StoppedState:
self._buffer.close()
self._data = None
self.button1.setEnabled(True)
self.button2.setEnabled(False)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 50, 640, 480)
window.show()
sys.exit(app.exec_())
UPDATE:
The solution above will only work on Windows and Linux, because there is currently no support for streaming on OSX:
See: Qt Multimedia Backends

Related

Pushing QWidget Window to topmost in Python

I'm new to Python and have mostly learnt C# in the past. I am creating a QWidget class:
class Window(QWidget):
def __init__(self, gif, width, height):
super().__init__()
self.setGeometry(400, 200, width, height)
self.setWindowTitle("Python Run GIF Images")
self.setWindowIcon(QIcon('icons/qt.png'))
label = QLabel(self)
movie = QMovie(gif)
label.setMovie(movie)
movie.start()
And then define a function that creates a QApplication and then the Window:
def run_gif(gif, width, height):
app = QApplication([])
window = Window(gif, width, height)
window.show()
app.exec()
app.shutdown()
My issue is getting the gif to show topmost when it is launched. There is a Topmost property you can set on a Winform in C# which means no other window or other app you click on will cover the Window. This isn't essential but I want it to at least be shown above the code editor it is launched from so that the user doesn't have to select the Window to see the gif contained in the Window. I've spent hours looking at properties I can set either in the class or the method and not getting the result I need.
However, when I call the run_gif method for the second time, it does show it topmost, but along with the below exception (at least I think it's an exception from what I'm reading, it doesn't affect the running of the program other than printing to the console).
QApplication::regClass: Registering window class 'Qt640ScreenChangeObserverWindow' failed. (Class already exists.)
The only advice I can find on this error is not Python-specific and I don't really understand it. Maybe I'm being a bit ambitious for my early stage learning Python but not really seeing anything much on this error.
This is using PySide6.
Using setWindowFlag(Qt.WindowType.WindowStaysOnTopHint, True) seems to be working for me so far.
Example:
from PySide6.QtWidgets import *
from PySide6.QtCore import *
from PySide6.QtGui import *
class Window(QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
self.layout = QVBoxLayout(self)
self.resize(200,100)
self.label = QLabel("Always on top...")
self.layout.addWidget(self.label)
self.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint, True) # <--
if __name__ == '__main__':
app = QApplication([])
window = Window()
window.show()
app.exec()

QProgressDialog only shows after long-running code is finished

I have a program designed according to the MVC model. My view has an open button where I can open files. The files are parsed and a lot of calculation is done with the file contents.
Now I want to display a loader to indicate that the user should wait. I am totally new to Python and Qt. I am using PyQt5 (Qt 5.6.2) with Python 3.6.2.
I added the showLoader() method to the openFiles() method of my view:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, controller, parent = None):
# initializing the window
def showOpenDialog(self):
files, filters = QtWidgets.QFileDialog.getOpenFileNames(self, 'Open file(s)', '',
"Raw files (*.rw.dat);;Data files (*.dat)" +
";;Text files (*.txt);;All files (*)")
self.showLoader("Loading file(s)")
self.doSomeStuffWithTheFiles(files)
self.hideLoader()
def showLoader(self, text):
self._progress = QtWidgets.QProgressDialog(text, "Abort", 0, 0, self);
self._progress.setWindowModality(QtCore.Qt.WindowModal);
This will display the Loader but it will appear after the file is loaded. Not even immediately after the file is loaded, but it will take an additional 1-2 seconds after everything is done (including some repainting of the Window)
I read a lot about threads so I assume the file parsing is blocking the progress loader which makes sense. I read that I should add the QProgressDialog to a slot (I don't really know what that is) but this doesn't help me because I want the QProgressDialog to be displayed after the QFileDialog.
I also read something about adding QtWidgets.QApplication.processEvents() to repaint the Window, but this didn't work for me (or I used it wrong).
So my questions are:
How do I display the QProgressDialog when I call the showLoader() method?
Do I have to execute my calculations and file parsing in a different thread and if have to how do I do this?
If I wanted to display some more information in the QProgressDialog like updating the text and the progress, how do I do this?
Further Question
The solution pointed out by #ekhumoro works fine. I see the loader and the files are parsed correctly. My problem is now that updating my existing MainWindow does not work.
After executing the code I see a little window popping up but it is disappearing instantly. (I had a problem like this and it was about the C++ garbage collector in the background of Qt. But in my understanding the layout should keep a reference to the ParsedDataWidget so this doesn't make sense for me.) Also the ParsedDataWidget is a widget which should be added to the layout "inline" and not appearing as a "window".
# a class that handles the data parsing of each file and creates an
# object that contains all the data with some methods...
class DataParser
def __init__(self, data):
# handle the data
# displaying the parsed data in a fancy way
class ParsedDataWidget(QtWidgets.QWidget)
def __init__(self, data):
# create some UI
# the worker class just like #ekhumoro wrote it (stripped down to
# relevant code)
class Worker(QtCore.QObject):
def run(self):
self._stop = False
for count, file in enumerate(self._files, 1):
# parse the data in the DataParser and create an object
# of the files data
data = DataParser(file)
# works fine, the data is parsed correctly
print(data)
# does not work
window.addParsedData(data)
self.loaded.emit(count, file)
if self._stop:
break
self.finished.emit()
# the window class like mentioned before (or like #ekhumoro wrote it)
class Window(QtWidgets.QWidget):
def __init__(self):
self._data_container = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
self._data_container.setLayout(layout)
def addParsedData(data):
data_widget = ParsedDataWidget(data)
layout = self._data_container.layout()
layout.addWidget(data_widget)
So what do I have to do in order to get the addParsedData method to work?
Edit
I was trying some changings of the code. If I replace the ParsedDataWidget with a QLabel I get the following result:
If i close the window python crashes.
Solution
With some further research I found my problem: You should not use threads with PyQt, you should use SIGNALS instead (written here)
So I changed the code of the worker, I added another SIGNAL called finishedParsing which is emitted if the loading is completed. This SIGNAL
holds the DataParser. The could would look like this:
class Worker(QtCore.QObject):
finishedParsing = QtCore.pyqtSignal(DataParser)
def run(self):
self._stop = False
for count, file in enumerate(self._files, 1):
# parse the data in the DataParser and create an object
# of the files data
data = DataParser(file)
# emit a signal to let the window know that this data is
# ready to use
self.finishedParsing.emit(data)
self.loaded.emit(count, file)
if self._stop:
break
self.finished.emit()
class Window(QtWidgets.QWidget):
def showOpenDialog(self):
if files and not self.thread.isRunning():
# do the opening stuff like written before
self.worker = Worker(files)
#...
self.worker.finishedParsing.connect(self.addParsedData)
This works now!
Below is an example that implements what you asked for. In real usage, the QThread.sleep line should be replaced with a function call that processes each file. This could either be defined as a method of the Worker class, or passed in as an argument to its __init__.
import sys, os
from PyQt5 import QtCore, QtWidgets
class Worker(QtCore.QObject):
loaded = QtCore.pyqtSignal(int, str)
finished = QtCore.pyqtSignal()
def __init__(self, files):
super().__init__()
self._files = files
def run(self):
self._stop = False
for count, file in enumerate(self._files, 1):
QtCore.QThread.sleep(2) # process file...
self.loaded.emit(count, file)
if self._stop:
break
self.finished.emit()
def stop(self):
self._stop = True
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QtWidgets.QPushButton('Choose Files')
self.button.clicked.connect(self.showOpenDialog)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
self.thread = QtCore.QThread()
def showOpenDialog(self):
files, filters = QtWidgets.QFileDialog.getOpenFileNames(
self, 'Open file(s)', '',
'Raw files (*.rw.dat);;Data files (*.dat)'
';;Text files (*.txt);;All files (*)',
'All files (*)')
if files and not self.thread.isRunning():
self.worker = Worker(files)
self.worker.moveToThread(self.thread)
self.worker.finished.connect(self.thread.quit)
self.thread.started.connect(self.worker.run)
self.thread.finished.connect(self.worker.deleteLater)
self.showProgress(
'Loading file(s)...', len(files), self.worker.stop)
self.worker.loaded.connect(self.updateProgress)
self.thread.start()
def updateProgress(self, count, file):
if not self.progress.wasCanceled():
self.progress.setLabelText(
'Loaded: %s' % os.path.basename(file))
self.progress.setValue(count)
else:
QtWidgets.QMessageBox.warning(
self, 'Load Files', 'Loading Aborted!')
def showProgress(self, text, length, handler):
self.progress = QtWidgets.QProgressDialog(
text, "Abort", 0, length, self)
self.progress.setWindowModality(QtCore.Qt.WindowModal)
self.progress.canceled.connect(
handler, type=QtCore.Qt.DirectConnection)
self.progress.forceShow()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 100, 50)
window.show()
sys.exit(app.exec_())

How to embed Selenium Firefox browser into PyQt4 frame

I hope this question isn't too far-fetched. I'm good with Selenium and I've been working with PyQt4 recently. I want to use them both together with a program I'm currently working on and it'd work out a lot more smoothly if I could embed the controllable browser into a Qt4 frame or widget. Can this be done? And if so, how?
It doesn't have to be done with Selenium, I just want to be able to control the browser or at least show a webpage in a Qt widget or frame.
So after some research into methods other people have used, I figured it out.
The code I used came from a "very simple browser" module I obtained from here
I modified the code to be more customizable for my future self.
Here's my modified version of the code:
from PyQt4 import QtCore, QtGui, QtWebKit
class Browser(QtGui.QMainWindow):
def __init__(self, size=[800,600], frame=None, centralWidget=None, default_url='https://www.google.com', backButton=True, forwardButton=True, topBar=True):
"""
Initialize the browser GUI and connect the events
"""
self.showBackButton = backButton
self.showForwardButton = forwardButton
self.showTopBar = topBar
QtGui.QMainWindow.__init__(self)
self.resize(size[0],size[1])
if (centralWidget == None):
self.centralwidget = QtGui.QWidget(self)
else:
self.centralwidget = centralWidget
self.mainLayout = QtGui.QHBoxLayout(self.centralwidget)
self.mainLayout.setSpacing(0)
self.mainLayout.setMargin(1)
if (frame == None):
self.frame = QtGui.QFrame(self.centralwidget)
else:
self.frame = frame
self.gridLayout = QtGui.QVBoxLayout(self.frame)
self.gridLayout.setMargin(0)
self.gridLayout.setSpacing(0)
self.horizontalLayout = QtGui.QHBoxLayout()
if (self.showTopBar):
self.tb_url = QtGui.QLineEdit(self.frame)
if (self.showBackButton):
self.bt_back = QtGui.QPushButton(self.frame)
if (self.showForwardButton):
self.bt_ahead = QtGui.QPushButton(self.frame)
if (self.showBackButton):
self.bt_back.setIcon(QtGui.QIcon().fromTheme("go-previous"))
if (self.showForwardButton):
self.bt_ahead.setIcon(QtGui.QIcon().fromTheme("go-next"))
if (self.showBackButton):
self.horizontalLayout.addWidget(self.bt_back)
if (self.showForwardButton):
self.horizontalLayout.addWidget(self.bt_ahead)
if (self.showTopBar):
self.horizontalLayout.addWidget(self.tb_url)
self.gridLayout.addLayout(self.horizontalLayout)
self.html = QtWebKit.QWebView()
self.gridLayout.addWidget(self.html)
self.mainLayout.addWidget(self.frame)
#self.setCentralWidget(self.centralwidget) --- Not needed when embedding into a frame
if (self.showTopBar):
self.connect(self.tb_url, QtCore.SIGNAL("returnPressed()"), self.browse)
if (self.showBackButton):
self.connect(self.bt_back, QtCore.SIGNAL("clicked()"), self.html.back)
if (self.showForwardButton):
self.connect(self.bt_ahead, QtCore.SIGNAL("clicked()"), self.html.forward)
self.connect(self.html, QtCore.SIGNAL("urlChanged(const QUrl)"), self.url_changed)
self.default_url = default_url
if (self.showTopBar):
self.tb_url.setText(self.default_url)
self.open(self.default_url)
def browse(self):
"""
Make a web browse on a specific url and show the page on the
Webview widget.
"""
if (self.showTopBar):
url = self.tb_url.text() if self.tb_url.text() else self.default_url
self.html.load(QtCore.QUrl(url))
self.html.show()
else:
pass
def url_changed(self, url):
"""
Triggered when the url is changed
"""
if (self.showTopBar):
self.tb_url.setText(url.toString())
else:
pass
def open(self, url):
self.html.load(QtCore.QUrl(url))
self.html.show()
It could use some work at the moment, but I've tested it out and it's doing exactly what I need it to do. I tested it out with the following chunk of code that runs when the script is executed
if (__name__ == "__main__"):
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
window.resize(800,600)
myFrame = QtGui.QFrame(window)
myFrame.resize(200,200)
myFrame.move(10,10)
main = Browser(centralWidget=myFrame, default_url='https://www.google.com/', forwardButton=False, backButton=False, topBar=False)
window.show()
sys.exit(app.exec_())
Like I said, it could use work, but it does exactly what I needed it to do. Now I can embed it into a frame (with the size of my choosing) to use within another application.
Regarding my modifications: I made it possible to keep/remove the back button, forward button and top bar (for the URL). But the webbrowser is still controllable using the "open" function.
And if you wanted to open another webpage, it's as simple as the following
main.open('https://your.webpage.here.com')

Must construct a QApplication before a QPaintDevice

My problem is that when I'm using Spyder, code that was working fine before I had to re-install Spyder now requires me to re-start Spyder (quitting Python x,y, too) every time I run it.
I thought I had this solved yesterday, but for some reason today my problem has returned.
Someone suggested this answer yesterday, and it worked...momentarily.
I have tried:
import sys
from PyQt4 import QtGui
class CompiledWindow(QtGui.QWidget):
def __init__(self, parent = None):
......
cw = CompiledWindow()
cw.show()
The error I get is:
QWidget: Must construct a QApplication before a QPaintDevice
Then I went back to this (same as before the dots)
app = QtGui.QApplication(sys.argv)
cw = CompiledWindow()
cw.show()
Now it runs fine the FIRST time I run it and the second time, I get the same error as before. The third time, it tells me I don't have a shell selected.
I really don't fundamentally understand what it's complaining about. If I must construct a QApplication before a QPaintDevice, why does it run OK the first time? Clearly, it's happening the first time. Why do subsequent runs give me errors? Also, When I got rid of the app part as suggested in the linked answer, it worked...until this morning.
ETA This is a minimal working example as requested. Thanks!
import sys
from PyQt4 import QtGui
class CompiledWindow(QtGui.QWidget):
def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent)
self.setWindowTitle('Enter data below to generate report')
names = ['Name Report', 'Open Logo Picture']
grid = QtGui.QGridLayout(self)
self.report_title_ask = QtGui.QLineEdit()
self.rt = QtGui.QLabel()
self.rt.setText(names[0])
grid.addWidget(self.report_title_ask, 0, 1)
grid.addWidget(self.rt, 0, 0)
self.Logo_button = QtGui.QPushButton(names[1], self)
self.LogoLabel = QtGui.QLabel()
self.Logo_button.clicked.connect(self.logo_pic)
grid.addWidget(self.Logo_button, 1, 0)
grid.addWidget(self.LogoLabel, 1,1)
self.done_button = QtGui.QPushButton()
self.done_button.setText('Click this when all data is entered')
self.done_button.clicked.connect(self.finish_input_click)
grid.addWidget(self.done_button, 2,1)
def rt_button_click(self):
self.report_title = self.report_title_ask.text()
def logo_pic(self):
self.Logo_picture = unicode(QtGui.QFileDialog.getOpenFileName())
self.LogoLabel.setText(self.Logo_picture)
def finish_input_click(self):
self.report_title = self.report_title_ask.text()
self.close()
app = QtGui.QApplication(sys.argv)
cw = CompiledWindow()
cw.show()

Unable to seek video in PyQt4

I wrote a simple video player using Phonon in PyQt4. The videos are playing fine. But I am unable to seek the video to given position. This the code I've written:
#!/usr/bin/python
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.phonon import Phonon
import sys
class VideoPlayer(QWidget):
def __init__(self, address, parent = None):
self.address = address
QWidget.__init__(self)
self.player = Phonon.VideoPlayer(Phonon.VideoCategory, self)
self.player.load(Phonon.MediaSource(self.address))
window = QHBoxLayout(self)
window.addWidget(self.player)
self.setWindowTitle("Simple Video Player")
self.player.play()
self.player.seek(10240)
app = QApplication(sys.argv)
vp = VideoPlayer(sys.argv[1])
vp.show()
app.exec_()
All I am trying to do is to start and stop a video at given positions.
Thanks in advance.
It is not possible to seek to a position in a media source whilst it is still loading.
So connect a handler to the media object's stateChanged signal, and wait until it's state changes to PlayingState before attempting to seek.
self.player.mediaObject().stateChanged.connect(self.handleStateChanged)
...
def handleStateChanged(self, newstate, oldstate):
if newstate == Phonon.PlayingState:
self.player.seek(10240)
Some media isn't easily seekable by Phonon. The documentation says
Note that the backend is free to ignore the seek request if the media source isn't seekable; you can check this by asking the media object of the VideoPlayer.
player->mediaObject()->isSeekable();
My guess is that your video isn't seekable.
What media are you using? Things like streaming video (for example), generally aren't seekable.

Categories

Resources