Home > Enterprise >  PyQt5: QProgressBar will not update
PyQt5: QProgressBar will not update

Time:04-20

I have an application where one of its functions is to use pytube to download YouTube videos. Everything works perfectly except for updating the QProgressBar with the download progress. Please see below code:

import pytube

import PyQt5.QtWidgets as QtWidgets
import PyQt5.QtCore as QtCore


class Worker(QtCore.QObject):
    finished = QtCore.pyqtSignal()
    progress = QtCore.pyqtSignal(str, int, int)

    def __init__(self, url, save_path, fname):
        super(Worker, self).__init__()
        self.url = url
        self.save_path = save_path
        self.fname = fname
        self.perc_dl = 0

        self.download_video()

    def run(self):
        if self.perc_dl < 100:
            self.progress.emit('Downloading: {}%'.format(str(self.perc_dl)), int(self.perc_dl), 100)
            print(self.perc_dl)

        else:
            self.progress.emit('Done!', 100, 100)
            self.finished.emit()

    def download_video(self):
        yt = pytube.YouTube(self.url, on_progress_callback=self.progress_function)
        stream = yt.streams.filter(progressive=True, file_extension='mp4').get_highest_resolution()
        stream.download(output_path=self.save_path, filename=self.fname)

    def progress_function(self, stream, chunk, bytes_remaining):
        curr = stream.filesize - bytes_remaining
        per_downloaded = round((curr / stream.filesize) * 100, 1)
        self.perc_dl = per_downloaded
        self.run()


class DownloadFromYT(QtWidgets.QDialog):
    def __init__(self, url, save_path, fname):
        super(DownloadFromYT, self).__init__()
        self.url = url
        self.save_path = save_path
        self.fname = fname

        self.vLayoutMaster = QtWidgets.QVBoxLayout()
        self.hLayout = QtWidgets.QHBoxLayout()
        self.hLayout.setAlignment(QtCore.Qt.AlignRight)

        self.label = QtWidgets.QLabel()
        self.label.setText('Downloading video')

        self.pBar = QtWidgets.QProgressBar()
        self.pBar.setInvertedAppearance(True)
        self.pBar.setTextVisible(True)
        self.pBar.setFixedWidth(200)
        self.pBar.setAlignment(QtCore.Qt.AlignCenter)
        self.pBar.setMaximum(100)
        self.pBar.setFormat('Downloading: ')

        self.noButton = QtWidgets.QPushButton('No')
        self.noButton.setFixedWidth(50)
        self.noButton.hide()
        self.yesButton = QtWidgets.QPushButton('Yes')
        self.yesButton.setFixedWidth(50)
        self.yesButton.hide()

        self.vLayoutMaster.addWidget(self.label)
        self.vLayoutMaster.addSpacing(10)
        self.vLayoutMaster.addWidget(self.pBar)
        self.vLayoutMaster.addSpacing(20)
        self.hLayout.addWidget(self.noButton)
        self.hLayout.addWidget(self.yesButton)
        self.vLayoutMaster.addLayout(self.hLayout)

        # Signals / slots
        self.noButton.clicked.connect(self.reject)
        self.yesButton.clicked.connect(self.accept)

        # Widget
        self.setLayout(self.vLayoutMaster)
        self.setWindowTitle('Downloading')
        self.setFixedSize(250, 100)
        self.show()

        # Init download
        self.run_thread(url, save_path, fname)

    def run_thread(self, url, save_path, fname):
        self.thrd = QtCore.QThread()
        self.worker = Worker(url, save_path, fname)
        self.worker.moveToThread(self.thrd)

        self.thrd.start()
        self.thrd.started.connect(self.worker.run)
        self.worker.finished.connect(self.thrd.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.worker.progress.connect(self.show_progress)
        self.thrd.finished.connect(self.thrd.deleteLater)

    def show_progress(self, label, n, total):
        self.pBar.setFormat(label)
        self.pBar.setMaximum(total)
        self.pBar.setValue(n)
        QtCore.QCoreApplication.processEvents()

        if label == 'Done!':
            self.thrd.quit()
            self.noButton.show()
            self.yesButton.show()
            self.label.setText('Video has been downloaded. Would you like to set the chosen\n'
                               'file path as the "Local file" path?')

I have used this basic structure in other parts of my application to display and update a progress bar with no issues, however for some reason here, self.pBar refuses to update when the progress signal is emitted from the Worker class. As you can see, I have a print() statement in the run method to check that the code is actually entering that if block and periodically printing out the download progress to the console, which it is. The application updates when the thread has finished running, so I get the final, completely filled progress bar, but in between the start and end the progress bar does not update as expected.

Any help would be greatly appreciated!

CodePudding user response:

The problem seems to be that despite all your effort to wire up all that thread-worker components correctly, you are still running self.download_video() in the main (UI) thread. And therefore it is blocking refreshing of the UI. If you want to run in in the secondary thread, you must start it in Worker.run() function and not in the Worker constructor. If you wire up all threading correctly, then you should not need to call processEvents() at all to refresh UI.

If you are unsure about the thread in which the download function runs, print the thread ID in your print function and compare it to thread id of the main UI thread. Have a look at https://doc.qt.io/qt-5/qthread.html#currentThread

  • Related