You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5+yt-dlp多线程下载出现exit code -1073741819错误

PyQt5 + yt-dlp 线程下载时出现0xC0000005访问冲突错误

基于PyQt5开发YouTube视频下载工具,使用yt-dlp实现下载功能。通过线程执行download_thread函数时,会随机触发Process finished with exit code -1073741819 (0xC0000005)错误;部分场景下下载任务可完整执行,但结束后仍会触发该错误。若不使用线程直接运行函数,下载可正常完成,但程序界面会失去响应。


函数实现

def DOWNLOAD(self):
        if not self.LINEEDIT_LINK.text() == '' and _FILE_SIZE > 0:
            self.PUSHBUTTON_DOWNLOAD.setEnabled(False)
            self.PUSHBUTTON_DOWNLOAD.setStyleSheet(set_download_button_font_size(14))

            downloadThread = Thread(target=self.download_thread)
            downloadThread.daemon = True
            downloadThread.start()


    def my_hook(self, d):
        if d['status'] == 'downloading':
            self.__cleen_percent(dl=d)
        elif d['status'] == 'finished':
            self.PROGRESSBAR.setValue(0)
            self.PUSHBUTTON_DOWNLOAD.setText("Download")
            self.PUSHBUTTON_DOWNLOAD.setCheckable(True)

    def __cleen_percent(self, dl):
        network_speed = dl['_speed_str']
        percent = dl['_percent_str']
        all_mb = round(dl['total_bytes_estimate'] / 1024 / 1024, 2)
        downloaded_mb = round(dl['downloaded_bytes'] / 1024 / 1024, 2)
        eta = dl['eta']

        for i, char in enumerate(percent):
            if char == '%':
                value = percent[i - 5:i + 1].replace('%', '')
                value = (float(value.replace(' ', '')))
                self.PROGRESSBAR.setValue(int(value))
                self.PUSHBUTTON_DOWNLOAD.setText(
                    f'{network_speed} | {downloaded_mb} MB / {all_mb} MB | eta: {eta} secs')
                break

    # YOUTUBE THREADS
    def download_thread(self):
        try:
            ydl_opts_download = {
                'outtmpl': FOLDER_TO_SAVE + '/%(title)s.%(ext)s',
                'format': f"bv*[height<={_RESOLUTION}][ext={_VIDEO_FORMAT}]+ba[ext=m4a]/b[height<={_RESOLUTION}][ext={_VIDEO_FORMAT}] / bv*+ba/b",
                'progress_hooks': [self.my_hook],
            }

            ydl_opts_download_auto = {
                'outtmpl': FOLDER_TO_SAVE + '/%(title)s.%(ext)s',
                'format': f"bv*[height<={_RESOLUTION}]+ba[ext=m4a]/b[height<={_RESOLUTION}] / bv*+ba/b",
                'progress_hooks': [self.my_hook],
            }

            ydl_opts_download_maxres = {
                'outtmpl': FOLDER_TO_SAVE + '/%(title)s.%(ext)s',
                'format': f"bv*[ext={_VIDEO_FORMAT}]+ba[ext=m4a]/b[ext={_VIDEO_FORMAT}] / bv*+ba/b",
                'progress_hooks': [self.my_hook],
            }

            ydl_opts_download_maxres_auto = {
                'outtmpl': FOLDER_TO_SAVE + '/%(title)s.%(ext)s',
                'format': f"bv*+ba[ext=m4a]/b / bv*+ba/b",
                'progress_hooks': [self.my_hook],
            }

            if FILE_TYPE == 0:
                if MAX_RESOLUTION is True:
                    with YoutubeDL(ydl_opts_download_maxres_auto) as ydl:
                        ydl.download([_URL])
                else:
                    with YoutubeDL(ydl_opts_download_auto) as ydl:
                        ydl.download([_URL])
            else:
                if MAX_RESOLUTION is True:
                    with YoutubeDL(ydl_opts_download_maxres) as ydl:
                        ydl.download([_URL])
                else:
                    with YoutubeDL(ydl_opts_download) as ydl:
                        ydl.download([_URL])

            if OPEN_FOLDER is True:
                startfile(FOLDER_TO_SAVE)
            if NOTIFICATIONS is True:
                match LANGUAGE:
                    case 0:
                        toast.show_toast(
                            "Youtube Downloader",
                            "Video downloaded",
                            duration=5,
                            threaded=True,
                            icon_path=load_file("icons/youtube-downloader-icon.ico"),
                        )
                    case 1:
                        toast.show_toast(
                            "Youtube Downloader",
                            "Видео скачано",
                            duration=5,
                            threaded=True,
                            icon_path=load_file("icons/youtube-downloader-icon.ico"),
                        )

            if CLOSE_APP is True:
                QCoreApplication.quit()

            self.PUSHBUTTON_DOWNLOAD.setEnabled(True)
            self.PUSHBUTTON_DOWNLOAD.setStyleSheet(set_download_button_font_size(16))

        except Exception as e:
            print(e)
            self.PUSHBUTTON_DOWNLOAD.setEnabled(True)
            self.PUSHBUTTON_DOWNLOAD.setStyleSheet(set_download_button_font_size(16))

终端输出

[youtube] YBBbNbumaoM: Downloading webpage
[youtube] YBBbNbumaoM: Downloading android player API JSON
[youtube] YBBbNbumaoM: Downloading MPD manifest
[info] YBBbNbumaoM: Downloading 1 format(s): 248+140
[dashsegments] Total fragments: 111
[download] Destination: E:\code\PycharmProjects\pythonProject\YOUTUBE_DOWNLOADER_2\videos\Free Bird Isolated Guitar Track HQ.f248.webm
[download]  37.8% of ~  11.91MiB at  933.49KiB/s ETA 00:08 (frag 42/111)Windows fatal exception: access violation

Thread 0x00002544 (most recent call first):
  File "E:\code\PycharmProjects\pythonProject\YOUTUBE_DOWNLOADER_2\downloader.py", line 1184 in __cleen_percent
  File "E:\code\PycharmProjects\pythonProject\YOUTUBE_DOWNLOADER_2\downloader.py", line 1166 in my_hook
  File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\common.py", line 459 in _hook_progress
  File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\fragment.py", line 284 in frag_progress_hook
  File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\common.py", line 459 in _hook_progress
  File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\http.py", line 314 in download
  File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\http.py", line 377 in real_download
  File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\common.py", line 444 in download
  File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\fragment.py", line 124 in _download_fragment
  File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\fragment.py", line 469 in download_fragment
  File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\fragment.py", line 521 in download_and_append_fragments
  File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\fragment.py", line 382 in download_and_append_fragments_multiple
  File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\dash.py", line 60 in real_download
  File "E:\code\Python\Lib\site-packages\yt_dlp\downloader\common.py", line 444 in download
  File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 2970 in dl
  File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 3229 in process_info
  File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 2779 in process_video_result
  File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 1674 in process_ie_result
  File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 1615 in __extract_info
  File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 1518 in wrapper
  File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 1507 in extract_info
  File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 3344 in wrapper
  File "E:\code\Python\Lib\site-packages\yt_dlp\YoutubeDL.py", line 3369 in download
  File "E:\code\PycharmProjects\pythonProject\YOUTUBE_DOWNLOADER_2\downloader.py", line 1222 in download_thread
  File "E:\code\Python\Lib\threading.py", line 975 in run
  File "E:\code\Python\Lib\threading.py", line 1038 in _bootstrap_inner
  File "E:\code\Python\Lib\threading.py", line 995 in _bootstrap

Current thread 0x00002cb0 (most recent call first):
  File "E:\code\PycharmProjects\pythonProject\YOUTUBE_DOWNLOADER_2\downloader.py", line 1451 in <module>
[download]  39.6% of ~  11.92MiB at   49.16KiB/s ETA 00:11 (frag 44/111)
Process finished with exit code -1073741819 (0xC0000005)

问题根源与修复方案

问题根源

错误0xC0000005是Windows下的内存访问冲突,核心原因是PyQt的UI组件只能在主线程(GUI线程)中操作。你的my_hook和__cleen_percent函数直接在子线程中修改PROGRESSBAR、PUSHBUTTON_DOWNLOAD等UI元素,违反了线程安全规则,导致内存访问异常。

修复方案

方案1:改用QThread + 信号槽(官方推荐)

通过自定义QThread子类,用信号传递进度信息,主线程接收信号后更新UI,确保所有UI操作在主线程执行。

from PyQt5.QtCore import QThread, pyqtSignal

# 自定义下载线程类
class DownloadThread(QThread):
    progress_updated = pyqtSignal(dict)  # 传递进度信息的信号
    download_finished = pyqtSignal()     # 下载完成信号
    download_failed = pyqtSignal(str)    # 下载失败信号

    def __init__(self, url, folder, file_type, max_res, resolution, video_format):
        super().__init__()
        self.url = url
        self.folder = folder
        self.file_type = file_type
        self.max_res = max_res
        self.resolution = resolution
        self.video_format = video_format

    def run(self):
        try:
            # 进度钩子:发射信号而非直接操作UI
            def progress_hook(d):
                self.progress_updated.emit(d)

            # 构建yt-dlp配置
            ydl_opts_base = {
                'outtmpl': f"{self.folder}/%(title)s.%(ext)s",
                'progress_hooks': [progress_hook],
            }

            if self.file_type == 0:
                if self.max_res:
                    ydl_opts_base['format'] = "bv*+ba[ext=m4a]/b / bv*+ba/b"
                else:
                    ydl_opts_base['format'] = f"bv*[height<={self.resolution}]+ba[ext=m4a]/b[height<={self.resolution}] / bv*+ba/b"
            else:
                if self.max_res:
                    ydl_opts_base['format'] = f"bv*[ext={self.video_format}]+ba[ext=m4a]/b[ext={self.video_format}] / bv*+ba/b"
                else:
                    ydl_opts_base['format'] = f"bv*[height<={self.resolution}][ext={self.video_format}]+ba[ext=m4a]/b[height<={self.resolution}][ext={self.video_format}] / bv*+ba/b"

            with YoutubeDL(ydl_opts_base) as ydl:
                ydl.download([self.url])

            self.download_finished.emit()
        except Exception as e:
            self.download_failed.emit(str(e))

# 原DOWNLOAD方法修改
def DOWNLOAD(self):
    if not self.LINEEDIT_LINK.text() == '' and _FILE_SIZE > 0:
        self.PUSHBUTTON_DOWNLOAD.setEnabled(False)
        self.PUSHBUTTON_DOWNLOAD.setStyleSheet(set_download_button_font_size(14))

        # 创建线程实例并传入参数
        self.download_thread = DownloadThread(
            url=_URL,
            folder=FOLDER_TO_SAVE,
            file_type=FILE_TYPE,
            max_res=MAX_RESOLUTION,
            resolution=_RESOLUTION,
            video_format=_VIDEO_FORMAT
        )
        # 连接信号到主线程槽函数
        self.download_thread.progress_updated.connect(self.handle_progress_update)
        self.download_thread.download_finished.connect(self.handle_download_finished)
        self.download_thread.download_failed.connect(self.handle_download_failed)
        self.download_thread.start()

# 主线程处理进度更新的槽函数
def handle_progress_update(self, d):
    if d['status'] == 'downloading':
        self.__cleen_percent(dl=d)
    elif d['status'] == 'finished':
        self.PROGRESSBAR.setValue(0)
        self.PUSHBUTTON_DOWNLOAD.setText("Download")
        self.PUSHBUTTON_DOWNLOAD.setCheckable(True)

# 主线程处理下载完成的槽函数
def handle_download_finished(self):
    if OPEN_FOLDER is True:
        startfile(FOLDER_TO_SAVE)
    if NOTIFICATIONS is True:
        match LANGUAGE:
            case 0:
                toast.show_toast(
                    "Youtube Downloader",
                    "Video downloaded",
                    duration=5,
                    threaded=True,
                    icon_path=load_file("icons/youtube-downloader-icon.ico"),
                )
            case 1:
                toast.show_toast(
                    "Youtube Downloader",
                    "Видео скачано",
                    duration=5,
                    threaded=True,
                    icon_path=load_file("icons/youtube-downloader-icon.ico"),
                )

    if CLOSE_APP is True:
        QCoreApplication.quit()

    self.PUSHBUTTON_DOWNLOAD.setEnabled(True)
    self.PUSHBUTTON_DOWNLOAD.setStyleSheet(set_download_button_font_size(16))

# 主线程处理下载失败的槽函数
def handle_download_failed(self, error_msg):
    print(error_msg)
    self.PUSHBUTTON_DOWNLOAD.setEnabled(True)
    self.PUSHBUTTON_DOWNLOAD.setStyleSheet(set_download_button_font_size(16))

方案2:用QMetaObject.invokeMethod在主线程更新UI

若不想重构线程逻辑,可在原钩子函数中用QMetaObject.invokeMethod包装UI操作,强制其在主线程执行:

from PyQt5.QtCore import QMetaObject, Qt

def __cleen_percent(self, dl):
    network_speed = dl['_speed_str']
    percent = dl['_percent_str']
    all_mb = round(dl['total_bytes_estimate'] / 1024 / 1024, 2)
    downloaded_mb = round(dl['downloaded_bytes'] / 1024 / 1024, 2)
    eta = dl['eta']

    for i, char in enumerate(percent):
        if char == '%':
            value = percent[i - 5:i + 1].replace('%', '')
            value = int(float(value.replace(' ', '')))
            text = f'{network_speed} | {downloaded_mb} MB / {all_mb} MB | eta: {eta} secs'
            # 强制在主线程更新进度条
            QMetaObject.invokeMethod(self.PROGRESSBAR, "setValue", Qt
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 00:25:04