Always get 0 when using QMediaPlayer.duration() - python

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.

Related

QMediaPlayer crashes when created in response to drop event

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()

How QMediaPlayer.setPosition works for mp3?

I got a problem when I try to play a song from a certain position: it does'nt work (the song plays from the beginning).
This problem only occurs when the song is a 'mp3' song, not a 'm4a' one (they're the only format I tested).
The problem seems to come from qt (or PyQt ?) but I'm not sure, here's a minimal example, do I miss something ?
from PyQt5.QtWidgets import QApplication
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtCore import QUrl
if __name__ == "__main__":
app = QApplication([])
player = QMediaPlayer()
media_content = QMediaContent(QUrl.fromLocalFile("path_to_my_music_file.mp3"))
player.setMedia(media_content)
player.setPosition(10000)
player.play()
app.exec_()
setMedia() is asynchronous:
Note: This function returns immediately after recording the specified source of the media. It does not wait for the media to finish loading and does not check for errors. Listen for the mediaStatusChanged() and error() signals to be notified when the media is loaded and when an error occurs during loading.
It's possible that, due to the nature of MP3 files, Qt takes some time before being able to correctly seek. Unfortunately, as far as I can tell, this can only be done after some amount of time has passed after playing the file.
A possible solution is to connect to a custom function that delays the setPosition until the media becomes seekable.
This is a subclass that should take care of it (I only tested with mp3 files, so you should try it with other file types to ensure that it works properly).
class Player(QMediaPlayer):
_delayedPos = 0
def setPosition(self, pos):
super().setPosition(pos)
if pos and not self.isSeekable():
self._delayedPos = pos
try:
# ensure that the connection is done only once
self.seekableChanged.connect(self.delaySetPosition, Qt.UniqueConnection)
except:
pass
else:
self._delayedPos = 0
def delaySetPosition(self, seekable):
if seekable:
self.setPosition(self._delayedPos)
try:
# just to be safe, in case the media changes before the previous one
# becomes seekable
self.seekableChanged.disconnect(self.delaySetPosition)
except:
pass
Following #musicamante answer ; a more robust version would be to use one of the following signals :
mediaStatusChanged(QMediaPlayer::MediaStatus status)
mediaChanged(const QMediaContent &media)
currentMediaChanged(const QMediaContent &media)
The best option being mediaStatusChanged which allows you to check the QMediaPlayer::MediaStatus :
from PyQt5.QtWidgets import QApplication
from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
from PyQt5.QtCore import QUrl
def update_position(status):
if(status == QMediaPlayer.LoadedMedia):
player.setPosition(10000)
player.play()
if __name__ == "__main__":
app = QApplication([])
player = QMediaPlayer()
media_content = QMediaContent(QUrl.fromLocalFile("path_to_my_music_file.mp3"))
player.mediaStatusChanged.connect(update_position)
player.setMedia(media_content)
app.exec_()

How to can I add threading to PyQt5 GUI?

So I have created a GUI using QT Designer. It works pretty well, but on more complex calls it doesn't update the main window and locks up. I want to run my CustomComplexFunction() while updating a textEdit in the main window from constantly changing backend information, and I wanted it to run every 2 seconds. The following code seems right and runs without errors, but doesn't update the textEdit. Please note i'm importing a .ui file designed from QT Designer with a pushButton and textEdit and the code won't run without it.
Main.py
import sys
from PyQt5.QtWidgets import QDialog, QApplication, QPushButton, QVBoxLayout, QMainWindow
from PyQt5.QtCore import QCoreApplication, QObject, QRunnable, QThread, QThreadPool, pyqtSignal, pyqtSlot
from PyQt5 import uic, QtGui
class Worker(QObject):
newParams = pyqtSignal()
#pyqtSlot()
def paramUp(self):
x=1
for x in range(100):
time.sleep(2)
self.newParams.emit()
print ("sent new params")
x +=1
Ui_somewindow, _ = uic.loadUiType("mainwindow.ui") #the path to UI
class SomeWindow(QMainWindow, Ui_somewindow, QDialog):
def __init__(self):
QMainWindow.__init__(self)
Ui_somewindow.__init__(self)
self.setupUi(self)
# Start custom functions
self.params = {}
self.pushButton.clicked.connect(self.complex) #NumEvent
def complex(self):
self.work = Worker()
self.thread = QThread()
self.work.newParams.connect(self.changeTextEdit)
self.work.moveToThread(self.thread)
self.thread.start()
self.CustomComplexFunction()
def CustomComplexFunction(self):
self.params['City'] = 'Test'
def changeTextEdit(self):
try:
City = self.params['City']
self.textEdit_14.setPlainText(City)
except KeyError:
City = None
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SomeWindow()
window.show()
sys.exit(app.exec_())
You can see the official docs for Signals and Slots here and this SO post was also very helpful, but it seems like I built it correctly. According to the docs, the emitter doesn't care if the signal is used. This might be why the code doesn't have errors but doesn't work either.
Any ideas on how to make it work? Or atleast some way to test the emitter and signals??
You have forgot to connect the thread to the worker object.
self.work = Worker()
self.thread = QThread()
self.thread.started.connect(self.worker.work) # <--new line, make sure work starts.
self.thread.start()
Good luck with the application :-)

How to call a method asynchronously in PyQt5 using Python3?

How to call a method asynchronously in PyQt5 using Python3?
I have tried to use a signal to do it.
import sys
from time import sleep
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel
class MainWindow(QMainWindow):
asyncFuncSignal = pyqtSignal()
def __init__(self):
super().__init__()
self.initUi()
def initUi(self):
self.label = QLabel(self)
self.label.setText("loading...")
# Trying to call 'self.asyncFunc' asynchronously
self.asyncFuncSignal.connect(self.asyncFunc)
self.asyncFuncSignal.emit()
print("loaded")
def asyncFunc(self):
# Doing something hard that takes time
# I have used 'sleep' to implement the delay
sleep(2)
self.label.setText("done")
print("asyncFunc finished")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
This program tries to finish asyncFunc before writing "loaded". But I would like the program to finish initUi immediately with shown loading... in the label and after that the text done appears in 2 seconds.
What is the best and the shortest way to do it?
It's said here http://pyqt.sourceforge.net/Docs/PyQt5/signals_slots.html I can use queued connections but I haven't found an example how to implement it.
PyQt5.QtCore.Qt.QueuedConnection may help. Just replace
self.asyncFuncSignal.connect(self.asyncFunc)
with
from PyQt5.QtCore import Qt
...
self.asyncFuncSignal.connect(self.asyncFunc, Qt.QueuedConnection)

PyQt - QPushButton loop

I'm new to Python and PyQt so sorry if I can't describe my problems clearly. I want to do a cinema seat selector UI and this is the code I have made below:
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import (QWidget, QApplication)
class Ui2(QWidget):
def __init__(self):
super(Ui2, self).__init__()
uic.loadUi('seat.ui', self)
self.A1.setStyleSheet("background-color: red")
self.B1.clicked.connect(self.greenButton)
self.show()
def greenButton(self):
self.B1.setStyleSheet("background-color: green")
self.B1.clicked.connect(self.whiteButton)
def whiteButton(self):
self.B1.setStyleSheet("background-color: white")
self.B1.clicked.connect(self.greenButton)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Ui2()
sys.exit(app.exec_())
The problem is, if I click the button B1 multiple times the program will freeze - I read it somewhere that this is caused by full memory.
Also this is only for the button B1, what should I do if I want to implement the same functions for all buttons?
Thanks a lot!
You shouldn't call self.B1.clicked.connect so many times. Each time you call that function, it registers another event handler. When the button is clicked, all the event handlers that have ever been registered get called. So as you keep clicking, each click causes more and more things to happen. Eventually it fails.
In general you want to try to have one handler for each event. Here is one simple way to do that:
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import (QWidget, QApplication)
class Ui2(QWidget):
def __init__(self):
super(Ui2, self).__init__()
uic.loadUi('seat.ui', self)
self.b1_color = "green"
self.A1.setStyleSheet("background-color: red")
self.B1.clicked.connect(self.onButton)
self.show()
def onButton(self):
if self.b1_color == "green":
self.b1_color = "white"
else:
self.b1_color = "green"
self.B1.setStyleSheet("background-color: " + self.b1_color)
Tested with PySide and Qt4.8, but it should still work in your environment (I hope).

Categories