I want to play sounds with QtMultimedia.
In the case of QMediaPlayer, I could play mp3 file , it is made from gTTS.
(I think it is okay but I don't like the file remains unless I excute codes for deleting it.)
I make a mp3 file with gTTS module and I want to play sounds with the buffer directly.
It seems that I can make a valid object but QAudioOutput doesn't do anything.
I seriarize the mp3 file into a database and fetch it when I like.
What is short of my code?
Here is the excerpt of my original Code.
In my original code, the buffer data is in Qt.UserRole + x and I can take them whenever.
The playlist is constucted with QTableWidget and QTableWidgetItem.
from PySide2 import QtCore
from PySide2 import QtWidgets
from PySide2 import QtMultimedia
import os
import PySide2
import sys
dirname = os.path.dirname(PySide2.__file__)
plugin_path = os.path.join(dirname, 'plugins', 'platforms')
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = plugin_path
import gtts
def main():
if QtWidgets.QApplication.instance() is not None:
app = QtWidgets.QApplication.instance()
else:
app = QtWidgets.QApplication([])
widget = QtWidgets.QTableWidget()
widget.setColumnCount(2)
text = "hello"
lang = "en"
onsei = gtts.gTTS(text=text, lang=lang)
buf = QtCore.QBuffer()
buf.setOpenMode( QtCore.QIODevice.WriteOnly)
onsei.write_to_fp(buf)
buf.close()
if not widget.rowCount():
widget.setRowCount(1)
else:
widget.insertRow(1)
nitem = QtWidgets.QTableWidgetItem()
nitem.setText(str(widget.rowCount()))
item = QtWidgets.QTableWidgetItem()
item.setText("{0}_tts_lang_{1}.mp3".format(text, lang))
item.setData(QtCore.Qt.UserRole+1, buf.data())
variant = item.data(QtCore.Qt.UserRole+1)
format = QtMultimedia.QAudioFormat()
format.setSampleRate(8000)
format.setChannelCount(1)
format.setSampleSize(16)
format.setCodec("audio/pcm")
format.setByteOrder(QtMultimedia.QAudioFormat.LittleEndian)
format.setSampleType(QtMultimedia.QAudioFormat.UnSignedInt)
buf = QtCore.QBuffer()
buf.setData(variant)
buf.open(QtCore.QIODevice.ReadOnly)
buf.seek(0)
audio = QtMultimedia.QAudioOutput(format, app)
audio.setVolume(0.5)
audio.setBufferSize(buf.size())
audio.start(buf)
buf.close()
print(67)
# sys.exit(QtWidgets.QApplication.exec_())
sys.exit()
if __name__ == "__main__":
main()
Instead of using QAudioOutput you could use QMediaPlayer:
import sys
import threading
import uuid
from PySide2 import QtCore, QtGui, QtWidgets, QtMultimedia
import gtts
IdentifierRole = QtCore.Qt.UserRole
DataRole = QtCore.Qt.UserRole + 1
DownLoadRole = QtCore.Qt.UserRole + 2
ActiveRole = QtCore.Qt.UserRole + 3
class BackgroundColorDelegate(QtWidgets.QStyledItemDelegate):
def initStyleOption(self, option, index):
super().initStyleOption(option, index)
color = None
if index.data(DownLoadRole):
color = QtGui.QColor("green")
if index.data(ActiveRole):
color = QtGui.QColor("red")
if color:
option.backgroundBrush = color
class DownLoader(QtCore.QObject):
downloaded = QtCore.Signal(str, QtCore.QByteArray)
def start(self, identifier, text, lang):
threading.Thread(
target=self._execute, args=(identifier, text, lang), daemon=True
).start()
def _execute(self, identifier, text, lang):
tts = gtts.gTTS(text=text, lang=lang)
buf = QtCore.QBuffer()
buf.open(QtCore.QBuffer.ReadWrite)
tts.write_to_fp(buf)
self.downloaded.emit(identifier, buf.data())
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.player = QtMultimedia.QMediaPlayer()
self.current_buff = QtCore.QBuffer()
self.tablewidget = QtWidgets.QTableWidget(
0,
2,
selectionBehavior=QtWidgets.QAbstractItemView.SelectRows,
editTriggers=QtWidgets.QAbstractItemView.NoEditTriggers,
)
delegate = BackgroundColorDelegate(self.tablewidget)
self.tablewidget.setItemDelegateForColumn(0, delegate)
self.tablewidget.itemClicked.connect(self.on_item_clicked)
self.setCentralWidget(self.tablewidget)
self.add_row("hello", "en")
self.add_row("world", "en")
def add_row(self, text, lang):
it = QtWidgets.QTableWidgetItem("{0}_tts_lang_{1}.mp3".format(text, lang))
identifier = str(uuid.uuid4())
it.setData(IdentifierRole, identifier)
downloader = DownLoader(self)
downloader.start(identifier, text, lang)
downloader.downloaded.connect(self.on_downloaded)
downloader.downloaded.connect(downloader.deleteLater)
row = self.tablewidget.rowCount()
self.tablewidget.insertRow(row)
self.tablewidget.setItem(row, 0, it)
#QtCore.Slot(str, QtCore.QByteArray)
def on_downloaded(self, identifier, data):
model = self.tablewidget.model()
indexes = model.match(
model.index(0, 0), IdentifierRole, identifier, flags=QtCore.Qt.MatchExactly
)
if indexes:
item = self.tablewidget.itemFromIndex(indexes[0])
item.setData(DataRole, data)
item.setData(DownLoadRole, True)
#QtCore.Slot("QTableWidgetItem*")
def on_item_clicked(self, item):
self.player.stop()
self.current_buff.close()
data = item.data(DataRole)
if not data:
return
self.current_buff.setData(data)
self.current_buff.open(QtCore.QIODevice.ReadOnly)
self.player.setMedia(QtMultimedia.QMediaContent(), self.current_buff)
self.player.play()
for row in range(self.tablewidget.rowCount()):
it = self.tablewidget.item(row, 0)
it.setData(ActiveRole, it is item)
def main():
app = QtWidgets.QApplication.instance()
if app is None:
app = QtWidgets.QApplication([])
w = MainWindow()
w.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Related
How can I set the audio output of a QMediaPlayer to a specific output in Windows 7 and later?
This was really easy in PySide (using Phonon) but I can't find a way to do it in PySide2.
There are some related questions already, like this old but still not solved one, or this one that asks exactly what I want.
They are both in c++ and its difficult to convert it to PySide2.
The second one is answered with this code:
QMediaService *svc = player->service();
if (svc != nullptr)
{
QAudioOutputSelectorControl *out = qobject_cast<QAudioOutputSelectorControl *>
(svc->requestControl(QAudioOutputSelectorControl_iid));
if (out != nullptr)
{
out->setActiveOutput(this->ui->comboBox->currentText());
svc->releaseControl(out);
}
}
Another one with an attempt to python conversion didn't work also.
I tried to convert them to Python code, but the result was not successful.
Here is my minimal attempt:
import sys
from PySide2 import QtMultimedia
from PySide2.QtCore import QUrl, Qt
from PySide2.QtMultimedia import QMediaPlayer, QMediaContent
from PySide2.QtWidgets import (QPushButton, QSlider, QHBoxLayout, QVBoxLayout,
QFileDialog, QStyle, QApplication, QDialog, QComboBox)
class Window(QDialog):
def __init__(self):
super().__init__()
self.out_combo = QComboBox()
mode = QtMultimedia.QAudio.AudioOutput
devices = QtMultimedia.QAudioDeviceInfo.availableDevices(mode)
for item in [(dev.deviceName(), dev) for dev in devices]:
self.out_combo.addItem(item[0], item[1])
self.out_combo.currentIndexChanged.connect(self.out_device_changed)
openBtn = QPushButton('Open file')
openBtn.clicked.connect(self.open_file)
self.playBtn = QPushButton()
self.playBtn.setEnabled(False)
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
self.playBtn.clicked.connect(self.play_file)
self.slider = QSlider(Qt.Horizontal)
self.slider.setRange(0, 0)
self.slider.sliderMoved.connect(self.set_position)
hor_layout = QHBoxLayout()
hor_layout.setContentsMargins(0, 0, 0, 0)
hor_layout.addWidget(openBtn)
hor_layout.addWidget(self.playBtn)
hor_layout.addWidget(self.slider)
ver_layout = QVBoxLayout()
ver_layout.addWidget(self.out_combo)
ver_layout.addLayout(hor_layout)
self.setLayout(ver_layout)
self.player = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.player.stateChanged.connect(self.mediastate_changed)
self.player.positionChanged.connect(self.position_changed)
self.player.durationChanged.connect(self.duration_changed)
self.show()
def open_file(self):
file_name, _ = QFileDialog.getOpenFileName(self, "Open file")
if file_name != '':
self.player.setMedia(QMediaContent(QUrl.fromLocalFile(file_name)))
# self.label.setText(basename(file_name))
self.playBtn.setEnabled(True)
def play_file(self):
if self.player.state() == QMediaPlayer.PlayingState:
self.player.pause()
else:
self.player.play()
def mediastate_changed(self, state):
if self.player.state() == QMediaPlayer.PlayingState:
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
else:
self.playBtn.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
def position_changed(self, position):
self.slider.setValue(position)
def duration_changed(self, duration):
self.slider.setRange(0, duration)
def set_position(self, position):
self.player.setPosition(position)
def out_device_changed(self, idx):
device = self.out_combo.itemData(idx)
service = self.player.service()
if service:
out = service.requestControl("org.qt-project.qt.mediastreamscontrol/5.0")
if out:
out.setActiveOutput(self.out_combo.currentText())
service.releaseControl(out)
else:
print("No output found!")
app = QApplication(sys.argv)
window = Window()
sys.exit(app.exec_())
I'm looking for help regarding how to add a QTableView inside a QGroupBox (this is because i need to create 4 QTableView each displaying one of each possible Status 'In QC','Ready for QC','In Progress','Pending').
The following code currently generates a program that displays a Single QTableView, that refreshes every 5 seconds with new data, the only one that matters is the Status (currently represented as column F) as the rest of the data is displayed for identification purposes. (Please note that in this example i use a code that generates automatically data to display in the QTableView, as this Table actually feeds from an Excel file, going to attach the code that reads the excel file at the end of this post):
import sys
import pandas as pd
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QAbstractTableModel, QObject, Qt
from PyQt5.QtGui import QBrush
from PyQt5.QtWidgets import QApplication, QTableView
import threading
class PandasManager(QObject):
dataFrameChanged = pyqtSignal(pd.DataFrame)
def start(self):
self.t = threading.Timer(0, self.load)
self.t.start()
def load(self):
import random
headers = list("ABCDEFG")
data = [random.sample(range(255), len(headers)) for _ in headers]
for d in data:
d[5] = random.choice(["Ready for QC", "In Progress", "Pending", "In QC"])
df = pd.DataFrame(data, columns=headers,)
self.dataFrameChanged.emit(df)
self.t = threading.Timer(5.0, self.load)
self.t.start()
def stop(self):
self.t.cancel()
class PandasModel(QAbstractTableModel):
def __init__(self, df=pd.DataFrame()):
QAbstractTableModel.__init__(self)
self._df = df
#pyqtSlot(pd.DataFrame)
def setDataFrame(self, df):
self.beginResetModel()
self._df = df
self.endResetModel()
def rowCount(self, parent=None):
return self._df.shape[0]
def columnCount(self, parent=None):
return self._df.shape[1]
def data(self, index, role=Qt.DisplayRole):
if index.isValid():
if role == Qt.BackgroundRole:
if self.columnCount() >= 6:
it = self._df.iloc[index.row(), 5]
if it == "Ready for QC":
return QBrush(Qt.yellow)
if it == "In Progress":
return QBrush(Qt.green)
if role == Qt.DisplayRole:
return str(self._df.iloc[index.row(), index.column()])
def headerData(self, col, orientation, role):
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
return self._df.columns[col]
return None
if __name__ == "__main__":
app = QApplication(sys.argv)
w = QTableView()
model = PandasModel()
w.setModel(model)
w.show()
manager = PandasManager()
manager.dataFrameChanged.connect(model.setDataFrame)
manager.start()
ret = app.exec_()
manager.stop()
sys.exit(ret)
Hopefully this explains my question, as i have been struggling on how to use QGroupBox and how to add the QTableView as I'm using it this way.
Kind Regards,
PS: Attaching code that reads from an excel file
def load(self):
weekNumber = date.today().isocalendar()[1]
aux = pd.read_excel("PCS tasks 2020.xlsm", sheet_name="W" + str(weekNumber))
today = datetime.today()
df = aux[aux["Date Received"] == today.strftime("%Y-%d-%m")]
df = df[
[
"Requestor",
"Subject",
"Task type",
"Created by",
"QC Executive",
"Status",
]
].fillna("")
df = df[df["Status"] != "Completed"]
self.dataFrameChanged.emit(df)
self.t = threading.Timer(5.0, self.load)
self.t.start()
As the example of the QGroupBox docs shows, you must use a layout that allows you to distribute the widgets (QGridLayout, QHBoxLayout, QVBoxLayout, etc.) and set it in the QGroupBox:
if __name__ == "__main__":
app = QApplication(sys.argv)
w = QTableView()
model = PandasModel()
w.setModel(model)
groupbox = QGroupBox()
lay = QVBoxLayout()
lay.addWidget(w)
groupbox.setLayout(lay)
groupbox.show()
manager = PandasManager()
manager.dataFrameChanged.connect(model.setDataFrame)
manager.start()
ret = app.exec_()
manager.stop()
sys.exit(ret)
I am creating a program to play videos and then process them. I am able to play the videos with QMediaPlayer. How do I access specific frames as images or something similar. My end goal would be to format the video into a 4-d tensor of size [Num of frames, width_of_video, height_of_video, channels].
Here is the code that loads my video.:
self.clear_layout(self.vlayout)
videoItem = QVideoWidget()
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.mediaPlayer.durationChanged.connect(self.update_duration)
self.mediaPlayer.positionChanged.connect(self.update_slider_position)
self.vlayout.addWidget(videoItem)
self.mediaPlayer.setVideoOutput(videoItem)
local = QUrl.fromLocalFile(self.video_paths)
media = QMediaContent(local)
self.mediaPlayer.setMedia(media)
self.play_video()
Here is a working example that I converted to Python from the C++ version available in this question: How to save a frame using QMediaPlayer?
import sys
import uuid
import PyQt5
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt, QObject, QUrl, QRect, pyqtSignal, QPoint
from PyQt5.QtGui import QPainter, QImage
from PyQt5.QtWidgets import QWidget, QApplication, QMainWindow, QGridLayout, QToolBar, QAction
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAbstractVideoBuffer, \
QVideoFrame, QVideoSurfaceFormat, QAbstractVideoSurface
from PyQt5.QtMultimediaWidgets import QVideoWidget
class VideoFrameGrabber(QAbstractVideoSurface):
frameAvailable = pyqtSignal(QImage)
def __init__(self, widget: QWidget, parent: QObject):
super().__init__(parent)
self.widget = widget
def supportedPixelFormats(self, handleType):
return [QVideoFrame.Format_ARGB32, QVideoFrame.Format_ARGB32_Premultiplied,
QVideoFrame.Format_RGB32, QVideoFrame.Format_RGB24, QVideoFrame.Format_RGB565,
QVideoFrame.Format_RGB555, QVideoFrame.Format_ARGB8565_Premultiplied,
QVideoFrame.Format_BGRA32, QVideoFrame.Format_BGRA32_Premultiplied, QVideoFrame.Format_BGR32,
QVideoFrame.Format_BGR24, QVideoFrame.Format_BGR565, QVideoFrame.Format_BGR555,
QVideoFrame.Format_BGRA5658_Premultiplied, QVideoFrame.Format_AYUV444,
QVideoFrame.Format_AYUV444_Premultiplied, QVideoFrame.Format_YUV444,
QVideoFrame.Format_YUV420P, QVideoFrame.Format_YV12, QVideoFrame.Format_UYVY,
QVideoFrame.Format_YUYV, QVideoFrame.Format_NV12, QVideoFrame.Format_NV21,
QVideoFrame.Format_IMC1, QVideoFrame.Format_IMC2, QVideoFrame.Format_IMC3,
QVideoFrame.Format_IMC4, QVideoFrame.Format_Y8, QVideoFrame.Format_Y16,
QVideoFrame.Format_Jpeg, QVideoFrame.Format_CameraRaw, QVideoFrame.Format_AdobeDng]
def isFormatSupported(self, format):
imageFormat = QVideoFrame.imageFormatFromPixelFormat(format.pixelFormat())
size = format.frameSize()
return imageFormat != QImage.Format_Invalid and not size.isEmpty() and \
format.handleType() == QAbstractVideoBuffer.NoHandle
def start(self, format: QVideoSurfaceFormat):
imageFormat = QVideoFrame.imageFormatFromPixelFormat(format.pixelFormat())
size = format.frameSize()
if imageFormat != QImage.Format_Invalid and not size.isEmpty():
self.imageFormat = imageFormat
self.imageSize = size
self.sourceRect = format.viewport()
super().start(format)
self.widget.updateGeometry()
self.updateVideoRect()
return True
else:
return False
def stop(self):
self.currentFrame = QVideoFrame()
self.targetRect = QRect()
super().stop()
self.widget.update()
def present(self, frame):
if frame.isValid():
cloneFrame = QVideoFrame(frame)
cloneFrame.map(QAbstractVideoBuffer.ReadOnly)
image = QImage(cloneFrame.bits(), cloneFrame.width(), cloneFrame.height(),
QVideoFrame.imageFormatFromPixelFormat(cloneFrame.pixelFormat()))
self.frameAvailable.emit(image) # this is very important
cloneFrame.unmap()
if self.surfaceFormat().pixelFormat() != frame.pixelFormat() or \
self.surfaceFormat().frameSize() != frame.size():
self.setError(QAbstractVideoSurface.IncorrectFormatError)
self.stop()
return False
else:
self.currentFrame = frame
self.widget.repaint(self.targetRect)
return True
def updateVideoRect(self):
size = self.surfaceFormat().sizeHint()
size.scale(self.widget.size().boundedTo(size), Qt.KeepAspectRatio)
self.targetRect = QRect(QPoint(0, 0), size)
self.targetRect.moveCenter(self.widget.rect().center())
def paint(self, painter):
if self.currentFrame.map(QAbstractVideoBuffer.ReadOnly):
oldTransform = self.painter.transform()
if self.surfaceFormat().scanLineDirection() == QVideoSurfaceFormat.BottomToTop:
self.painter.scale(1, -1)
self.painter.translate(0, -self.widget.height())
image = QImage(self.currentFrame.bits(), self.currentFrame.width(), self.currentFrame.height(),
self.currentFrame.bytesPerLine(), self.imageFormat)
self.painter.drawImage(self.targetRect, image, self.sourceRect)
self.painter.setTransform(oldTransform)
self.currentFrame.unmap()
class App(QApplication):
def __init__(self, sys_argv):
super().__init__(sys_argv)
# Show main window
self.view = QMainWindow()
self.centralWidget = QWidget(self.view)
self.gridLayout = QGridLayout(self.centralWidget)
self.gridLayout.setContentsMargins(0, 0, 0, 0)
self.gridLayout.setSpacing(0)
self.video_item = QVideoWidget()
self.gridLayout.addWidget(self.video_item)
self.view.setCentralWidget(self.centralWidget)
self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
self.grabber = VideoFrameGrabber(self.video_item, self)
self.mediaPlayer.setVideoOutput(self.grabber)
self.grabber.frameAvailable.connect(self.process_frame)
self.mediaPlayer.durationChanged.connect(self.update_duration)
self.mediaPlayer.positionChanged.connect(self.update_slider_position)
local = QUrl.fromLocalFile('c:/temp/lorem.mp4')
media = QMediaContent(local)
self.mediaPlayer.setMedia(media)
self.mediaPlayer.play()
self.view.show()
def process_frame(self, image):
# Save image here
image.save('c:/temp/{}.jpg'.format(str(uuid.uuid4())))
def update_duration(self):
pass
def update_slider_position(self):
pass
if __name__ == '__main__':
def except_hook(cls, exception, traceback):
sys.__excepthook__(cls, exception, traceback)
if hasattr(QtCore.Qt, 'AA_EnableHighDpiScaling'):
PyQt5.QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'):
PyQt5.QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
app = App(sys.argv)
app.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling)
sys.excepthook = except_hook
sys.exit(app.exec_())
How can I use the window handle to screenshot a running instance of Notepad? I already figured out how to successfully screenshot a widget within the python dialog itself. I have also figured out how to get the handle of the running notepad window. I'm stuck on trying to capture the screenshot of the window using the handle.
import os, sys
from PySide import QtGui, QtCore
import ctypes
class GuiCaptureWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(GuiCaptureWindow, self).__init__(parent)
self.resize(250, 100)
self.setWindowTitle('GUI Capture')
# console
self.ui_capture = QtGui.QPushButton('Capture')
main_layout = QtGui.QGridLayout()
main_layout.addWidget(self.ui_capture)
main_widget = QtGui.QWidget()
main_widget.setLayout(main_layout)
self.setCentralWidget(main_widget)
# signals
self.ui_capture.clicked.connect(self.capture)
def getRelativeFrameGeometry(self, widget):
g = widget.geometry()
fg = widget.frameGeometry()
return fg.translated(-g.left(),-g.top())
def screenCaptureWidget(self, widget, filename, fileformat='.png'):
rfg = self.getRelativeFrameGeometry(widget)
pixmap = QtGui.QPixmap.grabWindow(widget.winId(),
rfg.left(), rfg.top(),
rfg.width(), rfg.height())
filepath = os.path.abspath(filename + fileformat)
pixmap.save(filepath)
os.system("start " + filepath)
def capture(self):
self.collect_window_titles()
self.screenCaptureWidget(self.ui_capture, 'test')
def collect_window_titles(self):
EnumWindows = ctypes.windll.user32.EnumWindows
EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.POINTER(ctypes.c_int), ctypes.POINTER(ctypes.c_int))
GetWindowText = ctypes.windll.user32.GetWindowTextW
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
IsWindowVisible = ctypes.windll.user32.IsWindowVisible
apps = []
def foreach_window(hwnd, lParam):
if IsWindowVisible(hwnd):
length = GetWindowTextLength(hwnd)
buff = ctypes.create_unicode_buffer(length + 1)
GetWindowText(hwnd, buff, length + 1)
if buff.value:
apps.append({'title': buff.value, 'handle': hwnd})
return True
EnumWindows(EnumWindowsProc(foreach_window), 0)
for app in apps:
print app
if app['title'] == 'Untitled - Notepad':
print 'CAPTURE'
return
def main():
app = QtGui.QApplication(sys.argv)
ex = GuiCaptureWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
So I'm trying to make a simple file downloader in Python 3.4.2 and PyQt5
QThreads seems to be the way but there's no tutorials online or examples that I could understand for PyQt5. All I could find was Qt5 Reference for C/C++ and bunch of PyQt4 tutorials that don't work for PyQt5 and Python 3
Here's the GUI screenshot: http://i.imgur.com/KGjqRRK.png
And here's my code:
#!usr/bin/env python3
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from string import Template
import urllib.request
import sys
class Form(QWidget):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
lblUrl= QLabel("File URL:")
self.txtURL = QLineEdit()
self.bttDL = QPushButton("&Download")
self.pbar = QProgressBar()
self.pbar.setMinimum(0)
buttonLayout1 = QVBoxLayout()
buttonLayout1.addWidget(lblUrl)
buttonLayout1.addWidget(self.txtURL)
buttonLayout1.addWidget(self.bttDL)
buttonLayout1.addWidget(self.pbar)
self.bttDL.clicked.connect(self.bttPush)
mainLayout = QGridLayout()
mainLayout.addLayout(buttonLayout1, 0, 1)
self.setLayout(mainLayout)
self.setWindowTitle("pySFD")
def bttPush(self):
# check if the download is already running or just disable the button
# while it's running
url = self.txtURL.text()
if url == "":
QMessageBox.information(self, "Empty URL",
"Please enter the URL of the file you want to download.")
return
else:
filename = str(QFileDialog.getSaveFileName(self, 'Choose the download location and file name', '.'))
filename = filename[:-6]
filename = filename.split("('",maxsplit=1)[1]
self.dlThread = downloaderThread()
self.dlThread.connect(dlThread.run)
self.dlThread.start()
self.dlThread.emit(url)
class downloaderThread(QThread):
def __init__(self):
QThread.__init__(self)
def __del__(self):
self.wait()
def run(self, dlLink):
while dlLink.length == 0:
QThread.sleep(1)
pass
def report(block_count, block_size, total_size):
if block_count == 0:
self.pbar.setValue(0)
if (block_count * block_size) == total_size:
QMessageBox.information(self,"Done!","Download finished!")
return
self.pbar.setValue(self.pbar.value() + block_size)
urllib.request.urlretrieve(dlLink, filename, reporthook=report)
self.terminate()
if __name__ == '__main__':
app = QApplication(sys.argv)
screen = Form()
screen.show()
sys.exit(app.exec_())
I've tried a lot of ways and it just doesn't seem to work.
How do I make the progress bar (self.pbar) show the download progress in real time?
PS. This downloader is on my GitHub: https://github.com/dKatara/pySFD
Fixed it, it's not done yet, but threading works great!
Most recent version is on my GitHub here: https://github.com/dKatara/pySFD
#!usr/bin/env python3
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import urllib.request
import sys
import threading
dlThread = 0
hWindow = 0
fProgressCounter = 0.0
class Form(QWidget):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
global hWindow
hWindow = self
lblUrl = QLabel("File URL:")
self.txtURL = QLineEdit()
self.bttDL = QPushButton("&Download")
self.pbar = QProgressBar()
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
buttonLayout1 = QVBoxLayout()
buttonLayout1.addWidget(lblUrl)
buttonLayout1.addWidget(self.txtURL)
buttonLayout1.addWidget(self.bttDL)
buttonLayout1.addWidget(self.pbar)
self.bttDL.clicked.connect(self.bttPush)
mainLayout = QGridLayout()
mainLayout.addLayout(buttonLayout1, 0, 1)
self.setLayout(mainLayout)
self.setWindowTitle("pySFD")
def bttPush(self):
global dlThread
hSignals = sigHandling()
hSignals.dlProgress_update.connect(hSignals.pbar_incrementer)
hSignals.dlProgress_done.connect(hSignals.dlDone)
url = self.txtURL.text()
if url == "":
QMessageBox.information(self, "Empty URL",
"Please enter the URL of the file you want to download.")
return
else:
filename = str(QFileDialog.getSaveFileName(self, 'Choose the download location and file name', '.')) ## DETECT A CANCEL
filename = filename[:-6]
filename = filename.split("('",maxsplit=1)[1]
self.bttDL.setEnabled(False)
dlThread = threading.Thread(target=hSignals.runDL,args=(url, filename))
dlThread.start()
return
def pbarIncValue(self, val):
global fProgressCounter
#print("pbarIncValue({0})\nfProgressCounter={1}".format(val,fProgressCounter))
if self.pbar.value() >= 100:
self.dlProgress_done.emit()
return
if fProgressCounter > 1.0: # FIX
self.pbar.setValue(self.pbar.value() + 1)
fProgressCounter -= 1.0
fProgressCounter += val
else:
fProgressCounter += val
class sigHandling(QObject):
dlProgress_update = pyqtSignal(float)
dlProgress_done = pyqtSignal()
#pyqtSlot(float)
def pbar_incrementer(self, val):
hWindow.pbarIncValue(val)
#pyqtSlot()
def dlDone(self):
print("in dlDone")
hWindow.pbar.setValue(100)
hWindow.bttDL.setEnabled(True)
def runDL(self, dlLink, filename):
#print("in run")
global dlThread, hWindow
def report(block_count, block_size, total_size):
if block_count == 0:
#print("block_count == 0")
self.dlProgress_update.emit(0)
if (block_count * block_size) == total_size:
self.dlProgress_done.emit()
incAmount = float((100*block_size) / total_size)
#print("BS={0} TS={1} incAmount={2}".format(block_size,total_size,incAmount))
self.dlProgress_update.emit(incAmount)
urllib.request.urlretrieve(dlLink, filename, reporthook=report)
#print("emit dlProgress_done")
self.dlProgress_done.emit()
#print("about to leave dlThread")
pass
if __name__ == '__main__':
app = QApplication(sys.argv)
screen = Form()
screen.show()
sys.exit(app.exec_())