yt-dlp 下载工具:从踩坑到最终方案
Posted on May 2, 2026 • 8 min read • 3,780 wordsyt-dlp 自动下载及使用
完整记录一个"看似简单"的视频批量下载工具的构建过程,以及在 GitHub Copilot 辅助下的工程实践与 token 消耗优化思路。
早期为了学习英语付费购买了 VidJuice UniTube。后续电脑重装多次后,想重新激活,发现官网都打不开了。啊,这后续只能找客服咨询了。 网查发现 yt-dlp 还满热门的,想用其下载视频及字幕方便继续学习英语精听,作为替代方案。具体需求如下:
urls.txt 批量下载,高清画质(1080p+).bat 和 bash 两种运行方式最初打算用 Go 写一个下载脚本,调用 os/exec 执行 yt-dlp 命令。
失败原因:
n challenge solving failed — Chocolatey 安装的 yt-dlp 属于第三方包,未捆绑 EJS 求解脚本,无法解 YouTube 的 JS 挑战android client does not support cookies — 同时指定了 android 客户端和 cookies,参数冲突决策:放弃 Go,改用原生 yt-dlp + shell 脚本。删除 main.go、go.mod、ytdl.exe。
尝试从浏览器导出 cookies.txt 交给 yt-dlp。
失败原因:YouTube 定期轮换 cookie,导出的文件很快失效,每次都要重新导出,维护成本高。
决策:改用 --cookies-from-browser,每次运行时直接从浏览器读取最新 cookies。
--cookies-from-browser chrome 时报 SQLite 数据库锁定错误。
根本原因:Chrome 使用 SQLite 排他锁,运行时其他进程无法读取;而 Firefox 使用 WAL 模式,打开时也可以被读取。
解决方案:脚本检测 Chrome 进程,若运行中则自动降级到 Firefox,Firefox 未登录 YouTube 则提示用户登录。
# Chrome 运行检测
if tasklist.exe 2>/dev/null | grep -qi "chrome.exe"; then
CHROME_RUNNING=true
fitoolkits/yt-dlp/
├── urls.txt ← 每次只维护这个,每行一个 URL
├── yt-dlp.conf ← 下载配置
├── 2download.sh ← bash 运行(日常使用)
├── download.bat ← Windows 双击运行
├── merge_subs.py ← 字幕后处理(Python)
└── README.mdYouTube 用 JS 挑战(n-challenge)对抗爬虫,yt-dlp 需要运行 JS 来求解。Chocolatey 版 yt-dlp 不含这个脚本。
解决:配置从 GitHub 自动下载 EJS 求解器:
# yt-dlp.conf
--js-runtimes node
--remote-components ejs:github
--extractor-args youtube:player_client=tv,web首次运行会从 GitHub 下载一次,之后缓存本地。
D:yt-dlpDown 缺少反斜杠)
在 yt-dlp.conf 中写:
-P D:\yt-dlpDown ← 错误!yt-dlp 的 conf 解析器会把反斜杠当转义字符吃掉,导致路径变成相对路径 D:yt-dlpDown。
解决:所有路径改用正斜杠:
-P D:/yt-dlpDown ← 正确
-o %(title)s/%(title)s.%(ext)s默认输出模板 -o %(title)s.%(ext)s 把所有视频和字幕文件堆在 D:\yt-dlpDown\ 根目录,乱成一锅粥。
解决:改为按视频标题建子目录:
-o %(title)s/%(title)s.%(ext)s每个视频的所有文件(视频 + 各语言字幕)都在自己的目录下:
D:\yt-dlpDown\
└── 视频标题\
├── 视频标题.mp4
├── 视频标题.en.ass
├── 视频标题.zh-Hans.ass
└── 视频标题.en-zh.ass下载 Bilibili 视频后,播放器明明显示有字幕,但脚本报告:
[skip] no soft subtitle streams found (burned-in subtitles cannot be extracted)根本原因:字幕类型决定能否提取:
| 类型 | 特征 | 能否提取 |
|---|---|---|
| 软字幕(内嵌轨道) | MKV/MP4 容器里独立的字幕轨,播放器可开关 | 可以,ffmpeg 提取 |
| 烧录字幕(硬编码) | 文字直接画在视频画面像素上,播放器无法关闭 | 不行,只能 OCR |
Bilibili 二创/搬运视频几乎全是烧录字幕,播放器显示的只是画面本身,不是独立的字幕轨道。
关键教训:这个问题在写代码之前无法预知,必须下载真实视频才能触发,是典型的「用了才发现」问题。
添加内嵌字幕提取功能后,每次运行都会对已处理过的目录(有 .ass 无 .srt)重新走 ffprobe 检测:
[subtitle] Vibe Coding in VS Code...
[info] no external subtitles, checking embedded streams...
[skip] no soft subtitle streams found原因:新功能改变了逻辑,但旧的「已处理」状态判断没有同步更新。
修复(一行):
if not srts:
if list(d.glob("*.ass")):
return # 已处理过,跳过这类 bug 只有实际跑多个视频目录时才会暴露,单次测试发现不了。
| 需求 | SRT | ASS |
|---|---|---|
| 绿色字幕 | <font color> 标签,多数播放器忽略 |
原生 PrimaryColour 属性,所有主流播放器渲染 |
| 自适应字体大小 | 完全由播放器决定 | PlayResX/Y 定义参考分辨率,播放器等比缩放 |
| 双语位置控制 | 无法控制 | Alignment 精确控制每行位置 |
PlayResY: 720 ← 参考分辨率(基准)
Fontsize: 30 ← 基于 720p 的字号播放器渲染时自动换算:
$$\text{实际字号} = 30 \times \frac{\text{实际窗口高度}}{720}$$
无论什么分辨率,字幕始终占窗口高度约 5%(单行),双语合计约 10%。
同一个 Dialogue 事件内用内联标签切换字体,英文第一行 + 中文第二行,统一在底部:
Dialogue: 0,0:00:01.00,0:00:03.00,BiBottom,,0,0,0,,{\fnArial}Hello World\N{\fnMicrosoft YaHei}你好世界通过时间区间合并(boundary split)保证每个时间段的中英文严格对齐。
脚本自动识别视频原始语言,按规则生成字幕:
_ZH_LANGS = {"zh-Hans", "zh-Hant", "zh", "zh-CN", "zh-TW"}
# 逻辑:
# - 原始为中文 → 只生成 zh-Hans.ass
# - 原始为其他语言 → 生成 原文.ass + zh-Hans.ass + 原文-zh.ass| 视频语言 | 生成文件 |
|---|---|
| 英语 | base.en.ass + base.zh-Hans.ass + base.en-zh.ass |
| 日语 | base.ja.ass + base.zh-Hans.ass + base.ja-zh.ass |
| 中文 | base.zh-Hans.ass(仅一个) |
# 输出路径(必须正斜杠)
-P D:/yt-dlpDown
-o %(title)s/%(title)s.%(ext)s
# 最高画质
-f bestvideo[ext=mp4]+bestaudio[ext=m4a]/bestvideo+bestaudio/best
--merge-output-format mp4
# n-challenge 修复
--js-runtimes node
--remote-components ejs:github
--extractor-args youtube:player_client=tv,web
# 字幕(覆盖常见语言)
--write-sub
--write-auto-sub
--sub-langs zh-Hans,zh-Hant,zh,en,ja,ko,fr,de,es,pt,it,ru,ar,hi,th,vi
--convert-subs srt
--no-playlist
--retries 5
--fragment-retries 5
--file-access-retries 3cookies.txt 存在?
└─ 是 → 用 cookies.txt(手动维护模式)
└─ 否 → Chrome 目录存在?
└─ 是 → Chrome 正在运行?
└─ 否 → 用 Chrome cookies(最优)
└─ 是 → Firefox 目录存在?
└─ 是 → 用 Firefox cookies(需已登录 YouTube)
└─ 否 → 报错退出
└─ 否 → 用 Edge cookies整个项目消耗了约 11% 月度订阅额度,主要浪费来源:
| 原因 | 估算占比 |
|---|---|
| 字幕需求反复迭代 6 次(每次读文件+改+测试) | ~30% |
| 每次调试都重新从 YouTube 下载字幕 | ~20% |
| 没有 instructions 文件,每次重复交代背景 | ~20% |
| 路径、cookies 等问题多轮排查 | ~20% |
| 对话超长触发压缩,摘要本身占 token | ~10% |
关键改进点:
.github/copilot-instructions.md
一次性固化项目背景,后续每次对话 AI 自动加载,无需重复交代:
## 项目:yt-dlp 下载工具
- 路径约定:conf 文件必须用正斜杠
- 输出:D:/yt-dlpDown/<title>/<title>.mp4
- 字幕:merge_subs.py,PlayResY=720,fontsize=30,ASS格式
- Cookies:优先 Firefox(Chrome 运行时锁库)字幕格式迭代了 6 次,每次都是 AI 直接改代码,用户看效果再反馈。
正确流程:
用户:"字幕改成底部双行,英文上中文下"
AI:"计划修改:
1. 删除 EnTop 样式,新增 BiBottom 样式(Alignment=2)
2. _merge_bilingual() 改为生成单条 Dialogue
3. 内联 \fn 标签切换字体
确认后执行?"
用户:"确认"
AI:一次完成所有修改每次调试字幕脚本,都重新从 YouTube 下载 SRT,浪费时间和 token。
正确做法:项目目录放一份 20 条的测试 SRT,调试时本地跑:
python merge_subs.py ./test # 0.1秒,不需要网络低效:粘贴 100 行完整 yt-dlp 输出
高效:只粘最后 ERROR 行:
ERROR: Sign in to confirm you're not a bot.# 1. 编辑 urls.txt,每行一个 YouTube URL
vim urls.txt
# 2. 运行下载(bash 方式)
bash 2download.sh
# 3. 或者直接双击 download.bat(Windows)下载完成后自动处理字幕,输出结构:
D:\yt-dlpDown\
└── 视频标题\
├── 视频标题.mp4
├── 视频标题.en.ass ← 纯英文,绿色,自适应
├── 视频标题.zh-Hans.ass ← 纯中文,绿色,自适应
└── 视频标题.en-zh.ass ← 双语(英上中下),底部,绿色# Windows,用 Chocolatey
choco install yt-dlp ffmpeg nodejs python
# 首次运行 yt-dlp 会自动从 GitHub 下载 EJS 求解脚本(需要网络)Firefox 需提前登录 YouTube(一次性操作)。
这个项目本质上只是「配置 yt-dlp + 写一个字幕后处理脚本」,核心代码不超过 200 行。但整个过程消耗了大量时间和 token,教训分两类:
可以提前避免的消耗:
本来就无法节省的消耗(探索成本):
这类「用了才发现」的问题是工程探索的正常成本,不必强求节省。
合理预期:优化前期准备可以节省 30-50% 的 token;运行时发现的问题,该花的还是要花。
AI 辅助开发的本质不是「让 AI 替你思考」,而是「把你的思考高效地转化为代码」。需求越清晰,AI 越高效,token 消耗越少。
项目代码:toolkits/yt-dlp/ 目录下所有文件