Play mp3 using Python, PyQt, and Phonon - python

I been trying all day to figure out the Qt's Phonon library with Python.
My long term goal is to see if I could get it to play a mms:// stream, but since I can't find an implementation of this done anywhere, I will figure that part out myself. (figured I'd put it out there if anyone knew more about this specifically, if not no big deal.)
Anyway, I figured I'd work backwards from a working example I found online. This launches a file browser and will play the mp3 file specified. I wanted to strip out the file browser stuff and get it down to the essentials of executing the script and having it play an Mp3 file with a hardcoded path.
I'm assuming my problem is a misunderstanding of setCurrentSource() and specifying the data types. (see: http://www.riverbankcomputing.co.uk/static/Docs/PyQt4/html/phonon-mediasource.html#fileName)
I'm keeping my question kind of broad because ANY help with understanding Phonon would be greatly appreciated.
import sys
from PyQt4.QtGui import QApplication, QMainWindow, QDirModel, QColumnView
from PyQt4.QtGui import QFrame
from PyQt4.QtCore import SIGNAL
from PyQt4.phonon import Phonon
class MainWindow(QMainWindow):
m_model = QDirModel()
def __init__(self):
QMainWindow.__init__(self)
self.m_fileView = QColumnView(self)
self.m_media = None
self.setCentralWidget(self.m_fileView)
self.m_fileView.setModel(self.m_model)
self.m_fileView.setFrameStyle(QFrame.NoFrame)
self.connect(self.m_fileView,
SIGNAL("updatePreviewWidget(const QModelIndex &)"), self.play)
def play(self, index):
self.delayedInit()
self.m_media.setCurrentSource(
Phonon.MediaSource(self.m_model.filePath(index)))
self.m_media.play()
def delayedInit(self):
if not self.m_media:
self.m_media = Phonon.MediaObject(self)
audioOutput = Phonon.AudioOutput(Phonon.MusicCategory, self)
Phonon.createPath(self.m_media, audioOutput)
def main():
app = QApplication(sys.argv)
QApplication.setApplicationName("Phonon Tutorial 2 (Python)")
mw = MainWindow()
mw.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

Phonon supports different audio file formats on different platforms, using the system's own support for media formats, so it could be that your system doesn't provide libraries for playing MP3 files. Certainly, MP3 is not supported out of the box on some Linux distributions. If you are using Linux, please take a look at the following page for information about enabling MP3 support:
http://doc.qt.io/qt-4.8/phonon-overview.html#linux
Another way to diagnose problems with Phonon's media formats is to run the Capabilities example provided with Qt:
http://doc.qt.io/qt-4.8///qt-phonon-capabilities-example.html
This should tell you which media formats are supported by your system.

In delayedInit method; create MediaObject like following:
def delayedInit(self):
if not self.m_media:
self.m_media = Phonon.createPlayer(Phonon.MusicCategory)

If Phonon is not outputting audio or video, but not throwing any errors. You might just have to sudo apt-get install phonon-backend-gstreamer and also maybe sudo apt-get install libphonon-dev
Phonon uses a backend of gstreamer or vlc silently, so when its not there, no errors but no functionality either.
after running those commands I was able to hear sound from phonon on my raspberry pi
Hopefully this will help someone in the future.

Related

Kivy Audio Will Not Seek, Win 11

According to the documentation Kivy 2.0.0 audio will likely not seek unless played first. However, I cannot get it to seek at all with this simple code on Windows 11. It always plays from the beginning and the position is always 0,
from kivy.core.audio import SoundLoader
from kivy.clock import Clock
sound = SoundLoader.load('test.mp3')
def play():
sound.play()
sound.seek(5)
print(sound.get_pos())
if sound:
print("Sound found at %s" % sound.source)
print("Sound is %.3f seconds" % sound.length)
#Clock.schedule_once(play, .3)
play()
Also, I tried to schedule a delay using Clock and the audio does not play at all. What am I missing?
Edit:
It was suggested that this code needs to be wrapped in an app to work. That did fix the clock delay issue, but the player still will not seek ahead,
from kivy.core.audio import SoundLoader
from kivy.clock import Clock
from kivy.app import App
from kivy.uix.button import Button
class MainApp(App):
sound = SoundLoader.load('test.mp3')
def build(self):
return Button(text='Play', on_press = self.clock)
def clock(self, evt):
Clock.schedule_once(self.play, 1)
def play(self, evt):
self.sound.play()
self.sound.seek(5)
#self.sound.play()
print(self.sound.get_pos())
MainApp().run()
After digging into the Kivy source code I found out why Kivy audio will not seek.
The default Kivy audio provider on Windows is SDL2 and when I looked at the source (kivy/core/audio/audio_sdl2.pyx) I found it does NOT have a seek method. Only "play" and "stop." In the init.py file the "seek" method returns "pass." However, the audio_ffpyplayer.py files DOES have a "seek" method. If one has ffpyplayer installed on his system Kivy will load it and seek will work, but with the default install it will not.
(It would be good if the Kivy Docs would reflect this. It would have saved me and others, I'm sure, a lot of "hair pulling.")
I tried ffpyplayer and it works good, but it has a lot of bloat. When packaged with pyinstaller there is around 40MB of added dlls. Since Windows already has the files needed to play audio, this bloat is redundant. While looking for a Python tool to play Windows audio using existing dlls, I found mp3play (https://github.com/michaelgundlach/mp3play). It is old but works flawlessly using only Window's native audio dlls and adds essentially no bloat. I think "playsound" will work as well.

periodic polling of port or hardware IO point on Raspberry Pi

In developing an application using Qt5 with Python, you are generally event driven. No sweat, works like a charm. However, there are instances when you need to poll the status of some hardware GPIO (i.e. a button push), or get some information from a serial port, or something like a gpsd daemon.
What is the preferred way to handle this? Via a QTimer, say, running every 50 msec? Or is there some other method I haven't found? Is it better to set up a trigger on a GPIO pi (https://www.ics.com/blog/control-raspberry-pi-gpio-pins-python) or is there any conflict with the Qt5 Gui?
Basic documentation doesn't look horrible, and I can follow some examples, of course, but didn't know if there was a better/canonical/more Pythonic method.
https://doc.qt.io/qtforpython/PySide2/QtCore/QTimer.html
https://python-catalin.blogspot.com/2019/08/python-qt5-qtimer-class.html
I don't think there is a pythonic solution, not because you can't use python but because python is not relevant to the topic. And there is no canonical solution either, everything will depend on the application.
From my experience I have found it much easier to reuse libraries that handle GPIOs like Rpi.GPIO or gpiozero. These libraries have as a strategy to create threads where the state of the pins is monitored, so you cannot use the callbacks directly to update the GUI but you must implement wrapper(see this for example).
trivial example:
import sys
from gpiozero import Button
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QUrl
from PyQt5.QtWidgets import QMainWindow, QApplication
class ButtonManager(QObject):
pressed = pyqtSignal()
def __init__(self, parent=None):
super(ButtonManager, self).__init__(parent)
self._button = Button(20)
self._button.when_pressed = self._on_when_pressed
def _on_when_pressed(self):
self.pressed.emit()
class MainWindow(QMainWindow):
#pyqtSlot()
def on_pressed(self):
print("pressed")
def main():
app = QApplication(sys.argv)
w = MainWindow()
button_manager = ButtonManager()
button_manager.pressed.connect(w.on_pressed)
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
If you are going to use serial ports then the best option is to use Qt Serial Port since unlike pyserial it is not necessary to use threads but the notification is through signals(See this for example)
Using a QTimer is another option.

Using qtDesigner with python seamlessly [duplicate]

This question already has answers here:
QtDesigner changes will be lost after redesign User Interface
(2 answers)
Closed 4 years ago.
I've been looking for a better way to work with frontends made using qtDesigner connected to a python backend. All the methods I have found have the following form:
Make GUI in designer
Output to python code using pyuic (usually with -x option)
Write backend code inside this output file
This methodology is not simple to maintain or edit. Any time you change the UI, it completely breaks workflow: you have to reconvert, generate a new file, fix that file back up to where you were before, then finally get back on track. This requires a lot of manual copy-paste of code, which is an invitation to errors on multiple levels (newly generated file layout may not be the same, manually fixing name changes while pasting, etc.). You can also end up losing work if you aren't careful, since you could accidentally overwrite the file and destroy the backend code.
Also, this doesn't use any of the control in qtDesigner like the Signal/Slot and Action editors. These must be here for something, but I can't find a way to actually direct these to call backend functions.
Is there a better way to work with this, possibly using the features of qtDesigner?
You don't have to add your code in the output file :
If you take a 'home.py' generated by PYUIC, containing a QMainWindow which name set by QtDesigner/generated by Puic would be Ui_Home(), your main code could be :
from home import Ui_Home
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class window_home(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
#set up the user interface from Designer
self.ui = Ui_Home()
self.ui.setupUi(parent)
#then do anything as if you were in your output file, for example setting an image for a QLabel named "label" (using QtDesigner) at the root QMainWindow :
self.ui.label.setPixmap(QPixmap("./pictures/log.png"))
def Home():
f=QMainWindow()
c=window_home(f)
f.show()
r=qApp.exec_()
if __name__=="__main__":
qApp=QApplication(sys.argv)
Home()
I found an even cleaner method for working with this, that does not require preemptive conversion after each edit at all. Instead it takes the .ui file itself, so all you need to do is restart the program itself to update the design.
import sys
import os
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5 import uic
path = os.path.dirname(__file__) #uic paths from itself, not the active dir, so path needed
qtCreatorFile = "XXXXX.ui" #Ui file name, from QtDesigner, assumes in same folder as this .py
Ui_MainWindow, QtBaseClass = uic.loadUiType(path + qtCreatorFile) #process through pyuic
class MyApp(QMainWindow, Ui_MainWindow): #gui class
def __init__(self):
#The following sets up the gui via Qt
super(MyApp, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
#set up callbacks
self.ui.NAME OF CONTROL.ACTION.connect(self.test)
def test(self):
#Callback Function
if __name__ == "__main__":
app = QApplication(sys.argv) #instantiate a QtGui (holder for the app)
window = MyApp()
window.show()
sys.exit(app.exec_())
Note that this is Qt5. Qt5 and Qt4 are not API compatible, so it will be a little different in Qt4 (and presumably earlier as well).

PyQt5 QListView sluggish first drag/drop on windows - python 3.5/3.4 32bit, pyqt5 from sourceforge

I have installed 32bit Python 3.4 and 3.5 and PyQt5 on our windows 7 work machine via the executable available from https://sourceforge.net/projects/pyqt/, however I now find that when I run my simple drag and drop test code it is very sluggish moving the first element (the ui freezes for about 4-5 seconds before completing the move). All subsequent drag and drop operations happen without that delay.
By "ui freezes" I mean that the selection remains highlit and in its original place when i move the cursor away and the drag and drop guide graphics (a line appearing where the items would move to if i let the mouse button go at that time, the mouse cursor changing to a different icon to indicate that drag and drop is occuring) do not appear. If I release the mouse button during this time the selected elements are moved to that location but not until the 4-5 second wait time has elapsed.
The code is as follows:
from PyQt5.QtWidgets import QMainWindow, QApplication, QListView, QAbstractItemView
from PyQt5.QtGui import QStandardItemModel, QStandardItem
def createModel():
model = QStandardItemModel()
for i in range(0,101):
item = QStandardItem(str(i))
item.setText(str(i))
item.setEditable(False)
item.setDropEnabled(False)
model.appendRow(item)
return model
class TestListView(QMainWindow):
def __init__(self, parent=None):
super(TestListView, self).__init__(parent)
self.listView = QListView()
self.setCentralWidget(self.listView)
self.listView.setModel(createModel())
self.listView.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.listView.setDragEnabled(True)
self.listView.setDragDropMode(QAbstractItemView.InternalMove)
def main():
app = QApplication([])
lvt = TestListView()
lvt.show()
app.exec_()
if __name__ == '__main__':
main()
I am hoping someone can point out a foolish mistake I've made that is the cause of this issue (like when I earlier had passed ints to the QStandardItems constructor instead of strings, resulting in a crash each time I tried to drag and drop), but if that isn't the case, if anyone is able to recommend a combination of pyqt5 and 32bit (64bit is not an option for us) python components that they've found that does not exhibit this behaviour? I don't really care if it's python 3.x or python 2.x (although I haven't seen any pyqt5/python2 combinations previously), as long as it works.
I've tried the python-qt5 package that pip installs (after following the instructions here https://blogs.msdn.microsoft.com/pythonengineering/2016/04/11/unable-to-find-vcvarsall-bat/ by installing visual c++ build tools) in both 3.4 and 3.5, but the full version of this script will use .ui files from QtCreator, and the pip version of python-qt5 throws an error:
File "testReorder.py", line 2, in <module>
from PyQt5 import uic
File "c:\python35-32\lib\site-packages\PyQt5\uic\__init__.py", line 43, in <module>
from .Compiler import indenter, compiler
File "c:\python35-32\lib\site-packages\PyQt5\uic\Compiler\compiler.py", line 43, in <module>
from ..properties import Properties
File "c:\python35-32\lib\site-packages\PyQt5\uic\properties.py", line 46, in <module>
from .icon_cache import IconCache
File "c:\python35-32\lib\site-packages\PyQt5\uic\icon_cache.py", line 27, in <module>
from .port_v3.as_string import as_string
ImportError: No module named 'PyQt5.uic.port_v3'
when I include the import line
from PyQt5 import uic
in the code.
Edit: Having gotten home and tested the code on my linux machine (and seen no signs of sluggishness), I'm thinking this issue must be either specific to the combination of the pyqt, python and windows version, or something specific to that particular windows installation, and not a problem with my code.
I'd still be interested in hearing of anyone able to run the same code and not see the same issues on a windows (especially windows 7) machine, but I'm thinking it's less likely that I can assign the blame for this behaviour solely at pyqt's door.
Ran same code on another windows 7 computer with same packages installed. The issue is not seen there, so is obviously a problem with something specific to this machine rather than the code I've written/versions of python I'm using/version of pyqt.

Interacting with a gtk.container while gtk.main() is executing?

Experimenting with a battery monitor icon at the moment in Python using pygtk and egg.trayicon to create an icon to display a battery icon/tooltip.
I seem to be able to add the icon and the tooltip text, but when it then reaches the gtk.main() stage I need a way to modify these so it can then show the updated values.
I've tried gobject.idle_add() and gobject.timeout_add() without much luck, not sure where to go from this.
Anyone got any ideas?
EDIT: Perhaps not the clearest of questions.
I need to loop, fetching information from acpi while running and apply it to widgets inside the gtk container.
EDIT 2: Ok, it's properly down now. The issue was that I wasn't returning anything inside my callback. I just gave it "return 123" and now it's happily chugging away in my system tray, notifying me of my battery percentage :)
This example works for me:
# -*- Mode: Python -*-
# vi:si:et:sw=4:sts=4:ts=4
import gobject
import gtk
from egg import trayicon
label = gtk.Label("Over here")
def callback(widget, ev):
label.set_text("You found me")
def timeout():
label.set_text("What are you waiting for?")
tray = trayicon.TrayIcon("TrayIcon")
box = gtk.EventBox()
box.add(label)
tray.add(box)
tray.show_all()
box.connect("button-press-event", callback)
gobject.timeout_add(3000L, timeout)
gtk.main()
Without seeing your code, it's hard to tell what doesn't work.

Categories