QML gauges not updating from Python - python

I'm still getting to grips with QT... I've made a python file and a QML file. the Python file updates the gauge's value from data it gets over UDP.
This only works once though... the first UDP packet comes in and updates the gauge, but when it gets the next packet,despite the value updating, the gauge itself does not.
QML
CircularGauge {
id: circularGauge
x: 30
y: 30
value: itt1value
minimumValue: 0
maximumValue: 1200
tickmarksVisible: false
style: CircularGaugeStyle {
maximumValueAngle: 400
minimumValueAngle: 90
}
}
Python:
def configureApplication():
# Set up the application window
app = QGuiApplication(sys.argv)
view = QQuickView()
view.setResizeMode(QQuickView.SizeRootObjectToView)
view.setTitle("my title")
# Load the QML file
qml_file = os.path.join(os.path.dirname(__file__), "maingui.qml")
view.setSource(QUrl.fromLocalFile(os.path.abspath(qml_file)))
# load the slots into the QML file
view.rootContext().setContextProperty("itt1value", 0)
t = threading.Thread(target=receivedata, args=(view,))
t.start()
# Show the window
if view.status() == QQuickView.Error:
sys.exit(-1)
view.show()
# execute and cleanup
app.exec_()
del view
In the threaded method receivedata() I get the data from UDP, process it, then send it to the gauge like so:
view.rootContext().setContextProperty("itt1value", itt)
receivedata() has a while loop in it with the above details, but the gauge only actually updates once. If I put a statement in the QML file to display itt1value, it always has the correct value, so do I need to put in a method to detect the change to this value and re-paint the gauge?
Edit: I was asked for the details of receivedata(), so I have attached it here:
def receivedata(view):
print("Starting UDP server...")
UDP_IP = "192.168.0.14"
UDP_PORT = 49000
sock = socket.socket(socket.AF_INET, # Internet
socket.SOCK_DGRAM) # UDP
sock.bind((UDP_IP, UDP_PORT))
olditt = 0
loopruns = 0 # for debugging
while True:
rawstring = sock.recv(1024)
hexarray = []
#lots of irrelevent formatting here, result is int(value)
itt = float(hextoint(value, olditt))
olditt = itt
itt = format(itt, '.3f')
current = str(loopruns) # for debugging
view.setTitle(current) # for debugging
view.rootContext().setContextProperty("itt1value", itt)
loopruns = loopruns + 1
print(itt)

You have the following errors:
You cannot directly modify the GUI from another thread.
A value can be exported again with setContextProperty(), this will not change the previous value unless the QML is reloaded.
If you want "itt" to modify any value in QML it must be of compatible types, in this case the value of CircularGauge is "real" and therefore the type of data supported in python is float.
Considering the above, I have created a QObject since it can notify changes through signals since it is thread-safe, and export the QObject making connections using Connections.
main.py
import os
import random
import sys
import threading
import time
from PySide2.QtCore import QObject, QUrl, Signal
from PySide2.QtGui import QGuiApplication
from PySide2.QtQuick import QQuickView
class Connections(QObject):
titleChanged = Signal(str, arguments=["title"])
valueChanged = Signal(float, arguments=["value"])
def receivedata(connector):
# configurations
loopruns = 0
while True:
# other stuff
time.sleep(0.1)
itt = random.uniform(0.0, 1200.0)
connector.valueChanged.emit(itt)
connector.titleChanged.emit(str(loopruns))
loopruns += 1
def main(args):
app = QGuiApplication(args)
view = QQuickView(title="my title", resizeMode=QQuickView.SizeRootObjectToView)
connector = Connections()
connector.titleChanged.connect(view.setTitle)
view.rootContext().setContextProperty("connector", connector)
# Load the QML file
qml_file = os.path.join(os.path.dirname(__file__), "maingui.qml")
view.setSource(QUrl.fromLocalFile(os.path.abspath(qml_file)))
# start thread
threading.Thread(target=receivedata, args=(connector,)).start()
# Show the window
if view.status() == QQuickView.Error:
return -1
view.show()
# execute and cleanup
ret = app.exec_()
del view
return ret
if __name__ == "__main__":
sys.exit(main(sys.argv))
maingui.qml
import QtQml 2.13
import QtQuick.Extras 1.4
import QtQuick.Controls.Styles 1.4
CircularGauge {
id: circularGauge
value: 100
minimumValue: 0
maximumValue: 1200
tickmarksVisible: false
style: CircularGaugeStyle {
maximumValueAngle: 400
minimumValueAngle: 90
}
Connections{
target: connector
onValueChanged: circularGauge.value = value
}
}

Related

Qt: getting a thumbnail for a video

Is there a better way to get a thumbnail for a video? OS is primarily Linux, but hopefully there's a cross platform way to do it.
This is what I have right now:
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw
from PySide6 import QtCore as qtc
app = qtw.QApplication()
thumbnail_file = "video.mp4"
loop = qtc.QEventLoop()
widget = qtmw.QVideoWidget()
widget.setVisible(False)
media_player = qtm.QMediaPlayer()
media_player.setVideoOutput(widget)
media_player.mediaStatusChanged.connect(loop.exit)
media_player.positionChanged.connect(loop.exit)
media_player.setSource(thumbnail_file)
loop.exec()
media_player.mediaStatusChanged.disconnect()
media_player.play()
if media_player.isSeekable():
media_player.setPosition(media_player.duration() // 2)
loop.exec()
media_player.positionChanged.disconnect()
media_player.stop()
image = media_player.videoSink().videoFrame().toImage()
image.save('thumbnail.jpg')
app.exec()
This will be ran in a separate thread so the time is not really an issue, but it's still pretty convoluted.
There are many different approaches to this, since QMediaPlayer can undergo multiple state changes which can be monitored in a variety of ways. So the question of what is "best" probably comes down to what is most reliable/predictable on the target platform(s). I have only tested on Arch Linux using Qt-6.3.1 with a GStreamer-1.20.3 backend, but I think the solution presented below should work correctly with most other setups.
As it stands, the example code in the question doesn't work on Linux. As pointed out in the comments, this can be fixed by using the videoFrameChanged signal of the mediad-player's video-sink. (Unfortunately, at time of writing, these APIs are rather poorly documented). However, the example can be simplified and improved in various ways, so I have provided an alternative solution below.
During testing, I found that the durationChanged and postionChanged signals cannot be relied upon to give the relevant notifications at the appropriate time, so I have avoided using them. It's best to wait for the player to reach the buffered-state before setting the position, and then wait until a frame is received that can be verified as matching the requested position before saving the image. (I would also advise adding a suitable timeout in case the media-player gets stuck in an indeterminate state).
To illustrate the timing issues alluded to above, here is some sample output:
frame changed: 0
duration change: 13504
status change: b'LoadedMedia'
frame changed: 0
status change: b'BufferedMedia'
set position: 6752
frame changed: 6752
save: exit
frame changed: 0
frame changed: 0
duration change: 13504
status changed: b'LoadedMedia'
status changed: b'BufferedMedia'
set position: 6752
frame changed: 6752
save: exit
As you can see, the exact sequence of events isn't totally predictable. If the position is set before the buffered-state is reached, the output looks like this:
frame changed: 0
duration changed: 13504
status changed: LoadedMedia
set position: 0
frame changed: 0
status change: BufferedMedia
frame changed: 40
frame changed: 80
... # long list of changes
frame changed: 6720
frame changed: 6760
save: exit
Here is the demo script (which works with both PySide6 and PyQt6):
import os
from PySide6.QtCore import QCoreApplication, QTimer, QEventLoop, QUrl
from PySide6.QtMultimedia import QMediaPlayer, QVideoSink
# from PyQt6.QtCore import QCoreApplication, QTimer, QEventLoop, QUrl
# from PyQt6.QtMultimedia import QMediaPlayer, QVideoSink
def thumbnail(url):
position = 0
image = None
loop = QEventLoop()
QTimer.singleShot(15000, lambda: loop.exit(1))
player = QMediaPlayer()
player.setVideoSink(sink := QVideoSink())
player.setSource(url)
def handle_status(status):
nonlocal position
print('status changed:', status.name)
# if status == QMediaPlayer.MediaStatus.LoadedMedia:
if status == QMediaPlayer.MediaStatus.BufferedMedia:
player.setPosition(position := player.duration() // 2)
print('set position:', player.position())
def handle_frame(frame):
nonlocal image
print('frame changed:', frame.startTime() // 1000)
if (start := frame.startTime() // 1000) and start >= position:
sink.videoFrameChanged.disconnect()
image = frame.toImage()
print('save: exit')
loop.exit()
player.mediaStatusChanged.connect(handle_status)
sink.videoFrameChanged.connect(handle_frame)
player.durationChanged.connect(
lambda value: print('duration changed:', value))
player.play()
if loop.exec() == 1:
print('ERROR: process timed out')
return image
video_file = 'video.mp4'
thumbnail_file = 'thumbnail.jpg'
try:
os.remove(thumbnail_file)
except OSError:
pass
app = QCoreApplication(['Test'])
image = thumbnail(QUrl.fromLocalFile(video_file))
if image is not None:
image.save(thumbnail_file)
Answer reached thanks to the discussion in the comments of OP:
from PySide6 import QtWidgets as qtw
from PySide6 import QtMultimedia as qtm
from PySide6 import QtMultimediaWidgets as qtmw
from PySide6 import QtCore as qtc
app = qtw.QApplication()
thumbnail_file = "video.mp4"
loop = qtc.QEventLoop()
video_sink = qtm.QVideoSink()
media_player = qtm.QMediaPlayer()
media_player.setVideoSink(video_sink)
media_player.mediaStatusChanged.connect(loop.exit)
media_player.setSource(thumbnail_file)
loop.exec()
media_player.mediaStatusChanged.disconnect()
if media_player.isSeekable() and media_player.duration():
media_player.positionChanged.connect(loop.exit)
media_player.setPosition(media_player.duration() // 2)
media_player.play()
loop.exec()
media_player.positionChanged.disconnect()
else:
media_player.play()
video_sink.videoFrameChanged.connect(loop.exit)
loop.exec()
video_sink.videoFrameChanged.disconnect()
media_player.stop()
image = media_player.videoSink().videoFrame().toImage()
image.save('thumbnail.jpg')

How to get absolute path from popup (Intent.ACTION_OPEN_DOCUMENT_TREE) kivy , andoid -11

I am a beginner programmer, writing my first application in kivy. And ran into limited storage issue for android - 11 (API 30). How to get the absolute path from the pop-up window when the user selects the folder to save the application data in which I am going to store some data. My application works fine without this choice on 9 anroid, but here's the problem.
here is the minimal code from that window. How to get the absolute path 'root_id' for further manipulations with this folder. By creating files in it and opening SaveDialoga in kivy
from kivy.uix.label import Label
import os
from android import activity, mActivity
from jnius import autoclass
from kivy.app import App
from jnius import cast
from android.storage import app_storage_path, primary_external_storage_path, secondary_external_storage_path
Intent = autoclass('android.content.Intent')
DocumentsContract = autoclass('android.provider.DocumentsContract')
Document = autoclass('android.provider.DocumentsContract$Document')
class Demo(App):
REQUEST_CODE = 42 # unique request ID
def set_intent(self):
intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
mActivity.startActivityForResult(intent, self.REQUEST_CODE)
def intent_callback(self, requestCode, resultCode, intent):
if requestCode == self.REQUEST_CODE:
msg = ""
root_uri = intent.getData()
print(root_uri.getPath())
# /tree/primary:CarInWay
root_id = DocumentsContract.getTreeDocumentId(root_uri)
print( root_id)
# primary:CarInWay
from pathlib import Path
p = Path(root_uri.getPath()).resolve()
print(p, p.is_dir(), p.is_absolute())
# /tree/primary:CarInWay False True
p = Path(root_id).resolve()
print( p, p.is_dir(), p.is_absolute())
# /data/data/car.carinway/files/app/primary:CarInWay False True
primary_ext_storage = primary_external_storage_path()
data_dir = str(os.path.join(primary_ext_storage, 'CarInWay'))
check_data_dir = os.path.exists(data_dir)
print(data_dir , check_data_dir)
# /storage/emulated/0/CarInWay === True
p = Path(primary_ext_storage + '/CarInWay')
print('===', p, '===', p.stat().st_mode)
# /storage/emulated/0/CarInWay === 16832
settings_path = app_storage_path()
secondary_ext_storage = secondary_external_storage_path()
print(settings_path, primary_ext_storage, secondary_ext_storage)
# /data/user/0/car.carinway/files /storage/emulated/0 None
def on_start(self):
self.set_intent()
def build(self):
activity.bind(on_activity_result=self.intent_callback)
self.label = Label()
return self.label
if __name__ == '__main__':
Demo().run()
Sorry for the not quite accurate postal question. But my problem is saving the data in non-application folders, so that when the application is updated, they are not overwritten.
The solution to the problem turned out to be simple.
context = autoclass('android.content.Context')
path_file = context.getExternalFilesDir(None)
path = path_file.getAbsolutePath()
Which made it possible to create a folder in ANDROID / DATA. Where can I already create and store data.

Displaying images like a presentation without showing the desktop during update

I'm trying to display images that are stored under ./pics directory in a row like a presentation on my secondary monitor using the following Python code under Xubuntu 14.04.
from PyQt4 import QtCore, QtGui
import sys
import os
import multiprocessing as mp
import time
def displayImage( impath ):
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
# Select monitor
screenres = QtGui.QApplication.desktop().screenGeometry(1);
window.move(QtCore.QPoint(screenres.x(), screenres.y()))
window.resize(screenres.width(), screenres.height())
# Set the image to be shown
pic = QtGui.QLabel(window)
pic.setGeometry(0, 0, 1960, 1080)
pic.setPixmap(QtGui.QPixmap( impath ))
window.showFullScreen()
app.exec_()
return
def controller( imdir ):
# List the contents of the directory
im_paths = os.listdir( imdir )
# Keep only image files
pc = list(im_paths)
for p in pc:
print p
if not p.split('.')[-1] in [ 'jpg', 'JPG', 'png', 'PNG' ]:
im_paths.remove( p )
if not im_paths:
return 1
# Start showing the images by calling project function as a new process (multiprocessing module)
for f in im_paths:
print 'Showing image:', p, '...'
# Run project process
p = mp.Process( target=displayImage, args=( imdir+f, ) )
p.start()
# Keep dispaying image for 3 seconds
time.sleep(2)
p.terminate()
return 0
if __name__ == '__main__':
controller( './pics/' )
exit(0)
The issue
There is a time interval when the process that displays the image A is terminated and until the process
that displays the image B to come up where the application displays nothing and for a moment the desktop is presented destroying the user experience.
Any ideas how to display images using Qt4 continuously?
PS. Answers that involve matplotlib and/or opencv may be accepted but I think that the straight forward path is through Qt.
Finally, I figured out the solution. The alexblae's idea that the qt application should not be restarted on each image update was the key point. But first... I had to understand in some detail the architecture of the Qt framework, how the one class inherits from another and how events are generated and handled.
Let me quote the updated code in order other users to review and get advantage when facing similar problems.
from PyQt4 import QtCore, QtGui
import multiprocessing as mp
import sys
import os
import time
class Pic( QtGui.QLabel ):
def __init__( self, w, im_paths ):
QtGui.QLabel.__init__(self,w)
self.ims = im_paths
# Set the first image that will be displayed
self.setPixmap(QtGui.QPixmap( self.ims[0] ) )
self.i = 1
# Catch timer events. On each event display the next image
def timerEvent(self):
# Update the displayed image
self.setPixmap(QtGui.QPixmap( self.ims[self.i] ) )
self.i = ( self.i + 1 ) % len( self.ims )
def displayImages( im_paths ):
app = QtGui.QApplication(sys.argv)
window = QtGui.QMainWindow()
screenres = QtGui.QApplication.desktop().screenGeometry(1);
window.move(QtCore.QPoint(screenres.x(), screenres.y()))
window.resize(screenres.width(), screenres.height())
pic = Pic(window, im_paths)
pic.setGeometry(0, 0, 1960, 1080)
window.showFullScreen()
# Generate a timerEvent every 1 second. On each second the handler of
# the event updates the displayed image
timer = QtCore.QTimer()
timer.timeout.connect(pic.timerEvent)
timer.start(1000) #1 second
sys.exit(app.exec_())
def controller( imdir ):
# List the contents of the directory
im_paths = sorted( os.listdir( imdir ) )
for i, path in enumerate( im_paths ):
im_paths[i] = imdir + '/' + path
# Keep only image files
pc = list(im_paths)
for p in pc:
if not p.split('.')[-1] in [ 'jpg', 'JPG', 'png', 'PNG' ]:
im_paths.remove( p )
if not im_paths:
return 1
# Run project process
p = mp.Process( target=displayImages, args=( im_paths, ) )
p.start()
# Keep displaying images for 1 minute
time.sleep(5)
p.terminate()
return 0
if __name__ == '__main__':
controller( './pics/' )
exit(0)
Pic class inherits from QLabel in order the timerEvent method to be defined. Using timer events we are able to inform the qt app when to update the displayed image.
Also, let me note that the usage of the multiprocessing module seems to be a bit overkill. Someone may call the displayImages function directly.
I have a solution for this using OpenCV with python.
Here is the code along with the explanation:
import cv2 # for read/display images
import glob # for linking to image file (.jpg)
#--- following is the directory containing my images of jpg format ---
jpg_files=glob.glob("C:/Users/Desktop/Stack/*.jpg")
#--- Here I run through every image file, read it and then display it with a lapse time of 1 second ---
for img_no in range(len(jpg_files)):
x = jpg_files[img_no]
img = cv2.imread(x,1)
cv2.imshow('BINGO.jpg',img)
cv2.waitKey(1000)
cv2.destroyAllWindows()
Hope this helps
:D

Tkinter window not playing well with threads

I've got a program that will eventually receive data from an external source over serial, but I'm trying to develop the display-side first.
I've got this "main" module that has the simulated data send and receive. It updates a global that is used by a Matplotlib stripchart. All of this works.
#-------------------------------------------------------------------------------
# Name: BBQData
# Purpose: Gets the data from the Arduino, and runs the threads.
#-------------------------------------------------------------------------------
import time
import math
import random
from threading import Thread
import my_globals as bbq
import sys
import BBQStripChart as sc
import serial
import BBQControl as control
ser = serial.serial_for_url('loop://', timeout=10)
def simData():
newTime = time.time()
if not hasattr(simData, "lastUpdate"):
simData.lastUpdate = newTime # it doesn't exist yet, so initialize it
simData.firstTime = newTime # it doesn't exist yet, so initialize it
if newTime > simData.lastUpdate:
simData.lastUpdate = newTime
return (140 + 0.05*(simData.lastUpdate - simData.firstTime), \
145 + 0.022*(simData.lastUpdate - simData.firstTime), \
210 + random.randrange(-10, 10))
else:
return None
def serialDataPump():
testCtr = 0;
while not bbq.closing and testCtr<100:
newData = simData()
if newData != None:
reportStr = "D " + "".join(['{:3.0f} ' for x in newData]) + '\n'
reportStr = reportStr.format(*newData)
ser.write(bytes(reportStr, 'ascii'))
testCtr+=1
time.sleep(1)
bbq.closing = True
def serialDataRcv():
while not bbq.closing:
line = ser.readline()
rcvdTime = time.time()
temps = str(line, 'ascii').split(" ")
temps = temps[1:-1]
for j, x in enumerate(temps):
bbq.temps[j].append(float(x))
bbq.plotTimes.append(rcvdTime)
def main():
sendThread = Thread(target = serialDataPump)
receiveThread = Thread(target = serialDataRcv)
sendThread.start()
receiveThread.start()
# sc.runUI()
control.runControl() #blocks until user closes window
bbq.closing = True
time.sleep(2)
exit()
if __name__ == '__main__':
main()
## testSerMain()
However, I'd like to add a SEPARATE tkinter window that just has the most recent data on it, a close button, etc. I can get that window to come up, and show data initially, but none of the other threads run. (and nothing works when I try to run the window and the plot at the same time.)
#-------------------------------------------------------------------------------
# Name: BBQ Display/Control
# Purpose: displays current temp data, and control options
#-------------------------------------------------------------------------------
import tkinter as tk
import tkinter.font
import my_globals as bbq
import threading
fontSize = 78
class BBQControl(tk.Tk):
def __init__(self,parent):
tk.Tk.__init__(self,parent)
self.parent = parent
self.labelFont = tkinter.font.Font(family='Helvetica', size=int(fontSize*0.8))
self.dataFont = tkinter.font.Font(family='Helvetica', size=fontSize, weight = 'bold')
self.makeWindow()
def makeWindow(self):
self.grid()
btnClose = tk.Button(self,text=u"Close")
btnClose.grid(column=1,row=5)
lblFood = tk.Label(self,anchor=tk.CENTER, text="Food Temps", \
font = self.labelFont)
lblFood.grid(column=0,row=0)
lblPit = tk.Label(self,anchor=tk.CENTER, text="Pit Temps", \
font = self.labelFont)
lblPit.grid(column=1,row=0)
self.food1Temp = tk.StringVar()
lblFoodTemp1 = tk.Label(self,anchor=tk.E, \
textvariable=self.food1Temp, font = self.dataFont)
lblFoodTemp1.grid(column=0,row=1)
#spawn thread to update temps
updateThread = threading.Thread(target = self.updateLoop)
updateThread.start()
def updateLoop(self):
self.food1Temp.set(str(bbq.temps[1][-1]))
def runControl():
app = BBQControl(None)
app.title('BBQ Display')
app.after(0, app.updateLoop)
app.mainloop()
bbq.closing = True
if __name__ == '__main__':
runControl()
Your title sums up the problem nicely: Tkinter doesn't play well with threads. That's not a question, that's the answer.
You can only access tkinter widgets from the same thread that created the widgets. If you want to use threads, you'll need your non-gui threads to put data on a queue and have the gui thread poll the queue periodically.
One way of getting tkinter to play well with threads is to modify the library so all method calls run on a single thread. Two other questions deal with this same problem: Updating a TKinter GUI from a multiprocessing calculation and Python GUI is not responding while thread is executing. In turn, the given answers point to several modules that help to solve the problem you are facing. Whenever I work with tkinter, I always use the safetkinter module in case threads appear to be helpful in the program.

Threading in pyqt4

In my GUI I have to download lots of stuff in between. I use urllib to do that. the problem of course then becomes that the GUI freezes up until everything gets downloaded.
My code is something like following
QtCore.QObject.connect( self.UI.commandLinkButton_2 , QtCore.SIGNAL("clicked()") , self.addStoryToHistory )
wherein the above function has the downloading code.
There is nothing like sending of shared data among this and the process just involves downloading of the data to a location.
What is the simplest way to not freeze up my GUI ? Should i use multiprocessing or QThreads?
Can anybody point me to some links.... I do not wish it to be very complex so if there is any easier way do it please point it out....
Thanks a lot...
Here's an example I've just stripped from a project I was working on a couple of months back using the http example from PyQt as a base. It'll download SIP from the Riverbank website.
It's using QHttp from QtNetwork instead of urllib and the progress bar is connected to its dataReadProgress signal. This should allow you to reliably download a file as well as having a responsive GUI.
from PyQt4.QtCore import QUrl, QFileInfo, QFile, QIODevice
from PyQt4.QtGui import QApplication, QDialog, QProgressBar, QLabel, QPushButton, QDialogButtonBox, \
QVBoxLayout, QMessageBox
from PyQt4.QtNetwork import QHttp
url_to_download = 'http://www.riverbankcomputing.co.uk/static/Downloads/sip4/sip-4.12.3.zip'
class Downloader(QDialog):
def __init__(self, parent=None):
super(Downloader, self).__init__(parent)
self.httpGetId = 0
self.httpRequestAborted = False
self.statusLabel = QLabel('Downloading %s' % url_to_download)
self.closeButton = QPushButton("Close")
self.closeButton.setAutoDefault(False)
self.progressBar = QProgressBar()
buttonBox = QDialogButtonBox()
buttonBox.addButton(self.closeButton, QDialogButtonBox.RejectRole)
self.http = QHttp(self)
self.http.requestFinished.connect(self.httpRequestFinished)
self.http.dataReadProgress.connect(self.updateDataReadProgress)
self.http.responseHeaderReceived.connect(self.readResponseHeader)
self.closeButton.clicked.connect(self.cancelDownload)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.statusLabel)
mainLayout.addWidget(self.progressBar)
mainLayout.addWidget(buttonBox)
self.setLayout(mainLayout)
self.setWindowTitle('Download Example')
self.downloadFile()
def downloadFile(self):
url = QUrl(url_to_download)
fileInfo = QFileInfo(url.path())
fileName = fileInfo.fileName()
if QFile.exists(fileName):
QFile.remove(fileName)
self.outFile = QFile(fileName)
if not self.outFile.open(QIODevice.WriteOnly):
QMessageBox.information(self, 'Error',
'Unable to save the file %s: %s.' % (fileName, self.outFile.errorString()))
self.outFile = None
return
mode = QHttp.ConnectionModeHttp
port = url.port()
if port == -1:
port = 0
self.http.setHost(url.host(), mode, port)
self.httpRequestAborted = False
path = QUrl.toPercentEncoding(url.path(), "!$&'()*+,;=:#/")
if path:
path = str(path)
else:
path = '/'
# Download the file.
self.httpGetId = self.http.get(path, self.outFile)
def cancelDownload(self):
self.statusLabel.setText("Download canceled.")
self.httpRequestAborted = True
self.http.abort()
self.close()
def httpRequestFinished(self, requestId, error):
if requestId != self.httpGetId:
return
if self.httpRequestAborted:
if self.outFile is not None:
self.outFile.close()
self.outFile.remove()
self.outFile = None
return
self.outFile.close()
if error:
self.outFile.remove()
QMessageBox.information(self, 'Error',
'Download failed: %s.' % self.http.errorString())
self.statusLabel.setText('Done')
def readResponseHeader(self, responseHeader):
# Check for genuine error conditions.
if responseHeader.statusCode() not in (200, 300, 301, 302, 303, 307):
QMessageBox.information(self, 'Error',
'Download failed: %s.' % responseHeader.reasonPhrase())
self.httpRequestAborted = True
self.http.abort()
def updateDataReadProgress(self, bytesRead, totalBytes):
if self.httpRequestAborted:
return
self.progressBar.setMaximum(totalBytes)
self.progressBar.setValue(bytesRead)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
downloader = Downloader()
downloader.show()
sys.exit(app.exec_())
I suggest you to use the QNetworkManager instead and monitor the download process.
See this other question:
pyQT QNetworkManager and ProgressBars

Categories