# -*- coding: utf-8 -*-
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.uic import loadUiType
import youtube_dl
import pafy
import urllib.request
import urllib.parse
from urllib.parse import *
import win32clipboard
import sys
import os
import humanize
import subprocess
import time
import shutil
import re
from pySmartDL import SmartDL
from os.path import splitext, basename
from os import path
Close = False
ShutDown = False
Sleep = False
Restart = False
Paused = False
Stopped = False
Start = True
Resume = True
if getattr(sys, 'frozen', False):
# frozen
dir_ = os.path.dirname(sys.executable)
else:
# unfrozen
dir_ = os.path.dirname(os.path.realpath(__file__))
FORM_CLASS, _ = loadUiType(path.join(dir_, "main.ui"))
class MainApp(QMainWindow, FORM_CLASS):
def __init__(self, parent=None):
super(MainApp, self).__init__(parent)
QMainWindow.__init__(self)
self.setupUi(self)
self.lineEdit.installEventFilter(self)
self.lineEdit_3.installEventFilter(self)
self.lineEdit_6.installEventFilter(self)
self.Handle_Ui()
self.Handle_Buttons()
def closeEvent(self, evnt):
if self._want_to_close:
super(MainApp, self).closeEvent(evnt)
sys.exit()
def Handle_Ui(self):
#self.lineEdit.setFocus()
self.setFixedSize(861,441)
def Handle_Buttons(self):
self.pushButton_3.clicked.connect(self.open_file_loction)
self.pushButton_2.clicked.connect(self.Start)
self.pushButton_13.clicked.connect(self.Pause)
self.pushButton_14.clicked.connect(self.Stop)
self.pushButton.clicked.connect(self.Handle_Browse)
self.pushButton_4.clicked.connect(self.Download_youtube_video)
self.pushButton_10.clicked.connect(self.get_quality)
self.pushButton_5.clicked.connect(self.Browse2)
self.pushButton_6.clicked.connect(self.open_file_location2)
self.pushButton_11.clicked.connect(self.Search_Qualities)
self.pushButton_7.clicked.connect(self.Browse3)
self.pushButton_9.clicked.connect(self.download_playlist)
self.pushButton_8.clicked.connect(self.open_file_location3)
self.pushButton_12.clicked.connect(self.open_video)
self.comboBox_2.currentIndexChanged.connect(self.Action_Happened)
self.comboBox_3.currentIndexChanged.connect(self.Action_Happened)
self.comboBox_4.currentIndexChanged.connect(self.Action_Happened)
def Start(self):
global Start
global Stopped
global Paused
Start = True
Stopped = False
Paused = False
self.Download()
def Pause(self):
global Paused
global Start
global Resume
if self.pushButton_13.text()=="Pause Downloading":
Paused = True
Start = False
Stopped = False
Resume = False
self.pushButton_13.setText("Resume Downloading")
QApplication.processEvents()
elif self.pushButton_13.text()=="Resume Downloading":
Start = True
Paused = False
Resume = True
Stopped = False
self.pushButton_13.setText("Pause Downloading")
QApplication.processEvents()
def Stop(self):
global Stopped
global Start
Stopped = True
Start = False
Paused = False
self.Download()
def Download(self):
directory = os.path.expanduser("~") + "\AppData\Local\Temp\pySmartDL"
if not os.path.exists(directory):
os.makedirs(directory)
try:
global Paused
global Stopped
global Start
global XX
url = self.lineEdit.text()
save_location = self.lineEdit_2.text()
obj = SmartDL(url, progress_bar=False)
if Start == True:
try:
obj.start(blocking=False)
while True:
self.progressBar.setValue(obj.get_progress()*100)
self.label_8.setText("Downloaded: " + str(obj.get_dl_size(human=True)))
self.label_38.setText("Speed: " + str(obj.get_speed(human=True)))
self.label_39.setText("Remaining Time: " + str(obj.get_eta(human=True)))
time.sleep(0.2)
QApplication.processEvents()
if Paused == True:
obj.pause()
QApplication.processEvents()
if Resume == True:
obj.unpause()
QApplication.processEvents()
if obj.isFinished():
break
if Stopped == True:
obj.stop()
self.progressBar.setValue(0)
break
if obj.isSuccessful():
#os.rename(obj.get_dest(), save_location)
shutil.move(obj.get_dest(), save_location)
if Close == True:
QApplication.quit()
elif ShutDown == True:
os.system('shutdown -s')
elif Sleep == True:
os.system("rundll32.exe powrprof.dll,SetSuspendState 0,1,0")
elif Restart == True:
subprocess.call(["shutdown", "/r"])
if Stopped == False:
QMessageBox.information(self, "Download Completed", "Your Download is Completed")
except:
QMessageBox.warning(self, "Download Error", "Download Failed")
pass
except Exception as e:
pass
def main():
app = QApplication(sys.argv)
window = MainApp()
window.show()
app.exec_()
if __name__ == '__main__':
main()
I've been looking around for help on how to Apply Qthread on the above piece of code to update the progress Bar Dynamically with no "Not Responding problem"
I've read many topics and I could understand the main concept of Qthread but still can't get the idea on how to Apply it to my Code taking in consideration that download function is connected with a download button not running infinitely.
should I make a sub Qthread class or what can I do ?
If You Can help me with an example on how to use it with the above piece of code and I will Apply it on the remaining code of my Gui App..
Thanks in advance.
When implementing a QThread the task to be performed should be done in the run () method, if you want to update the GUI with data provided by the thread should not be done directly but through signals since the GUI should only be updated in the main thread called by this the GUI thread.
class DownloadThread(QThread):
dataChanged = pyqtSignal(int, str, str, str)
Started, Paused, Resume, Stopped = range(4)
downloadError = pyqtSignal()
downloadFinished = pyqtSignal()
def __init__(self, parent=None):
QThread.__init__(self, parent)
self.state = DownloadThread.Stopped
self.params = {"url": "", "save_location": ""}
def setParams(self, params):
self.params = params
def setState(self, state):
self.state = state
def run(self):
obj = SmartDL(self.params["url"], progress_bar=False)
try:
obj.start(blocking=False)
while True:
self.dataChanged.emit(obj.get_progress() * 100,
str(obj.get_dl_size(human=True)),
str(obj.get_speed(human=True)),
str(obj.get_eta(human=True)))
time.sleep(0.2)
if self.state == DownloadThread.Paused:
obj.pause()
if self.state == DownloadThread.Resume:
obj.unpause()
self.state = DownloadThread.Started
if obj.isFinished():
break
if self.state == DownloadThread.Stopped:
obj.stop()
self.progressBar.setValue(0)
break
if obj.isSuccessful():
# os.rename(obj.get_dest(), save_location)
shutil.move(obj.get_dest(), self.params["save_location"])
if self.state == DownloadThread.Started:
self.downloadFinished.emit()
except:
self.downloadError.emit()
You should avoid using global variables in addition that you can reduce several variables that are all updated at once by only one variable so I had to modify the GUI code.
class MainApp(QMainWindow, FORM_CLASS):
def __init__(self, parent=None):
# ...
self.Handle_Buttons()
self.download = DownloadThread(self)
self.download.dataChanged.connect(self.onDataChanged)
self.download.downloadError.connect(self.errorDownload)
self.download.downloadFinished.connect(self.successfulDownload)
def closeEvent(self, evnt):
# ...
def Handle_Ui(self):
# self.lineEdit.setFocus()
self.setFixedSize(861, 441)
def Handle_Buttons(self):
# ...
def onDataChanged(self, progress, downloaded, speed, remain):
self.progressBar.setValue(progress)
self.label_8.setText("Downloaded: " + downloaded)
self.label_38.setText("Speed: " + speed)
self.label_39.setText("Remaining Time: " + remain)
def Start(self):
directory = os.path.expanduser("~") + "\AppData\Local\Temp\pySmartDL"
if not os.path.exists(directory):
os.makedirs(directory)
params = {"url": self.lineEdit.text(),
"save_location": self.lineEdit_2.text()}
self.download.setParams(params)
self.download.setState(DownloadThread.Started)
self.download.start()
def Pause(self):
if self.pushButton_13.text() == "Pause Downloading":
self.download.setState(DownloadThread.Paused)
self.pushButton_13.setText("Resume Downloading")
elif self.pushButton_13.text() == "Resume Downloading":
self.download.setState(DownloadThread.Resume)
self.pushButton_13.setText("Pause Downloading")
def Stop(self):
self.download.setState(DownloadThread.Stopped)
self.progressBar.setValue(0)
def errorDownload(self):
QMessageBox.warning(self, "Download Error", "Download Failed")
def successfulDownload(self):
QMessageBox.information(self, "Download Completed", "Your Download is Completed")
Related
When User clicks on "Update" Push Button, box_updatetool_fcn will be executed. MainThread will update the Progressbar in the QMainWindow while the other QThread will download the file. But it executes perfectly, but the problem is signal emited by the WorkerThread is not updating immediately.
Even I have gone through many questions, none solved my problem. I don't know why my fcn_qthread_output is not executing immediately after Qthread finished.
Even the QThread finished() function also executing after the Main Function finished, which I am not using in my current program. I don't know what's wrong in the program, is something missing?
Here is the following console output -
Run Function Closed
Out1 : False, FileName
Main Function Ended
Out2 : True, FileName
What I am expecting is -
Run Function Closed
Out2 : True, FileName
Out1 : True, FileName
Main Function Ended
Below is the program execution flow -
class WorkerThread(QtCore.QThread):
outResult = QtCore.pyqtSignal(tuple)
def __init__(self,target,args,parent=None):
super(WorkerThread, self).__init__(parent)
self.fcn = target
self.args = args
def run(self):
outResult = self.fcn(*self.args)
self.outResult.emit(outResult)
print('Run Function Closed')
class ApplicationWindow(QtWidgets.QMainWindow):
def box_updatetool_fcn(self):
t1 = WorkerThread(target=self.boxapi.fcn_downloadfile, args=(fileID,)) #ThreadWithResult
t1.outResult.connect(self.fcn_qthread_output)
t1.start()
self.box_pbar_fcn(tempfilename,fsize)
print(f'Out1 : {self.var_box_output}')
self.boxapi.fcn_updatetool(file_fullpath,self.progpath)
print('Main Function Ended')
def fcn_qthread_output(self,result):
self.var_box_output = result
print(f'Out2 : {self.var_box_output}')
def box_pbar_fcn(self,filename,fullsize):
pobj = self.ui.progressbar
while(barprogress < 100):
if os.path.exists(filename):
currfilesize = os.path.getsize(filename)
barprogress = int(currfilesize/fullsize*100)
pobj.setValue(barprogress)
Reproducible Code -
I have used box_pbar_fcn (progress bar which shows the downloading progress) and t1 Thread (downloading the file) will Run in simultaneously.
import os, sys
from PyQt5 import QtWidgets, QtGui, QtCore
class WorkerThread(QtCore.QThread):
outResult = QtCore.pyqtSignal(tuple)
def __init__(self,parent=None):
super(WorkerThread, self).__init__(parent)
def run(self):
outResult = (True,'Out1')
self.outResult.emit(outResult)
print('Run Function Closed')
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.var_box_output = (False,'Inp1')
self.box_updatetool_fcn()
def box_updatetool_fcn(self):
tempfilename,fsize = 'Test',300
t1 = WorkerThread() #ThreadWithResult
t1.outResult.connect(self.fcn_qthread_output)
t1.start()
self.box_pbar_fcn(tempfilename,fsize)
print(f'Out1 : {self.var_box_output}')
print('Main Function Ended')
def fcn_qthread_output(self,result):
self.var_box_output = result
print(f'Out2 : {self.var_box_output}')
def box_pbar_fcn(self,filename,fullsize):
pass
#pobj = self.ui.progressbar
#while(barprogress < 100):
#if os.path.exists(filename):
# currfilesize = os.path.getsize(filename)
# barprogress = int(currfilesize/fullsize*100)
# pobj.setValue(barprogress)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mw = ApplicationWindow()
mw.show()
sys.exit(app.exec_())
The problem is that you are blocking the main thread with your while True (There should never be an instruction that blocks the eventloop for long). In Qt the logic is to do the job asynchronously. In this case I suspect that in a thread you are downloading a file and you want to show the download progress in the QProgressBar and for this you are continuously measuring the size of the file. If so, a possible solution is to use a QTimer to run the periodic task of measuring the file size.
Disclaimer: The following code that has not been tested should work. As I do not know how is the way you save the file, it could be that it does not work since many functions only write the file at the end and not in parts for efficiency reasons.
import os
import sys
from functools import cached_property
from PyQt5 import QtCore, QtGui, QtWidgets
class Downloader(QtCore.QThread):
outResult = QtCore.pyqtSignal(tuple)
def run(self):
import time
time.sleep(10)
outResult = (True, "Out1")
self.outResult.emit(outResult)
print("Run Function Closed")
class FileObserver(QtCore.QObject):
def __init__(self, parent=None):
super().__init__(parent)
self._filename = ""
self._current_size = 0
#property
def filename(self):
return self._filename
def start(self, filename, interval=100):
self._current_size = 0
self._filename = filename
self.timer.setInterval(interval)
self.timer.start()
def stop(self):
self.timer.stop()
#cached_property
def timer(self):
timer = QtCore.QTimer()
timer.timeout.connect(self._measure)
return timer
def _measure(self):
if os.path.exists(self.filename):
file_size = os.path.getsize(filename)
if self._current_size != file_size:
self._current_size = file_size
self.file_size_changed.emit(file_size)
class ApplicationWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.var_box_output = (False, "Inp1")
self.box_updatetool_fcn()
#cached_property
def file_observer(self):
return FileObserver()
def box_updatetool_fcn(self):
tempfilename, fullsize = "Test", 300
self.ui.progressbar.setMaximum(fullsize)
t1 = WorkerThread(self)
t1.outResult.connect(self.fcn_qthread_output)
t1.finished.connect(self.file_observer.stop)
t1.start()
self.file_observer.start(tempfilename, interval=10)
def fcn_qthread_output(self, result):
self.var_box_output = result
print(f"Out2 : {self.var_box_output}")
def box_pbar_fcn(self, file_size):
self.ui.progressbar.setValue(file_size)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
mw = ApplicationWindow()
mw.show()
sys.exit(app.exec_())
this code opens web site with Qt browser. If internet connection is lost I want to give Information message with QMessageBox:
import sys
from PyQt5.QtCore import *
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QLabel
from PyQt5.QtWebEngineWidgets import *
from PyQt5.QtGui import QGuiApplication as App
from PyQt5.QtGui import QPixmap, QWindow
from PyQt5 import QtNetwork, QtCore, QtWidgets
import urllib
from urllib.request import urlopen
import threading
import time
class WebApp(QMainWindow):
def __init__(self):
self.is_connected = None
self.is_msgshow = True
self.msg = QtWidgets.QMessageBox()
super().__init__()
self.title = "OZI"
self.t_internet = threading.Thread(target=self.is_internet)
self.t_internet.start()
self.t_refreshpage = threading.Thread(target=self.refresh_page)
self.t_refreshpage.start()
self.web = QWebEngineView()
self.web.window().setWindowTitle(self.title)
self.web.load(QUrl("http://www.google.com"))
self.web.showFullScreen()
def is_internet(self):
"""
Query internet using python
:return:
"""
while True:
time.sleep(5)
try:
urlopen("http://www.google.com", timeout=1)
self.is_connected = True
except urllib.error.URLError as Error:
print(Error)
self.is_connected = False
print(self.is_connected)
def refresh_page(self):
while True:
time.sleep(.1)
if self.is_connected == False:
time.sleep(5)
if self.is_connected == True:
self.web.page().action(QWebEnginePage.Reload).trigger()
else:
if self.is_msgshow == True:
print('testtt')
self.msg.information(None, 'INFO', 'PLEASE CHECK YOUR INTERNET CONNECTION!!!')
self.is_msgshow = False
self.msg.close()
else:
pass
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = WebApp()
sys.exit(app.exec_())
However, I couldn't succeed at
if self.is_msgshow == True:
print('testtt')
this part while internet connection is lost my code opens many MessageBox. I guess my fault is controlling self.is_msgshow flag.
Far beyond the problem you are pointing out, you have other errors and it is more dangerous than the one indicated: You should not access the GUI from another thread, and you should also not access the same variable from 2 threads without protecting it through a mutex, semaphore or Similary. For example, you are calling self.web.page().action(QWebEnginePage.Reload).trigger() and self.msg.information(None, 'INFO', 'PLEASE CHECK YOUR INTERNET CONNECTION!!!') from the secondary thread, and the variable "is_connected" is accessed by several threads.
Another error is that method QMessage::information() is static and generates a new object that is not directly accessible, instead you must use the object "self.msg" through its own methods to display the necessary information.
Considering the above I have created a class that is only responsible for analyzing the status of the connection and if it changes to emit a signal. To send information between threads in Qt you can use signals or QMetaObject::invokedMethod() in addition to the classic mutex and semaphores.
Then it is only necessary to analyze the value of the status sent to the GUI to implement the logic:
import sys
import threading
import time
import urllib
from urllib.request import urlopen
from PyQt5 import QtCore, QtWidgets, QtNetwork, QtWebEngineWidgets
class ConnectivityManager(QtCore.QObject):
statusChanged = QtCore.pyqtSignal(bool)
def __init__(self, *, timeout=4000, parent=None):
super().__init__(parent)
self._status = False
self._timeout = timeout
def start(self):
threading.Thread(target=self._check, daemon=True).start()
#QtCore.pyqtProperty(bool, notify=statusChanged)
def status(self):
return self._status
#QtCore.pyqtSlot(bool)
def _update_status(self, status):
if self._status != status:
self._status = status
self.statusChanged.emit(self.status)
def _check(self):
while True:
try:
urlopen("http://www.google.com", timeout=1)
status = True
except urllib.error.URLError as Error:
status = False
QtCore.QMetaObject.invokeMethod(
self,
"_update_status",
QtCore.Qt.QueuedConnection,
QtCore.Q_ARG(bool, status),
)
time.sleep(5)
class WebApp(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("OZI")
self.web = QtWebEngineWidgets.QWebEngineView()
self.setCentralWidget(self.web)
self.web.load(QtCore.QUrl("http://www.google.com"))
self.connectivity_manager = ConnectivityManager()
self.connectivity_manager.statusChanged.connect(self.on_status_changed)
self.connectivity_manager.start()
self.msg = QtWidgets.QMessageBox()
self.msg.setWindowTitle("INFO")
self.msg.setText("PLEASE CHECK YOUR INTERNET CONNECTION!!!")
#QtCore.pyqtSlot(bool)
def on_status_changed(self, status):
if status:
self.msg.hide()
self.statusBar().showMessage("Connected")
self.web.page().action(QtWebEngineWidgets.QWebEnginePage.Reload).trigger()
else:
self.statusBar().showMessage("Disconnected")
self.msg.show()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ex = WebApp()
ex.showFullScreen()
sys.exit(app.exec_())
On the other hand, the same logic can be implemented using QtNetwork without the need for threads:
import sys
from PyQt5 import QtCore, QtWidgets, QtNetwork, QtWebEngineWidgets
class ConnectivityManager(QtCore.QObject):
statusChanged = QtCore.pyqtSignal(bool)
def __init__(self, *, timeout=4000, parent=None):
super().__init__(parent)
self._status = False
self._timeout = timeout
self.manager = QtNetwork.QNetworkAccessManager()
self._timer = QtCore.QTimer(
singleShot=True, interval=self._timeout, timeout=self.verify_status
)
def start(self):
QtCore.QTimer.singleShot(0, self._check)
#QtCore.pyqtProperty(bool, notify=statusChanged)
def status(self):
return self._status
#QtCore.pyqtSlot(bool)
def _update_status(self, status):
if self._status != status:
self._status = status
self.statusChanged.emit(self.status)
def _check(self):
url = QtCore.QUrl("https://www.google.com/")
req = QtNetwork.QNetworkRequest(url)
self._reply = self.manager.get(req)
self._reply.finished.connect(self.verify_status)
self._timer.start()
#QtCore.pyqtSlot()
def verify_status(self):
if self._timer.isActive():
self._timer.stop()
if self._reply.error() == QtNetwork.QNetworkReply.NoError:
v = self._reply.attribute(
QtNetwork.QNetworkRequest.HttpStatusCodeAttribute
)
if 200 <= v < 300:
self._update_status(True)
else:
print("error", "code error: {}".format(v))
self._update_status(False)
else:
print("error", self._reply.errorString())
self._update_status(False)
else:
self._reply.finished.disconnect(self.verify_status)
self._reply.abort()
print("Timeout")
self._update_status(False)
QtCore.QTimer.singleShot(5000, self._check)
self._reply.deleteLater()
class WebApp(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("OZI")
self.web = QtWebEngineWidgets.QWebEngineView()
self.setCentralWidget(self.web)
self.web.load(QtCore.QUrl("http://www.google.com"))
self.connectivity_manager = ConnectivityManager()
self.connectivity_manager.statusChanged.connect(self.on_status_changed)
self.connectivity_manager.start()
self.msg = QtWidgets.QMessageBox()
self.msg.setWindowTitle("INFO")
self.msg.setText("PLEASE CHECK YOUR INTERNET CONNECTION!!!")
#QtCore.pyqtSlot(bool)
def on_status_changed(self, status):
if status:
self.msg.hide()
self.statusBar().showMessage("Connected")
self.web.page().action(QtWebEngineWidgets.QWebEnginePage.Reload).trigger()
else:
self.statusBar().showMessage("Disconnected")
self.msg.show()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ex = WebApp()
ex.showFullScreen()
sys.exit(app.exec_())
I do not know the pyqt5 at all but guessing by flow of your code, here:
if self.is_msgshow == True:
print('testtt')
self.msg.information(None, 'INFO', 'PLEASE CHECK YOUR INTERNET CONNECTION!!!')
self.is_msgshow = False
self.msg.close()
It seems the self.msg.information() call is expected to be synchronous. If that is so, then as long as it remains open, the is_msgshow is still True because you changing it once you got dialog closed. And this is your bug as if new even occurs then nothing blocks new dialog from being shown. The fix is pretty simple - just move self.is_msgshow = False to be very first thing done in that code block, you should be good:
if self.is_msgshow:
self.is_msgshow = False
print('testtt')
self.msg.information(None, 'INFO', 'PLEASE CHECK YOUR INTERNET CONNECTION!!!')
self.msg.close()
Additional note, you may want to reset it back to True once connectivity is restored otherwise you won't show a thing next time network is down.
The QMessageBox class provides a modal dialog for informing the user or for asking the user a question and receiving an answer.
https://doc.qt.io/qtforpython/PySide2/QtWidgets/QMessageBox.html
I am using a GUI with a checkbox made with QT and python, when the checkbox is checked i want to display 'stackoverflow' every 5 seconds, when it is unchecked i want it to do nothing. I have tried to call the following function when it is checked:
def work ():
threading.Timer(5, work).start ()
print "stackoverflow"
def checkbox(self, state):
if state == QtCore.Qt.Checked:
print 'checked'
work()
else: print 'unchecked'
But it keeps on printing 'stackoverflow'. How can i stop this?
Here is a solution.
from threading import Thread
import time
class WorkerThread:
def __init__(self, timer=5):
self.timer = timer
self._alive = False
def work(self):
while self._alive:
time.sleep(self.timer)
print("Stack Overflow")
def start(self):
self._thread = Thread(target=self.work)
self._alive = True
self._thread.start()
def stop(self):
self._alive = False
worker_thread = WorkerThread()
def checkbox(self, state):
if state == QtCore.Qt.Checked:
worker_thread.start()
else:
worker_thread.stop()
You can use variable to control thread
running = False
def work ():
print "stackoverflow"
if running:
threading.Timer(5, work).start ()
def checkbox(self, state):
global running
if state == QtCore.Qt.Checked:
print 'checked'
running = True
work()
else:
print 'unchecked'
running = False
I have looked at this tutorial,http://lzone.de/media+player+with+gstreamer+and+pygi, on how to stream audio from the web using Gtk3 and Gstreamer. I have working code minus the ability for actual music to be played. I need help, I'm new to making apps, but I think the issues stems from self.player.set_property('uri', self.uri) not doing its magic to fetch the music from the url self.uri = 'http://mp3channels.webradio.antenne.de/chillout'
Here is my source:
import sys, os, pygtk
import gi
gi.require_version('Gst', '1.0')
from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Gtk
from gi.repository import Gst
GObject.threads_init()
Gst.init(None)
class PlaybackInterface():
def __init__(self):
self.playing = False
# A free example sound track
self.uri = 'http://mp3channels.webradio.antenne.de/chillout'
# GTK window and widgets
self.window = Gtk.Window()
self.window.set_size_request(300,50)
vbox = Gtk.Box(Gtk.Orientation.HORIZONTAL, 0)
vbox.set_margin_top(3)
vbox.set_margin_bottom(3)
self.window.add(vbox)
self.playButtonImage = Gtk.Image()
self.playButtonImage.set_from_stock("gtk-media-play", Gtk.IconSize.BUTTON)
self.playButton = Gtk.Button.new()
self.playButton.add(self.playButtonImage)
self.playButton.connect("clicked", self.playToggled)
Gtk.Box.pack_start(vbox, self.playButton, False, False, 0)
self.slider = Gtk.HScale()
self.slider.set_margin_left(6)
self.slider.set_margin_right(6)
self.slider.set_draw_value(False)
self.slider.set_range(0, 100)
self.slider.set_increments(1, 10)
Gtk.Box.pack_start(vbox, self.slider, True, True, 0)
self.label = Gtk.Label(label='0:00')
self.label.set_margin_left(6)
self.label.set_margin_right(6)
Gtk.Box.pack_start(vbox, self.label, False, False, 0)
self.window.show_all()
# GStreamer Setup
self.player = Gst.ElementFactory.make('playbin', None)
self.player.set_property('uri', self.uri)
# Set properties
# bus = self.player.get_bus()
# bus.connect("message", self.on_message)
# self.player.connect("about-to-finish", self.on_finished)
def on_message(self, bus, message):
t = message.type
if t == Gst.Message.EOS:
self.player.set_state(Gst.State.NULL)
self.playing = False
elif t == Gst.Message.ERROR:
self.player.set_state(Gst.State.NULL)
err, debug = message.parse_error()
print "Error: %s" % err, debug
self.playing = False
self.updateButtons()
def on_finished(self, player):
self.playing = False
self.slider.set_value(0)
self.label.set_text("0:00")
self.updateButtons()
def play(self):
self.player.set_state(Gst.State.PLAYING)
GObject.timeout_add(1000, self.updateSlider)
def stop(self):
self.player.set_state(Gst.State.NULL)
def playToggled(self, w):
self.slider.set_value(0)
self.label.set_text("0:00")
if(self.playing == False):
self.play()
else:
self.stop()
self.playing=not(self.playing)
self.updateButtons()
def updateSlider(self):
if(self.playing == False):
return False # cancel timeout
try:
if self.IS_GST010:
nanosecs = self.player.query_position(Gst.Format.TIME)[2]
duration_nanosecs = self.player.query_duration(Gst.Format.TIME)[2]
else:
nanosecs = self.player.query_position(Gst.Format.TIME)[1]
duration_nanosecs = self.player.query_duration(Gst.Format.TIME)[1]
# block seek handler so we don't seek when we set_value()
# self.slider.handler_block_by_func(self.on_slider_change)
duration = float(duration_nanosecs) / Gst.SECOND
position = float(nanosecs) / Gst.SECOND
self.slider.set_range(0, duration)
self.slider.set_value(position)
self.label.set_text ("%d" % (position / 60) + ":%02d" % (position % 60))
except Exception as e:
# pipeline must not be ready and does not know position
print e
pass
return True
def updateButtons(self):
if(self.playing == False):
self.playButtonImage.set_from_stock("gtk-media-play", Gtk.IconSize.BUTTON)
else:
self.playButtonImage.set_from_stock("gtk-media-stop", Gtk.IconSize.BUTTON)
player = PlaybackInterface()
Gtk.main()
Gstreamer has a lot of dependencies. Peep the link https://raw.githubusercontent.com/Psychtoolbox-3/Psychtoolbox-3/beta/Psychtoolbox/PsychDocumentation/GStreamer.m. I uploaded working code on my Github account (At least on my machine) that will play and stop an audio stream from an http source. https://github.com/jchiefelk/musicplayer
I'm starting the UI from within Maya. If the UI hasn't been closed, running the UI again will completely freeze Maya (with the error "Event Loop is already running")
Manually closing the UI before re-running the script will prevent it from freezing up. But I guess that's not really practical.
Is there a way to detect if the UI I'm trying to run already exists? And possible force close it?
Here is a very simple PyQt5 solution using QLockFile:
from PyQt5 import QtCore, QtWidgets
lockfile = QtCore.QLockFile(QtCore.QDir.tempPath() + '/my_app_name.lock')
if lockfile.tryLock(100):
app = QtWidgets.QApplication([])
win = QtWidgets.QWidget()
win.setGeometry(50, 50, 100, 100)
win.show()
app.exec()
else:
print('app is already running')
There were a couple of fairly straightforward C++ solutions given on the Qt Wiki which no longer seem to exist. I ported one of them to PyQt, and have provided a sample script below. The original C++ solution has been split into two classes, because the messaging facility may not be needed.
PyQt5:
from PyQt5 import QtWidgets, QtCore, QtNetwork
class SingleApplication(QtWidgets.QApplication):
messageAvailable = QtCore.pyqtSignal(object)
def __init__(self, argv, key):
super().__init__(argv)
# cleanup (only needed for unix)
QtCore.QSharedMemory(key).attach()
self._memory = QtCore.QSharedMemory(self)
self._memory.setKey(key)
if self._memory.attach():
self._running = True
else:
self._running = False
if not self._memory.create(1):
raise RuntimeError(self._memory.errorString())
def isRunning(self):
return self._running
class SingleApplicationWithMessaging(SingleApplication):
def __init__(self, argv, key):
super().__init__(argv, key)
self._key = key
self._timeout = 1000
self._server = QtNetwork.QLocalServer(self)
if not self.isRunning():
self._server.newConnection.connect(self.handleMessage)
self._server.listen(self._key)
def handleMessage(self):
socket = self._server.nextPendingConnection()
if socket.waitForReadyRead(self._timeout):
self.messageAvailable.emit(
socket.readAll().data().decode('utf-8'))
socket.disconnectFromServer()
else:
QtCore.qDebug(socket.errorString())
def sendMessage(self, message):
if self.isRunning():
socket = QtNetwork.QLocalSocket(self)
socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly)
if not socket.waitForConnected(self._timeout):
print(socket.errorString())
return False
if not isinstance(message, bytes):
message = message.encode('utf-8')
socket.write(message)
if not socket.waitForBytesWritten(self._timeout):
print(socket.errorString())
return False
socket.disconnectFromServer()
return True
return False
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.edit = QtWidgets.QLineEdit(self)
self.edit.setMinimumWidth(300)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.edit)
def handleMessage(self, message):
self.edit.setText(message)
if __name__ == '__main__':
import sys
key = 'app-name'
# send commandline args as message
if len(sys.argv) > 1:
app = SingleApplicationWithMessaging(sys.argv, key)
if app.isRunning():
print('app is already running')
app.sendMessage(' '.join(sys.argv[1:]))
sys.exit(1)
else:
app = SingleApplication(sys.argv, key)
if app.isRunning():
print('app is already running')
sys.exit(1)
window = Window()
app.messageAvailable.connect(window.handleMessage)
window.show()
sys.exit(app.exec_())
PyQt4:
# only needed for python2
import sip
sip.setapi('QString', 2)
from PyQt4 import QtGui, QtCore, QtNetwork
class SingleApplication(QtGui.QApplication):
messageAvailable = QtCore.pyqtSignal(object)
def __init__(self, argv, key):
QtGui.QApplication.__init__(self, argv)
# cleanup (only needed for unix)
QtCore.QSharedMemory(key).attach()
self._memory = QtCore.QSharedMemory(self)
self._memory.setKey(key)
if self._memory.attach():
self._running = True
else:
self._running = False
if not self._memory.create(1):
raise RuntimeError(self._memory.errorString())
def isRunning(self):
return self._running
class SingleApplicationWithMessaging(SingleApplication):
def __init__(self, argv, key):
SingleApplication.__init__(self, argv, key)
self._key = key
self._timeout = 1000
self._server = QtNetwork.QLocalServer(self)
if not self.isRunning():
self._server.newConnection.connect(self.handleMessage)
self._server.listen(self._key)
def handleMessage(self):
socket = self._server.nextPendingConnection()
if socket.waitForReadyRead(self._timeout):
self.messageAvailable.emit(
socket.readAll().data().decode('utf-8'))
socket.disconnectFromServer()
else:
QtCore.qDebug(socket.errorString())
def sendMessage(self, message):
if self.isRunning():
socket = QtNetwork.QLocalSocket(self)
socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly)
if not socket.waitForConnected(self._timeout):
print(socket.errorString())
return False
if not isinstance(message, bytes):
message = message.encode('utf-8')
socket.write(message)
if not socket.waitForBytesWritten(self._timeout):
print(socket.errorString())
return False
socket.disconnectFromServer()
return True
return False
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.edit = QtGui.QLineEdit(self)
self.edit.setMinimumWidth(300)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.edit)
def handleMessage(self, message):
self.edit.setText(message)
if __name__ == '__main__':
import sys
key = 'app-name'
# send commandline args as message
if len(sys.argv) > 1:
app = SingleApplicationWithMessaging(sys.argv, key)
if app.isRunning():
print('app is already running')
app.sendMessage(' '.join(sys.argv[1:]))
sys.exit(1)
else:
app = SingleApplication(sys.argv, key)
if app.isRunning():
print('app is already running')
sys.exit(1)
window = Window()
app.messageAvailable.connect(window.handleMessage)
window.show()
sys.exit(app.exec_())
In case if someone want to run #ekhumoro solution with python3 there's need to make few adjustments to string operations, i'll share my copy where it was working python 3.
import sys
from PyQt4 import QtGui, QtCore, QtNetwork
class SingleApplication(QtGui.QApplication):
def __init__(self, argv, key):
QtGui.QApplication.__init__(self, argv)
self._memory = QtCore.QSharedMemory(self)
self._memory.setKey(key)
if self._memory.attach():
self._running = True
else:
self._running = False
if not self._memory.create(1):
raise RuntimeError( self._memory.errorString() )
def isRunning(self):
return self._running
class SingleApplicationWithMessaging(SingleApplication):
def __init__(self, argv, key):
SingleApplication.__init__(self, argv, key)
self._key = key
self._timeout = 1000
self._server = QtNetwork.QLocalServer(self)
if not self.isRunning():
self._server.newConnection.connect(self.handleMessage)
self._server.listen(self._key)
def handleMessage(self):
socket = self._server.nextPendingConnection()
if socket.waitForReadyRead(self._timeout):
self.emit(QtCore.SIGNAL('messageAvailable'), bytes(socket.readAll().data()).decode('utf-8') )
socket.disconnectFromServer()
else:
QtCore.qDebug(socket.errorString())
def sendMessage(self, message):
if self.isRunning():
socket = QtNetwork.QLocalSocket(self)
socket.connectToServer(self._key, QtCore.QIODevice.WriteOnly)
if not socket.waitForConnected(self._timeout):
print(socket.errorString())
return False
socket.write(str(message).encode('utf-8'))
if not socket.waitForBytesWritten(self._timeout):
print(socket.errorString())
return False
socket.disconnectFromServer()
return True
return False
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.edit = QtGui.QLineEdit(self)
self.edit.setMinimumWidth(300)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.edit)
def handleMessage(self, message):
self.edit.setText(message)
if __name__ == '__main__':
key = 'foobar'
# if parameter no. 1 was set then we'll use messaging between app instances
if len(sys.argv) > 1:
app = SingleApplicationWithMessaging(sys.argv, key)
if app.isRunning():
msg = ''
# checking if custom message was passed as cli argument
if len(sys.argv) > 2:
msg = sys.argv[2]
else:
msg = 'APP ALREADY RUNNING'
app.sendMessage( msg )
print( "app is already running, sent following message: \n\"{0}\"".format( msg ) )
sys.exit(1)
else:
app = SingleApplication(sys.argv, key)
if app.isRunning():
print('app is already running, no message has been sent')
sys.exit(1)
window = Window()
app.connect(app, QtCore.SIGNAL('messageAvailable'), window.handleMessage)
window.show()
sys.exit(app.exec_())
Example cli calls, assuming that your script name is "SingleInstanceApp.py":
python SingleInstanceApp.py 1
python SingleInstanceApp.py 1 "test"
python SingleInstanceApp.py 1 "foo bar baz"
python SingleInstanceApp.py 1 "utf8 test FOO ßÄÖÜ ßäöü łąćźżóń ŁĄĆŹŻÓŃ etc"
(and here is call wihout first parameter, so message simply will not be sent)
python SingleInstanceApp.py
Hope that it will help someone.
My solution is this:
import sys
from PyQt5.QtCore import QLockFile
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QMessageBox
from window import MainWindow
if __name__ == "__main__":
try:
app_object = QApplication(sys.argv)
lock_file = QLockFile("app.lock")
if lock_file.tryLock():
window = MainWindow()
window.show()
app_object.exec()
else:
error_message = QMessageBox()
error_message.setIcon(QMessageBox.Warning)
error_message.setWindowTitle("Error")
error_message.setText("The application is already running!")
error_message.setStandardButtons(QMessageBox.Ok)
error_message.exec()
finally:
lock_file.unlock()