【问题标题】:GUI Freezes while downloading PyQt5 and Pytube下载 PyQt5 和 Pytube 时 GUI 冻结
【发布时间】:2022-01-05 20:46:17
【问题描述】:

我使用 Python 制作了一个桌面应用程序,并使用了可以从 youtube 下载视频的 PyQt5 和 Pytube。当下载正在进行时,我想向用户展示动画。事实上,我做到了,但是当文件被下载时,PyQt 窗口似乎冻结了,一切都会暂停,直到下载完成。那么,有谁知道为什么会这样?我该如何解决?

这里是sn-p的代码:

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)

【问题讨论】:

  • pytube 似乎不提供异步函数,这意味着它的所有函数都是 阻塞 (因此,UI 被“阻塞”并且在它们返回之前没有响应)。唯一的方法是使用一个 QThread 来管理下载过程,所以我建议你对这个主题进行一些研究,因为实际上有数百篇关于它的帖子。
  • 谢谢...你能给我推荐一些最好的资料来研究它吗?

标签: python python-3.x pyqt5 pytube


【解决方案1】:

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

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)

【讨论】:

  • 我喜欢你的回答,而且我没有太多使用线程。那么我们可以直接在 github Repo 中编辑我的代码吗?如果你能帮助我,那就太好了。 github.com/hsmaharaj1/youvloader
  • 我不是要你参与我的项目。我已经做了一个。如果你不介意帮助我解决我项目中的这个特定问题。就这样。你能告诉我如何研究这个 QThreads 以便我可以实现它吗?
  • @HimanshuSekharMaharaj 正如我已经告诉过你的那样:我只帮助解决 SO 中的特定问题。正是因为 SO 限制了问题的扩展性是我积极参与的主要原因,我不想把时间花在帮助更广泛的问题上,因为它们对我不感兴趣。再见,阅读How to Ask 并查看tour。所以请不要要求社区在你的项目中实施答案,因为这不是网站的逻辑,我们不是导师或类似的。
  • 好的..谢谢
  • 嘿,我正在尝试使用 _thread 和线程模块创建线程,为什么它不起作用?
猜你喜欢
  • 1970-01-01
  • 2022-06-12
  • 1970-01-01
  • 1970-01-01
  • 2018-11-08
  • 2017-05-22
  • 1970-01-01
  • 2012-10-15
相关资源
最近更新 更多