Unable to seek video in PyQt4 - python

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.

Related

Play audio with PyQt5

If in playAudioFile is performed, icon has changed but play() method dosen't work. Every time the condition is false.
This code works in some files, but I can't see a rule. Format files and file's long doesn't matter. I tried with .wav, .mp3 and .mp4 for a try.
In windows player every files work.
import sys
import os
from PyQt5.QtCore import QUrl
from PyQt5.uic import loadUi
from PyQt5.QtWidgets import QApplication, QStackedWidget, QMainWindow, QStyle
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
class Term2(QMainWindow):
def __init__(self):
super().__init__()
loadUi('screen2.ui', self)
# create player
self.player = QMediaPlayer()
self.choose_music()
self.btn_play.clicked.connect(self.playAudioFile)
# play audio
def choose_music(self):
filepath = os.path.join(os.getcwd(), 'pc.wav')
url = QUrl.fromLocalFile(filepath)
content = QMediaContent(url)
self.player.setMedia(content)
print(filepath)
def playAudioFile(self):
if self.player.state() == QMediaPlayer.PlayingState:
self.player.pause()
self.btn_play.setIcon(
self.style().standardIcon(QStyle.SP_MediaPlay)
)
else:
self.player.play()
self.btn_play.setIcon(
self.style().standardIcon(QStyle.SP_MediaPause)
)
app = QApplication(sys.argv)
widget = QStackedWidget()
t2 = Term2()
widget.setWindowTitle('Project)
widget.setFixedWidth(700)
widget.setFixedHeight(400)
widget.addWidget(t2)
widget.show()
sys.exit(app.exec_())
I created button in Qt Designer and import in loadUi('screen2.ui', self)
I would use an audio player from a different library, that one appears quite old. I would recommend looking into playsound or the pygame audio mixer.
Playsound works like this:
from playsound import playsound
playsound(path_to_audio_file)
It is quite limited in abilities, but is simple, reliable, and works with all mp3 and wav formats. Here is a tutorial: https://www.geeksforgeeks.org/play-sound-in-python/
If you want something a little more advanced I would try out pygames audio mixer, you can play stuff, control volumes, timings and more. At its most basic:
from pygame import mixer
mixer.init()
mixer.music.load(path_to_audio_file)
mixer.music.play()
The best format for the mixer is wav, but others would probably work.
A tutorial for this one:
https://www.geeksforgeeks.org/python-playing-audio-file-in-pygame/

PyQt6 QMediaPlayer and QAudioOutput not behaving as expected

I've been having an issue migrating to PyQt6 with QAudioOutput and QMediaPlayer where the QMediaPlayer object seems to not work with any QAudioOutput I make. If I set a QAudioOutput object the video will fail to render and the event loop gets sluggish like buggy things are happening. Also the QMediaPlayer does not seem to be incrementing the QAudioOutput object's reference counter when QMediaPlayer.setAudioOutput is used, because unless I keep a reference to the object myself it gets cleared.
Here is some demo code:
import sys
from PyQt6.QtWidgets import QMainWindow, QApplication
from PyQt6.QtCore import QUrl
from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput
from PyQt6.QtMultimediaWidgets import QVideoWidget
class MainWin(QMainWindow):
def __init__(self, file_path):
super(MainWin, self).__init__()
self.cent_wid = QVideoWidget()
self.setCentralWidget(self.cent_wid)
self.player = QMediaPlayer()
self.audio_output = QAudioOutput()
#self.player.setAudioOutput(self.audio_output)
self.audio_output.setVolume(1.0)
self.player.setVideoOutput(self.cent_wid)
self.file_path = file_path
def showEvent(self, a0) -> None:
super(MainWin, self).showEvent(a0)
self.player.setSource(QUrl.fromLocalFile(self.file_path))
self.player.play()
if __name__ == '__main__':
app = QApplication([])
frm = MainWin(sys.argv[1])
frm.show()
app.exec()
For me, the above will run and play the video file (first argument for path), but the "player.setAudioOutput" is commented out. If it is uncommented then the player will fail. I've tried manually setting the QAudioDevice and PyQt (6.2.3, 6.2.2). Despite messing around for quite a while I can't get anything to work. Any ideas?
Although not a solution to this problem I have determined that the issue is with the vorbis audio codec on windows. Since Qt dropped DirectShow and only supports WMF this caused an issue on my computer. Unfortunately, I have not been able to get Qt to cooperate with any attempts to install codecs. Not 3rd party codecs or the "Web Media Extensions" from ms store. Below is some code that appears to prove that the vorbis codec is the issue (along with only files needing that codec breaking Qt):
from PyQt6.QtMultimedia import QMediaFormat
mf = QMediaFormat()
for codec in mf.supportedAudioCodecs(QMediaFormat.ConversionMode.Decode):
print(codec.name)

QMovie does not start if started from a thread

fellow developers! I have a question regarding Qt and multithreading.
=== SHORT VERSION ===========================================
Is it possible to do what I want with Qt? That is (1) show a loader; (2) download a gif in the background; (3) show the downloaded gif in the main window after it has been downloaded?
=== LONG VERSION ============================================
I have this idea that when I push a button, it:
shows a loader;
activates a thread that downloads a gif from the web;
replaces the default gif hidden in the main window with the downloaded one and shows it
hides the loader;
The problem that I'm experiencing is that when the downloaded gif is show, it is "frozen" or just the first frame is shown. Other than that everything is fine. However, it need the gif to be animated after the loader is hidden.
It is mentioned here that
So the Qt event loop is responsible for executing your code in response to various things that happen in your program, but while it is executing your code, it can't do anything else.
I believe this is the heart of the problem. It is also suggested that
It is recommended to use a QThread with Qt rather than a Python thread, but a Python thread will work fine if you don't ever need to communicate back to the main thread from your function.
Since my thread replaced the contents of the default gif, I believe that it does communicates back :(
Please find my code below :)
import sys
from time import sleep
from PyQt5.QtCore import QThread
from PyQt5.QtGui import QMovie
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel
class ChangeGif(QThread):
"""
A worker that:
1 - waits for 1 second;
2 - changes the content of the default gif;
3 - shows the default gif with new contents and hide the loader.
"""
def __init__(self, all_widgets):
QThread.__init__(self)
self.all = all_widgets
def run(self):
sleep(1)
self.new_gif_file = QMovie("files/new.gif") # This is the "right" gif that freezes :(
self.new_gif_file.start()
self.all.gif_label.setMovie(self.new_gif_file)
self.all.gif_label.show()
self.all.loader_label.hide()
class MainWindow(QWidget):
"""
Main window that shows a button. If you push the button, the following happens:
1 - a loader is shown;
2 - a thread in background is started;
3 - the thread changes the contents of the default gif;
4 - when the gif is replaced, the loader disappears and the default gif with the new content is shown
"""
def __init__(self):
super(MainWindow, self).__init__()
# BUTTON
self.button = QPushButton("Push me", self)
self.button.setFixedSize(100, 50)
self.button.clicked.connect(lambda: self.change_gif())
# DEFAULT GIF
self.gif_file = QMovie("files/default.gif") # This is the "wrong" gif
self.gif_file.start()
self.gif_label = QLabel(self)
self.gif_label.setMovie(self.gif_file)
self.gif_label.move(self.button.width(), 0)
self.gif_label.hide()
# LOADER
self.loader_file = QMovie("files/loader.gif")
self.loader_file.start()
self.loader_label = QLabel(self)
self.loader_label.setMovie(self.loader_file)
self.loader_label.move(self.button.width(), 0)
self.loader_label.hide()
# WINDOW SETTINGS
self.setFixedSize(500, 500)
self.show()
def change_gif(self):
self.loader_label.show()
self.worker = ChangeGif(self)
self.worker.start()
app = QApplication(sys.argv)
window = MainWindow()
app.exec_()
Other than the fact that no UI elements should be ever accessed or created outside the main Qt thread, this is valid also for UI elements that use objects created in other threads.
In your specific case, this not only means that you cannot set the movie in the separate thread, but you cannot create the QMovie there either.
In the following example, I'm opening a local file, and use a signal to send the data to the main thread. From there, I create a QBuffer to store the data in a IO device that can be used by QMovie. Note that both the buffer and the movie must have a persistent reference, otherwise they will be garbage collected as soon as the function returns.
from PyQt5.QtCore import QThread, QByteArray, QBuffer
class ChangeGif(QThread):
dataLoaded = pyqtSignal(QByteArray)
def __init__(self, all_widgets):
QThread.__init__(self)
self.all = all_widgets
def run(self):
sleep(1)
f = QFile('new.gif')
f.open(f.ReadOnly)
self.dataLoaded.emit(f.readAll())
f.close()
class MainWindow(QWidget):
# ...
def change_gif(self):
self.loader_label.show()
self.worker = ChangeGif(self)
self.worker.dataLoaded.connect(self.applyNewGif)
self.worker.start()
def applyNewGif(self, data):
# a persistent reference must be kept for both the buffer and the movie,
# otherwise they will be garbage collected, causing the program to
# freeze or crash
self.buffer = QBuffer()
self.buffer.setData(data)
self.newGif = QMovie()
self.newGif.setCacheMode(self.newGif.CacheAll)
self.newGif.setDevice(self.buffer)
self.gif_label.setMovie(self.newGif)
self.newGif.start()
self.gif_label.show()
self.loader_label.hide()
Note that the example above is just for explanation purposes, as the downloading process can be done using QtNetwork modules, which work asynchronously and provide simple signals and slots to download remote data:
from PyQt5.QtCore import QUrl
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
class MainWindow(QWidget):
def __init__(self):
# ...
self.downloader = QNetworkAccessManager()
def change_gif(self):
self.loader_label.show()
url = QUrl('https://path.to/animation.gif')
self.device = self.downloader.get(QNetworkRequest(url))
self.device.finished.connect(self.applyNewGif)
def applyNewGif(self):
self.loader_label.hide()
self.newGif = QMovie()
self.newGif.setDevice(self.device)
self.gif_label.setMovie(self.newGif)
self.newGif.start()
self.gif_label.show()
The main rule working with Qt is that only one main thread is responsible for manipulating UI widgets. It is often referred to as GUI thread. You should never try to access widgets from another threads. For example, Qt timers won't start activated from another thread and Qt would print warning in the runtime console. In your case - if you put all the manipulation with the QMovie in the GUI Thread, most probably everything will work as expected.
What to do? Use the signals and slots - they are also designed to work between threads.
What your code should do:
Show a loader form the main thread.
Activate a thread that downloads a gif from the web.
After the download is ready - emit a signal and capture it in main GUI thread'. Remember to use Qt::QueuedConnection` when connecting the signal and the slot, though it will be used automatically in some cases.
In the receiving slot replace the default gif in main window with the downloaded one and show it and hide the loader.
You'll have to use some synchronization mechanism to avoid data-racing. A mutex will be enough. Or you could pass the data as the signal-slot parameter, but in case of a gif it probably would be too big.

How to play a mp4 file in wx.media

I want to do an mp4 player and I am trying to get the module to play a video but at the moment of giving it no error occurs
def prueba(self,z):
self.open = wx.media.MediaCtrl(panel)
self.open.Load("video.mp4")
self.open.GetBestSize()
self.open.Play()
panel = wx.Panel(frame,id=-1,size=(500,500))
self.Bind(wx.media.EVT_MEDIA_LOADED,self.prueba)
Edit:
When I´m trying the following code:
import wx
import wx.media
class TestPanel(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.testMedia = wx.media.MediaCtrl(self, style=wx.SIMPLE_BORDER,)
self.media = '/home/rolf/BBB.ogv'
self.testMedia.Bind(wx.media.EVT_MEDIA_LOADED, self.play)
self.testMedia.Bind(wx.media.EVT_MEDIA_FINISHED, self.quit)
if self.testMedia.Load(self.media):
pass
else:
print("Media not found")
self.quit(None)
def play(self, event):
self.testMedia.Play()
def quit(self, event):
self.Destroy()
if __name__ == '__main__':
app = wx.App()
Frame = TestPanel()
Frame.Show()
app.MainLoop()
With an mp4 file...
ERROR
You need to load the media before the event EVT_MEDIA_LOADED will fire.
Try this as a skeleton program:
import wx
import wx.media
class TestPanel(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.testMedia = wx.media.MediaCtrl(self, style=wx.SIMPLE_BORDER,)
self.media = '/home/rolf/BBB.ogv'
self.testMedia.Bind(wx.media.EVT_MEDIA_LOADED, self.play)
self.testMedia.Bind(wx.media.EVT_MEDIA_FINISHED, self.quit)
if self.testMedia.Load(self.media):
pass
else:
print("Media not found")
self.quit(None)
def play(self, event):
self.testMedia.Play()
def quit(self, event):
self.Destroy()
if __name__ == '__main__':
app = wx.App()
Frame = TestPanel()
Frame.Show()
app.MainLoop()
Edit based on comment
The above code works on Linux.
If you are using a different OS or if you are on Linux but do not have Gstreamer installed, you may have an issue.
There is an option to choose your media backend:
Generally, you should almost certainly leave this part up to wx.media.MediaCtrl - but if you need a certain backend for a particular reason, such as QuickTime for playing .mov files, all you need to do to choose a specific backend is to pass the name of the backend class to wx.media.MediaCtrl.Create . The following are valid backend identifiers:
MEDIABACKEND_DIRECTSHOW: Use ActiveMovie/DirectShow. Uses the native ActiveMovie (I.E. DirectShow) control. Default backend on Windows and supported by nearly all Windows versions, even some Windows CE versions. May display a windows media player logo while inactive.
MEDIABACKEND_QUICKTIME: Use QuickTime. Mac Only. WARNING: May not working correctly embedded in a wx.Notebook.
MEDIABACKEND_GSTREAMER, Use GStreamer. Unix Only. Requires GStreamer 0.8 along with at the very least the xvimagesink, xoverlay, and gst-play modules of gstreamer to function. You need the correct modules to play the relevant files, for example the mad module to play mp3s, etc.
MEDIABACKEND_WMP10, Uses Windows Media Player 10 (Windows only) - works on mobile machines with Windows Media Player 10 and desktop machines with either Windows Media Player 9 or 10.
This would result in your media defintion looking like this:
self.testMedia = wx.media.MediaCtrl(self, style=wx.SIMPLE_BORDER,szBackend=wx.media.MEDIABACKEND_WMP10)
for example.

PyQt5 -- Unable to playback video from stream

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

Categories

Resources