如何在 PyQt5 中集成 youtube-dl

How to integrate youtube-dl in PyQt5

我正在尝试在图形用户界面中显示实时下载进度。我在 cmd 中获取实时下载进度,但 gui 仅在下载完成时更新。

def myhook(self,d):
    self.percentage=QLabel(self)
    self.d=d
    if self.d['status']=='finished':
        print("finished")
    if self.d['status']=='downloading':
        print(self.d['filename'],self.d['total_bytes'],self.d['downloaded_bytes'],self.d['elapsed'],self.d['eta']
        ,str(str(self.d['speed']/1000)[0:5]+'KiB/s')
        ,str((self.d['downloaded_bytes']*100)/self.d['total_bytes'])[0:5]+'%')
        if self.d['total_bytes']>1048576:
            total_size=self.d['total_bytes']/1048576
            total_size=str(total_size)[0:5]+' MB'
        if self.d['total_bytes']>1073741824:
            total_size=self.d['total_bytes']/1073741824
            total_size=str(total_size)[0:5]+' GB'
        self.percentage.setText('{} {}'.format(total_size
        ,str((self.d['downloaded_bytes']*100)/self.d['total_bytes'])[0:5]+' %'))
        self.percentage.setStyleSheet('color:white;border-bottom:2px solid orange;')
        self.percentage.setFont(QFont('Arial',15))
        self.percentage.resize(500,30)
        self.percentage.move(320,650)
        self.percentage.show()

    self.url=self.urlfield.text()
    print("144p")
    options={'format':'bestvideo[height=144]+bestaudio/best','noplaylist':True,'postprocessors':[{'key':'FFmpegMetadata'}]
    ,'noprogress':True,'progress_hooks':[self.myhook]}
    with youtube_dl.YoutubeDL(options) as ydll:
        ydll.download(['{}'.format(self.url)])

因为很多次你想知道如何从 youtube-dl 获取信息并在用 pyqt5 编写的 GUI 中显示它的正确方法,我将获得许可超越当前问题并显示一个更通用的示例。

youtube_dl 下载方法非常耗时,因此不应在主线程中执行,因为它会产生不需要的效果,例如 window 冻结。另一方面,youtube_dl 提供了两种信息获取方式:钩子和记录器。并且这些媒体与下载方法在同一个线程中执行,因此不应直接访问它们,而应通过信号访问。

qyoutubedl.py

import threading
from PyQt5 import QtCore

import youtube_dl


class QLogger(QtCore.QObject):
    messageChanged = QtCore.pyqtSignal(str)

    def debug(self, msg):
        self.messageChanged.emit(msg)

    def warning(self, msg):
        self.messageChanged.emit(msg)

    def error(self, msg):
        self.messageChanged.emit(msg)


class QHook(QtCore.QObject):
    infoChanged = QtCore.pyqtSignal(dict)

    def __call__(self, d):
        self.infoChanged.emit(d.copy())


class QYoutubeDL(QtCore.QObject):
    def download(self, urls, options):
        threading.Thread(
            target=self._execute, args=(urls, options), daemon=True
        ).start()

    def _execute(self, urls, options):
        with youtube_dl.YoutubeDL(options) as ydl:
            ydl.download(urls)
        for hook in options.get("progress_hooks", []):
            if isinstance(hook, QHook):
                hook.deleteLater()
        logger = options.get("logger")
        if isinstance(logger, QLogger):
            logger.deleteLater()

main.py

from PyQt5 import QtWidgets

from qyoutubedl import QLogger, QHook, QYoutubeDL

from hurry.filesize import size


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

        self.url_le = QtWidgets.QLineEdit()
        self.download_btn = QtWidgets.QPushButton(self.tr("Download"))
        self.progress_lbl = QtWidgets.QLabel()
        self.download_pgb = QtWidgets.QProgressBar()
        self.log_edit = QtWidgets.QPlainTextEdit(readOnly=True)

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)
        lay = QtWidgets.QGridLayout(central_widget)
        lay.addWidget(QtWidgets.QLabel(self.tr("url:")))
        lay.addWidget(self.url_le, 0, 1)
        lay.addWidget(self.download_btn, 0, 2)
        lay.addWidget(self.progress_lbl, 1, 1, 1, 2)
        lay.addWidget(self.download_pgb, 2, 1, 1, 2)
        lay.addWidget(self.log_edit, 3, 1, 1, 2)
        self.progress_lbl.hide()

        self.downloader = QYoutubeDL()

        self.download_btn.clicked.connect(self.download)

        self.url_le.setText("https://www.youtube.com/watch?v=BaW_jenozKc")

        self.resize(640, 480)

    def download(self):
        qhook = QHook()
        qlogger = QLogger()
        url = self.url_le.text()
        options = {
            "format": "bestvideo[height=144]+bestaudio/best",
            "noplaylist": True,
            "postprocessors": [{"key": "FFmpegMetadata"}],
            "noprogress": True,
            "logger": qlogger,
            "progress_hooks": [qhook],
        }
        self.downloader.download([url], options)
        qhook.infoChanged.connect(self.handle_info_changed)
        qlogger.messageChanged.connect(self.log_edit.appendPlainText)

        self.download_pgb.setRange(0, 1)

    def handle_info_changed(self, d):
        if d["status"] == "downloading":
            self.progress_lbl.show()
            total = d["total_bytes"]
            downloaded = d["downloaded_bytes"]
            self.progress_lbl.setText("{} of {}".format(size(downloaded), size(total)))
            self.download_pgb.setMaximum(total)
            self.download_pgb.setValue(downloaded)


def main():
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()