视频流下载
这段 Python 代码是一个 图形界面的 Windows 视频流下载器,使用了 PyQt5 作为 GUI 框架,yt-dlp 作为下载引擎,并结合 ffmpeg 实现音视频的自动合并。
🔍 代码总览
核心模块:
| 模块 | 作用 |
|---|---|
PyQt5 |
图形界面,用户输入视频地址/选择目录等操作 |
yt-dlp |
视频/音频流解析与下载(支持 m3u8/YouTube 等) |
ffmpeg |
音视频合并,生成完整 mp4 文件 |
📌 自动下载视频流的机制
关键点在于 yt-dlp 强大的自动处理能力,它:
-
自动识别视频地址(支持 YouTube、B站、m3u8等);
-
分析视频/音频轨道(DASH/HLS 分离流);
-
下载所有轨道并自动调用
ffmpeg进行合并。
🧠 为什么它能找到并下载视频流?
✅ yt-dlp 的作用
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([url])
-
yt-dlp是youtube-dl的高级分支,具备以下能力:-
自动解析网页并提取视频/音频链接(如
m3u8,dash,mp4); -
按格式选择最高质量的音视频;
-
下载后自动调用
ffmpeg进行合并(如video.webm + audio.webm -> mp4); -
支持断点续传、代理、证书忽略等功能。
-
⚙️ 视频流下载配置详解(_build_opts 方法)
def _build_opts(self, save_dir: str, hook):
return {
'format': 'bestvideo+bestaudio/best', # 自动选最高质量的视频 + 音频
'merge_output_format': 'mp4', # 合并后输出 mp4 格式
'postprocessors': [{ # 合并所需处理器
'key': 'FFmpegVideoConvertor',
'preferedformat': 'mp4',
}],
'continuedl': True, # 支持断点续传
'outtmpl': '.../%(title)s.%(ext)s', # 输出命名格式
'paths': {'temp': save_dir}, # 临时文件存放路径
'proxy': self.proxy, # 可选代理
'nocheckcertificate': True, # 忽略 HTTPS 证书验证
'progress_hooks': [hook], # 日志打印钩子
...
}
🧩 音视频合并的自动流程
yt-dlp 下载流程简化如下:
-
yt-dlp根据 URL 解析出视频格式列表; -
按
format='bestvideo+bestaudio'选择两个文件; -
分别下载视频流和音频流(通常为
.webm或.m4a); -
如果安装了
ffmpeg.exe:-
自动调用命令:
ffmpeg -i video.webm -i audio.webm -c copy output.mp4
-
-
合并成功后,生成最终 mp4 文件。
📂 文件结构与输出控制
'outtmpl': str(pathlib.Path(save_dir) / '%(title)s.%(ext)s'),
-
自动将视频命名为其原始标题;
-
视频和音频临时文件会放在
save_dir指定的目录中; -
下载完成后会清理临时文件,只保留合成好的
mp4。
🔐 HTTPS 支持与代理
-
nocheckcertificate=True→ 忽略证书错误(常见于某些自签名站点); -
proxy=http://...或socks5://...→ 支持FQ代理。
👁️ 日志钩子显示实时状态
def hook(d):
if d['status'] == 'downloading':
self.emitter.log.emit(f"{d.get('_percent_str','').strip()} {d.get('filename','')}")
elif d['status'] == 'finished':
self.emitter.log.emit(f"✅ 已完成:{d['filename']}")
-
实时显示下载进度、文件名;
-
下载完毕会提示“已完成”。
🖼️ 图形界面功能说明
| 控件 | 功能描述 |
|---|---|
url_edit |
输入视频地址(YouTube/m3u8等) |
dir_edit |
设置保存目录 |
proxy_edit |
可选代理地址 |
ignore_cert |
是否忽略证书 |
start_btn |
启动后台线程开始下载 |
log_view |
实时显示日志/状态 |
✅ 总结:为什么能自动识别并下载合并视频流
-
yt-dlp提供完整的自动化下载 + 合并流程; -
format=bestvideo+bestaudio精选音视频轨道; -
内部调用
ffmpeg实现合并; -
outtmpl、continuedl、proxy等提供用户级控制; -
下载进度通过钩子函数实时显示。
一句话结论
下载 **ffmpeg-master-latest-win64-gpl.zip**(约 100 MB),解压后把里面的 `bin\ffmpeg.exe` 放到与你 `video_downloader.py` **同一目录**即可。
--------------------------------------------------
逐步操作(Windows)
1. 打开页面
https://github.com/BtbN/FFmpeg-Builds/releases
2. 在 **Assets** 区域找到
```
ffmpeg-master-latest-win64-gpl.zip
```
点它下载。
3. 解压
- 解压后得到
```
ffmpeg-master-latest-win64-gpl\
├─ bin\
│ ├─ ffmpeg.exe ← 只要它
│ └─ ffprobe.exe
└─ ...
```
4. 放文件
把 `ffmpeg.exe` 复制到 `video_downloader.py` 所在文件夹,例如
```
D:\Tools\VideoDownloader\
├─ video_downloader.py
├─ ffmpeg.exe ← 放这里
```
5. 运行
双击 `video_downloader.py`(或 `python video_downloader.py`),
日志出现
```
[Merger] Merging formats into "xxx.mp4"
```
说明 ffmpeg 已生效,音视频自动合并。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Windows 视频流下载器(单文件最终版)
Python 3.x + PyQt5 + yt-dlp
功能:
1. 图形界面
2. 断点续传
3. 自动音画合并(需 ffmpeg.exe 同目录或 PATH)
4. 支持代理 / 忽略证书
"""
import sys, os, pathlib, threading
from PyQt5.QtWidgets import (QApplication, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QLineEdit, QLabel, QFileDialog,
QPlainTextEdit, QMessageBox, QCheckBox)
from PyQt5.QtCore import QObject, pyqtSignal
# ---------- 下载核心 ----------
import yt_dlp
class Downloader:
def __init__(self, proxy: str = None, ignore_cert: bool = False):
self.proxy = proxy
self.ignore_cert = ignore_cert
def _build_opts(self, save_dir: str, hook):
return {
'outtmpl': str(pathlib.Path(save_dir) / '%(title)s.%(ext)s'),
'progress_hooks': [hook],
'noplaylist': True,
'ignoreerrors': True,
'no_warnings': False,
'verbose': False,
'continuedl': True,
'paths': {'temp': save_dir},
# 关键:自动选最佳视频+音频并合并成 mp4
'format': 'bestvideo+bestaudio/best',
'merge_output_format': 'mp4',
'postprocessors': [{
'key': 'FFmpegVideoConvertor',
'preferedformat': 'mp4',
}],
} | ({'nocheckcertificate': True} if self.ignore_cert else {}) \
| ({'proxy': self.proxy} if self.proxy else {})
def download(self, url: str, save_dir: str, hook):
ydl_opts = self._build_opts(save_dir, hook)
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([url])
# ---------- GUI ----------
class LogEmitter(QObject):
log = pyqtSignal(str)
class MainWin(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("视频流下载器(单文件最终版)")
self.resize(550, 420)
# 控件
self.url_edit = QLineEdit()
self.url_edit.setPlaceholderText("粘贴视频/直播/m3u8 地址")
self.dir_edit = QLineEdit()
self.dir_edit.setPlaceholderText("保存目录")
self.browse_btn = QPushButton("浏览")
self.proxy_edit = QLineEdit()
self.proxy_edit.setPlaceholderText("http://127.0.0.1:1080 或 socks5://127.0.0.1:1080")
self.ignore_cert_cb = QCheckBox("忽略证书错误")
self.start_btn = QPushButton("开始下载")
self.log_view = QPlainTextEdit(readOnly=True)
# 布局
layout = QVBoxLayout(self)
layout.addWidget(QLabel("视频地址:"))
layout.addWidget(self.url_edit)
h_dir = QHBoxLayout()
h_dir.addWidget(self.dir_edit)
h_dir.addWidget(self.browse_btn)
layout.addWidget(QLabel("保存目录:"))
layout.addLayout(h_dir)
layout.addWidget(QLabel("代理(可选):"))
layout.addWidget(self.proxy_edit)
layout.addWidget(self.ignore_cert_cb)
layout.addWidget(self.start_btn)
layout.addWidget(QLabel("日志:"))
layout.addWidget(self.log_view)
# 信号
self.emitter = LogEmitter()
self.emitter.log.connect(self.log_view.appendPlainText)
# 事件
self.browse_btn.clicked.connect(self.choose_dir)
self.start_btn.clicked.connect(self.start_download)
def choose_dir(self):
path = QFileDialog.getExistingDirectory(self, "选择保存目录")
if path:
self.dir_edit.setText(path)
def start_download(self):
url = self.url_edit.text().strip()
save_dir = self.dir_edit.text().strip()
if not url or not save_dir:
QMessageBox.warning(self, "提示", "地址和目录不能为空")
return
os.makedirs(save_dir, exist_ok=True)
proxy = self.proxy_edit.text().strip() or None
ignore_cert = self.ignore_cert_cb.isChecked()
self.downloader = Downloader(proxy=proxy, ignore_cert=ignore_cert)
self.start_btn.setEnabled(False)
self.log_view.clear()
self.thread = threading.Thread(target=self.run_download,
args=(url, save_dir),
daemon=True)
self.thread.start()
def run_download(self, url, save_dir):
def hook(d):
if d['status'] == 'downloading':
self.emitter.log.emit(f"{d.get('_percent_str','').strip()} {d.get('filename','')}")
elif d['status'] == 'finished':
self.emitter.log.emit(f"✅ 已完成:{d['filename']}")
try:
self.downloader.download(url, save_dir, hook)
self.emitter.log.emit("全部任务下载完成!")
except Exception as e:
self.emitter.log.emit(f"❌ 错误:{e}")
finally:
self.start_btn.setEnabled(True)
# ---------- 入口 ----------
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MainWin()
win.show()
sys.exit(app.exec_())
浙公网安备 33010602011771号