GUI在下载PyQt5和Pytube时冻结

2022-02-23 00:00:00 python python-3.x pyqt5 pytube

问题描述

我已经用Python制作了一个桌面应用程序,并使用了可以从YouTube下载视频的PyQt5和Pytube。当下载正在进行时,我想向用户显示动画。实际上我是这样做的,但是当文件正在下载时,PyQt窗口似乎冻结了,一切都会暂停,直到下载完成。有人知道为什么会发生这种事吗?我如何修复它?

以下是代码片段:

def download_created(self, qual): # Used in 'selection' method
    selected_stream = yt.streams.get_by_resolution(qual)
    self.progress_func()
    try:
        self.download_btn.setCurrentIndex(-1)
        selected_stream.download(self.askLocation() + "/")
    except:
        pass
    
# This gets the quality that the user chooses
def selection(self):
    global quality
    quality = self.download_btn.currentText()
    try:
        self.download_created(quality) # Calls a method called 'download'
    except:
        self.start_anime()
# Fetching the details about the Link from Youtube
def download_youtube(self):
    global check
    if check != self.get_input():
        check = self.get_input()
        self.download_btn.clear()
        enter_url = self.get_input()
        try:
            global yt
            yt = pytube.YouTube(
                enter_url,
                on_progress_callback = on_progress, 
                on_complete_callback = self.complete_func)
            
            self.start_anime()
        except:
            self.input_error()
        VIDEO_TITLE = (yt.title)
        global VIDEO_ID
        VIDEO_ID = (yt.video_id)
        videos = yt.streams.filter(mime_type="video/mp4", progressive="True")

        # Display all the available qualities
        for i in videos:
            self.download_btn.addItem(i.resolution)
        self.download_btn.currentIndexChanged.connect(self.selection)

解决方案

您必须在另一个线程中执行耗时的任务,例如,在您的情况下,执行获取流和下载的任务。

import sys
import threading
from functools import cached_property

from PyQt5 import QtCore, QtWidgets

import pytube


class QPyTube(QtCore.QObject):
    initialized = QtCore.pyqtSignal(bool, str)
    download_started = QtCore.pyqtSignal()
    download_progress_changed = QtCore.pyqtSignal(int)
    download_finished = QtCore.pyqtSignal()

    def __init__(self, url):
        super().__init__()
        self._url = url
        self._yt = None
        self._mutex = threading.Lock()

        threading.Thread(target=self._init, daemon=True).start()

    @property
    def url(self):
        return self._url

    @cached_property
    def resolutions(self):
        return list()

    def _init(self):
        with self._mutex:
            self.resolutions.clear()
        try:
            self._yt = pytube.YouTube(
                self.url,
                on_progress_callback=self._on_progress,
                on_complete_callback=self._on_complete,
            )
            streams = self._yt.streams.filter(mime_type="video/mp4", progressive="True")
        except Exception as e:
            self.initialized.emit(False, str(e))
            return
        with self._mutex:
            self.resolutions = [stream.resolution for stream in streams]
        self.initialized.emit(True, "")

    def download(self, resolution, directory):
        threading.Thread(
            target=self._download, args=(resolution, directory), daemon=True
        ).start()

    def _download(self, resolution, directory):
        stream = self._yt.streams.get_by_resolution(resolution)
        self.download_started.emit()
        stream.download(directory)

    def _on_progress(self, stream, chunk, bytes_remaining):
        self.download_progress_changed.emit(
            100 * (stream.filesize - bytes_remaining) // stream.filesize
        )

    def _on_complete(self, stream, filepath):
        self.download_finished.emit()


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.le_url = QtWidgets.QLineEdit("http://youtube.com/watch?v=2lAe1cqCOXo")
        self.lbl_error = QtWidgets.QLabel()
        self.btn_search = QtWidgets.QPushButton("Search")
        self.cmb_resolutions = QtWidgets.QComboBox()
        self.le_directory = QtWidgets.QLineEdit("")
        self.btn_download = QtWidgets.QPushButton("Download")
        self.pgb_download = QtWidgets.QProgressBar()

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)
        lay = QtWidgets.QGridLayout(central_widget)
        lay.addWidget(self.le_url, 0, 0)
        lay.addWidget(self.btn_search, 0, 1)
        lay.addWidget(self.cmb_resolutions, 1, 0)
        lay.addWidget(self.le_directory, 1, 1)
        lay.addWidget(self.btn_download, 1, 2)
        lay.addWidget(self.pgb_download, 2, 0, 1, 3)

        self.btn_download.setEnabled(False)

        self._qpytube = None

        self.btn_search.clicked.connect(self.handle_search_clicked)
        self.btn_download.clicked.connect(self.handle_download_clicked)

    def handle_search_clicked(self):
        self.cmb_resolutions.clear()
        self.btn_search.setEnabled(False)
        self.btn_download.setEnabled(False)
        self.lbl_error.clear()
        self._qpytube = QPyTube(self.le_url.text())
        self._qpytube.initialized.connect(self.handle_initialized)
        self._qpytube.download_progress_changed.connect(self.pgb_download.setValue)
        self._qpytube.download_started.connect(self.handle_download_started)
        self._qpytube.download_finished.connect(self.handle_download_finished)

    @QtCore.pyqtSlot(bool, str)
    def handle_initialized(self, status, error=""):
        if status:
            self.cmb_resolutions.addItems(self._qpytube.resolutions)
            self.btn_download.setEnabled(True)
        else:
            self.lbl_error.setText(error)
        self.btn_search.setEnabled(True)

    def handle_download_clicked(self):
        self._qpytube.download(
            self.cmb_resolutions.currentText(), self.le_directory.text()
        )
        self.btn_search.setEnabled(False)
        self.btn_download.setEnabled(False)
        self.le_directory.setEnabled(False)

    def handle_download_started(self):
        self.lbl_error.clear()
        print("started")

    def handle_download_finished(self):
        self.pgb_download.setValue(100)
        self.btn_search.setEnabled(True)
        self.btn_download.setEnabled(True)
        self.le_directory.setEnabled(True)
        print("finished")


def main(args):
    app = QtWidgets.QApplication(args)

    w = MainWindow()
    w.show()

    app.exec_()


if __name__ == "__main__":
    main(sys.argv)

相关文章