video-pipeline 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +168 -0
- package/CHANGELOG.md +98 -0
- package/LICENSE +21 -0
- package/README.md +589 -0
- package/package.json +67 -0
- package/process_videos.js +2010 -0
package/README.md
ADDED
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
# 视频下载 / 转码 / 文本识别 / AI 分析 流程
|
|
2
|
+
|
|
3
|
+
基于 `process_videos.py`,一键完成:yt-dlp 下载 → ffmpeg 转码 → whisper 识别 → AI 关键词归纳 → 写回 Excel。
|
|
4
|
+
|
|
5
|
+
## 环境依赖
|
|
6
|
+
|
|
7
|
+
### 必装工具
|
|
8
|
+
|
|
9
|
+
| 工具 | 版本要求 | 安装方式 | 用途 |
|
|
10
|
+
|---|---|---|---|
|
|
11
|
+
| Python | 3.9+ | [python.org](https://www.python.org/) | 脚本运行 |
|
|
12
|
+
| yt-dlp | 最新 | `pip install yt-dlp` 或 [GitHub Release](https://github.com/yt-dlp/yt-dlp/releases) | 视频下载 |
|
|
13
|
+
| ffmpeg + ffprobe | 4.0+ | [ffmpeg.org](https://ffmpeg.org/download.html) 或 `winget install ffmpeg` | 音频转码 + 时长检测 |
|
|
14
|
+
|
|
15
|
+
> **验证安装**:在终端执行 `yt-dlp --version`、`ffmpeg -version`、`ffprobe -version`,确保均在 PATH 中。
|
|
16
|
+
|
|
17
|
+
### 必装 Node.js(YouTube n-sig 挑战)
|
|
18
|
+
|
|
19
|
+
YouTube 要求 JS 运行时解开 n-sig 挑战,否则无法提取视频格式。
|
|
20
|
+
|
|
21
|
+
| 方式 | 安装命令 |
|
|
22
|
+
|---|---|
|
|
23
|
+
| Node.js(推荐) | [nodejs.org](https://nodejs.org/) 下载 LTS 版,安装后 `node --version` 验证 |
|
|
24
|
+
| Deno | `winget install DenoLand.Deno` 或 [deno.com](https://deno.com/) |
|
|
25
|
+
|
|
26
|
+
> 脚本默认使用 `--js-runtimes node`,如果你装的是 deno,修改 `.env` 中 `YOUTUBE_JS_RUNTIMES=deno`。
|
|
27
|
+
|
|
28
|
+
### Python 依赖
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install pandas openpyxl requests python-dotenv
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### 环境变量配置(.env)
|
|
35
|
+
|
|
36
|
+
**从 v2 开始,所有路径、字段映射、平台参数均通过 `.env` 文件配置。** 这意味着同一套脚本可以直接用于其他 Excel 文件,只需修改 `.env` 中的值即可。
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 首次使用:复制模板
|
|
40
|
+
cp .env.example .env
|
|
41
|
+
|
|
42
|
+
# 编辑 .env 适配你的 Excel 结构
|
|
43
|
+
# 详见 .env.example 中的注释
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**核心配置项说明:**
|
|
47
|
+
|
|
48
|
+
| 分类 | 变量 | 说明 |
|
|
49
|
+
|------|------|------|
|
|
50
|
+
| 输入 | `EXCEL_FILE` | Excel 文件路径 |
|
|
51
|
+
| 列映射 | `COL_ID` / `COL_TITLE` / `COL_CONTENT` / `COL_KEYWORDS` | 唯一标识列 / 标题列 / 识别文本输出列 / AI 关键词输出列 |
|
|
52
|
+
| 列映射 | `COL_TENCENTVID` / `COL_BILIBILIBVID` / `COL_YOUTUBEID` / `COL_YOUKUID` | 各平台视频 ID 所在列 |
|
|
53
|
+
| Sheet | `VIDEO_SHEETS` | 逗号分隔需要处理的 sheet(留空则全部) |
|
|
54
|
+
| 平台 | `PLATFORM_PRIORITY` | 平台重试优先级 |
|
|
55
|
+
| 平台 | `{平台}_URL_TPL` | URL 模板(如 `YOUTUBE_URL_TPL=https://youtu.be/{youtubeId}`) |
|
|
56
|
+
| 平台 | `{平台}_COOKIES_FROM_BROWSER` | 从浏览器直读 cookie(推荐 Firefox,替代手动导出文件) |
|
|
57
|
+
| 平台 | `{平台}_COOKIE_FILE` | cookie 文件路径(备用方案,需定期更新) |
|
|
58
|
+
| 平台 | `{平台}_PROXY` | 代理地址(如 `http://127.0.0.1:7897`,Clash Verge) |
|
|
59
|
+
| 平台 | `{平台}_FORMAT` / `{平台}_USER_AGENT` | 下载格式 / UA |
|
|
60
|
+
| 平台 | `{平台}_JS_RUNTIMES` / `{平台}_REMOTE_COMPONENTS` | JS 运行时 / 远程组件(YouTube n-sig 求解) |
|
|
61
|
+
| 服务 | `WHISPER_BACKEND` | `local` 或 `service` |
|
|
62
|
+
| 服务 | `WHISPER_MODEL` | 模型名 (tiny/base/small/medium/large),仅 backend=local |
|
|
63
|
+
| 服务 | `WHISPER_DEVICE` | 本地模式设备 (cpu/cuda) |
|
|
64
|
+
| 服务 | `WHISPER_LANGUAGE` | 语言代码 (仅 backend=local),空=多语言自动检测(默认) |
|
|
65
|
+
| 服务 | `WHISPER_SERVICE_MODEL` | 模型文件路径 (仅 backend=service),如 models/ggml-base.bin |
|
|
66
|
+
| 工具 | `YTDLP` / `FFMPEG` / `FFPROBE` | 外部工具路径 |
|
|
67
|
+
| AI 分析 | `AI_ENABLED` | `true` 启用 / `false` 跳过(默认 true) |
|
|
68
|
+
| AI 分析 | `AI_API_KEY` / `AI_BASE_URL` / `AI_MODEL` | OpenAI 兼容 API 配置 |
|
|
69
|
+
| AI 分析 | `AI_PROMPT_TPL` | 提示词模板,必须包含 `{content}` 占位符 |
|
|
70
|
+
| AI 分析 | `AI_TIMEOUT` | 单次分析请求超时(秒,默认 300) |
|
|
71
|
+
|
|
72
|
+
### .env 配置项变更权限
|
|
73
|
+
|
|
74
|
+
`.env.example` 中每个配置项都带有变更权限标记,含义如下:
|
|
75
|
+
|
|
76
|
+
| 标记 | 含义 | 涵盖的配置项 | 示例 |
|
|
77
|
+
|------|------|-------------|------|
|
|
78
|
+
| **【自由】** | 值可随意改为任意合法内容 | 路径、开关、数字、字符串、URL、UA、格式参数等 | `EXCEL_FILE`, `YOUTUBE_PROXY`, `WHISPER_MODEL` |
|
|
79
|
+
| **【调序】** | 只能从固定集合中增减/排序,不能用集合外的值 | `PLATFORM_PRIORITY` | 只能包含 `bilibiliBvid` / `youtubeId` / `tencentVid` / `youkuId` |
|
|
80
|
+
| **【关联】** | 值需与脚本内约定的 Key 名一致 | URL 模板中的 `{占位符}` | `{youtubeId}` 必须跟 `COL_YOUTUBEID` 的后缀一致 |
|
|
81
|
+
| **【固定】** | 除非 Excel 列名或脚本内部逻辑改变,否则不应修改 | 列名映射 | `COL_ID=extra.id`、`COL_TITLE=title` 等 |
|
|
82
|
+
|
|
83
|
+
> **最容易混淆的是【调序】**:`PLATFORM_PRIORITY` 可以调整顺序、增减条目,但只能用脚本已定义的 4 个 key,新增 `tiktokId`、`douyinId` 等无效 key 会导致脚本无法识别。
|
|
84
|
+
|
|
85
|
+
### Whisper 语音识别
|
|
86
|
+
|
|
87
|
+
支持两种后端,通过 `WHISPER_BACKEND` 切换:
|
|
88
|
+
|
|
89
|
+
**远程服务模式**(默认,whisper.cpp server):
|
|
90
|
+
需要本地或远程运行 whisper.cpp server,监听 `http://localhost:9588`:
|
|
91
|
+
- `POST /inference` ← 上传 wav 文件,返回识别文本(参数: file/temperature/temperature_inc/response_format)
|
|
92
|
+
- `POST /load` ← 切换模型(参数: model=模型文件路径),如 `models/ggml-base.bin`
|
|
93
|
+
- 语言需在 whisper.cpp server 启动参数或管理后台设置,不支持通过 API 指定
|
|
94
|
+
- 通过 `WHISPER_SERVICE_MODEL` 指定模型文件路径(留空则使用服务当前已加载的模型)
|
|
95
|
+
- 脚本首次识别时会自动 `/load`,同一模型只加载一次(缓存)
|
|
96
|
+
|
|
97
|
+
**本地 CLI 模式**:
|
|
98
|
+
需要在本地安装 `openai-whisper`:`pip install openai-whisper`
|
|
99
|
+
```bash
|
|
100
|
+
# .env 配置
|
|
101
|
+
WHISPER_BACKEND=local
|
|
102
|
+
WHISPER_MODEL=base # tiny / base / small / medium / large
|
|
103
|
+
WHISPER_DEVICE=cpu # cpu 或 cuda
|
|
104
|
+
WHISPER_LANGUAGE=zh # 空=多语言自动检测(默认),需要指定时填 zh/en/ja 等
|
|
105
|
+
```
|
|
106
|
+
脚本会直接调用 `whisper` CLI,无需额外服务进程。
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## 目录结构
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
├── process_videos.py # 主流程脚本
|
|
114
|
+
├── .env.example # 环境变量模板(可提交 Git)
|
|
115
|
+
├── .env # 实际环境变量(已 gitignore,按需修改)
|
|
116
|
+
├── export_2026-06-10_split.xlsx # 数据源(YouTube视频 / 普诺赛中文站 两个 sheet)
|
|
117
|
+
├── cookies/
|
|
118
|
+
│ ├── bilibili.txt # B站 cookie(Netscape 格式)
|
|
119
|
+
│ └── youtube.txt # YouTube cookie 备用(Firefox 直读方案不需要)
|
|
120
|
+
├── downloads/ # yt-dlp 下载输出(mp4)
|
|
121
|
+
│ ├── YouTube视频/
|
|
122
|
+
│ └── 普诺赛中文站/
|
|
123
|
+
├── transcoded/ # ffmpeg 转码输出(wav 16kHz mono)
|
|
124
|
+
│ ├── YouTube视频/
|
|
125
|
+
│ └── 普诺赛中文站/
|
|
126
|
+
└── reports/ # 执行报告(JSON)
|
|
127
|
+
└── report_YYYYMMDD_HHMMSS.json
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Cookie 设置(首次使用必须)
|
|
133
|
+
|
|
134
|
+
### YouTube(推荐:Firefox 浏览器直读)
|
|
135
|
+
|
|
136
|
+
yt-dlp 可直接从 Firefox 浏览器读取 cookie,无需手动导出:
|
|
137
|
+
|
|
138
|
+
1. 用 Firefox 浏览器登录 [youtube.com](https://www.youtube.com)
|
|
139
|
+
2. 在 `.env` 中设置 `YOUTUBE_COOKIES_FROM_BROWSER=firefox`
|
|
140
|
+
3. 脚本自动通过 `--cookies-from-browser firefox` 读取
|
|
141
|
+
|
|
142
|
+
> Firefox 在 Windows 上的 cookie 加密格式 yt-dlp 可稳定解密,只要浏览器保持登录态即可。
|
|
143
|
+
> **Chrome/Edge 在 Windows 上 DPAPI 解密已知失败**,不推荐使用。
|
|
144
|
+
|
|
145
|
+
### YouTube(备用:手动导出文件)
|
|
146
|
+
|
|
147
|
+
如果无法使用 Firefox,可手动导出 cookie 文件:
|
|
148
|
+
|
|
149
|
+
1. Chrome 安装扩展 [Get cookies.txt LOCALLY](https://chromewebstore.google.com/detail/get-cookiestxt-locally/cclelndahbckbenkjhflpdbgdldlbecc)
|
|
150
|
+
2. 访问 [youtube.com](https://www.youtube.com) 并登录
|
|
151
|
+
3. 点击扩展图标 → Export → 保存为 `cookies/youtube.txt`
|
|
152
|
+
4. 在 `.env` 中注释掉 `YOUTUBE_COOKIES_FROM_BROWSER`,启用 `YOUTUBE_COOKIE_FILE=cookies/youtube.txt`
|
|
153
|
+
|
|
154
|
+
> ⚠️ YouTube cookie 有效期约 48 小时,过期后需重新导出。下载时如果报 `cookies does no longer seem to be valid`,说明 cookie 已失效。**优先用 Firefox 方案,免维护。**
|
|
155
|
+
|
|
156
|
+
### B站(bilibili)
|
|
157
|
+
|
|
158
|
+
1. 同样使用上述 Chrome 扩展
|
|
159
|
+
2. 访问 [bilibili.com](https://www.bilibili.com) 并登录
|
|
160
|
+
3. 点击扩展图标 → Export → 保存为 `cookies/bilibili.txt`
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 使用方法
|
|
165
|
+
|
|
166
|
+
### 单条测试
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
# 下载 + 转码 + 识别 + AI分析,指定 sheet + extra.id
|
|
170
|
+
python process_videos.py --sheet "YouTube视频" --id 2143
|
|
171
|
+
|
|
172
|
+
# 只跑下载
|
|
173
|
+
python process_videos.py --sheet "普诺赛中文站" --id 16 --step download
|
|
174
|
+
|
|
175
|
+
# 只跑转码(需要已有下载文件)
|
|
176
|
+
python process_videos.py --sheet "普诺赛中文站" --id 16 --step transcode
|
|
177
|
+
|
|
178
|
+
# 只跑识别(需要已有转码文件)
|
|
179
|
+
python process_videos.py --sheet "普诺赛中文站" --id 16 --step transcribe
|
|
180
|
+
|
|
181
|
+
# 只跑 AI 分析(需要已有识别文本)
|
|
182
|
+
python process_videos.py --sheet "普诺赛中文站" --id 16 --step analyze
|
|
183
|
+
|
|
184
|
+
# 强制重新下载(忽略已有文件)
|
|
185
|
+
python process_videos.py --sheet "YouTube视频" --id 2143 --force
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 批量全量
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# 全量执行(2 个并发,失败重试 3 次)
|
|
192
|
+
python process_videos.py --concurrency 2 --retry 3
|
|
193
|
+
|
|
194
|
+
# 只跑某一 sheet
|
|
195
|
+
python process_videos.py --sheet "YouTube视频" --concurrency 2 --retry 3
|
|
196
|
+
|
|
197
|
+
# 先干跑预览
|
|
198
|
+
python process_videos.py --dry-run
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 重跑失败
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# 第一次跑完后生成 reports/report_xxx.json
|
|
205
|
+
# 查看失败项:
|
|
206
|
+
python process_videos.py --retry-failed reports/report_20260610_143000.json --dry-run
|
|
207
|
+
|
|
208
|
+
# 重跑:
|
|
209
|
+
python process_videos.py --retry-failed reports/report_20260610_143000.json --concurrency 2 --retry 3
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### 超时控制(防止任务卡死)
|
|
213
|
+
|
|
214
|
+
每个步骤都有独立超时,超时后自动 kill 子进程、标记失败并继续执行后续任务:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# 自定义超时(单位秒)
|
|
218
|
+
python process_videos.py \
|
|
219
|
+
--download-timeout 900 \ # 下载 15 分钟
|
|
220
|
+
--transcode-timeout 600 \ # 转码 10 分钟
|
|
221
|
+
--transcribe-timeout 1200 \ # 识别 20 分钟
|
|
222
|
+
--analyze-timeout 180 # AI 分析 3 分钟
|
|
223
|
+
|
|
224
|
+
# 默认值:下载 600s / 转码 600s / 识别 600s / AI 分析 300s
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
- 超时属于**可重试错误**,会触发指数退避重试(`--retry` 控制次数)
|
|
228
|
+
- 无论超时多少次,**不会阻塞其他并发任务**,失败项会记录到报告
|
|
229
|
+
- 超时失败的任务可用 `--retry-failed` 单独重跑
|
|
230
|
+
|
|
231
|
+
### 工具预检(执行前自动检测)
|
|
232
|
+
|
|
233
|
+
每次执行任务前,脚本会自动检测本次涉及步骤所需的工具/服务是否可用:
|
|
234
|
+
|
|
235
|
+
| 步骤 | 检测项 | 不可用时行为 |
|
|
236
|
+
|------|--------|-------------|
|
|
237
|
+
| download | yt-dlp 可调用 | 提示用户,输入 `yes` 继续 / 其他取消 |
|
|
238
|
+
| transcode | ffmpeg + ffprobe 可调用 | 同上 |
|
|
239
|
+
| transcribe | whisper 服务/本地 CLI 可连通 | 同上 |
|
|
240
|
+
| analyze | AI_ENABLED=true 且 API 配置完整 | 同上 |
|
|
241
|
+
|
|
242
|
+
- **dry-run** 模式下同样展示检测结果(但不中断执行)
|
|
243
|
+
- 所有模式(正常执行、重跑失败、单步运行)均执行预检
|
|
244
|
+
- 即使工具不可用,用户仍可选择强制继续(但相应步骤大概率失败)
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 参数说明
|
|
249
|
+
|
|
250
|
+
| 参数 | 类型 | 默认值 | 说明 |
|
|
251
|
+
|---|---|---|---|
|
|
252
|
+
| `--sheet` | str | 全部 | 指定 sheet:`YouTube视频` 或 `普诺赛中文站` |
|
|
253
|
+
| `--id` | str | — | 指定 extra.id 或 title(单条测试) |
|
|
254
|
+
| `--step` | str | 全跑 | 只执行某步:`download` / `transcode` / `transcribe` |
|
|
255
|
+
| `--force` | flag | off | 强制重做下载+转码,忽略已有文件 |
|
|
256
|
+
| `--concurrency` | int | 1 | 并发数,建议 2~3 |
|
|
257
|
+
| `--retry` | int | 0 | 每步失败最大重试次数 |
|
|
258
|
+
| `--retry-delay` | float | 5 | 重试间隔基数(秒),指数退避 5→10→20 |
|
|
259
|
+
| `--download-timeout` | int | 600 | 单个下载任务最长执行时间(秒) |
|
|
260
|
+
| `--transcode-timeout` | int | 600 | 单个转码任务最长执行时间(秒) |
|
|
261
|
+
| `--transcribe-timeout` | int | 600 | 单个识别任务最长执行时间(秒) |
|
|
262
|
+
| `--analyze-timeout` | int | 300 | 单个 AI 分析任务最长执行时间(秒) |
|
|
263
|
+
| `--dry-run` | flag | off | 干跑模式,只列任务不执行 |
|
|
264
|
+
| `--retry-failed` | path | — | 从报告 JSON 重跑失败项 |
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 重试规则
|
|
269
|
+
|
|
270
|
+
| 可重试 | 不重试 |
|
|
271
|
+
|---|---|
|
|
272
|
+
| 网络超时、连接拒绝 | HTTP 404 / 403 / 401 |
|
|
273
|
+
| yt-dlp 下载中断 | 视频已删除 / 私有 |
|
|
274
|
+
| whisper 服务超时 | 无效 URL、文件不存在 |
|
|
275
|
+
| **步骤级超时(任务卡死)** | 参数错误(ValueError/TypeError) |
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## 智能跳过与自动重转码
|
|
280
|
+
|
|
281
|
+
脚本默认不会重复处理已有文件,但会在以下情况自动触发重做:
|
|
282
|
+
|
|
283
|
+
| 步骤 | 跳过条件 | 自动重做条件 |
|
|
284
|
+
|---|---|---|
|
|
285
|
+
| 下载 | 同名文件已存在(非 `--force`) | `--force` 或文件不存在 |
|
|
286
|
+
| 转码 | WAV 已存在 **且** MP4 时间戳 ≤ WAV 时间戳 | `--force` 或 **MP4 比 WAV 新**(重新下载过) |
|
|
287
|
+
| 识别 | —(每次必跑,覆盖写入 Excel) | — |
|
|
288
|
+
|
|
289
|
+
> 关键设计:即使不加 `--force`,只要视频重新下载过(MP4 的修改时间晚于 WAV),转码也会自动重新执行,**确保下载和转码内容始终保持一致**。
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 临时文件自动清理
|
|
294
|
+
|
|
295
|
+
yt-dlp 下载过程中会生成 `.part`(未完成分片)和 `.ytdl`(元数据)临时文件。脚本在以下时机自动清理这些残留:
|
|
296
|
+
|
|
297
|
+
| 时机 | 说明 |
|
|
298
|
+
|------|------|
|
|
299
|
+
| 下载开始前 | 清除上次中断留下的 `.part` / `.ytdl`,确保干净环境 |
|
|
300
|
+
| 跳过已有文件时 | 检查并清除该视频的历史残留 |
|
|
301
|
+
| 下载失败后 | 立即清理,避免无效文件占磁盘 |
|
|
302
|
+
|
|
303
|
+
> 例如:`2152.mp4.part` + `2152.mp4.ytdl` 会在下次下载该视频时自动删除,无需手动清理。
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## AI 关键词归纳
|
|
308
|
+
|
|
309
|
+
在识别完成后,脚本可自动调用 OpenAI 兼容 API 对识别文本做关键词归纳,结果写入 `keywords` 列。
|
|
310
|
+
|
|
311
|
+
### 配置
|
|
312
|
+
|
|
313
|
+
在 `.env` 中配置以下变量(见 `.env.example`):
|
|
314
|
+
|
|
315
|
+
```env
|
|
316
|
+
# 启用/禁用 AI 分析环节
|
|
317
|
+
AI_ENABLED=true
|
|
318
|
+
|
|
319
|
+
# OpenAI 兼容 API(支持任何兼容接口)
|
|
320
|
+
AI_API_KEY=sk-xxx
|
|
321
|
+
AI_BASE_URL=https://apihub.agnes-ai.com/v1
|
|
322
|
+
AI_MODEL=agnes-2.0-flash
|
|
323
|
+
|
|
324
|
+
# 提示词模板({content} 会被识别文本替换)
|
|
325
|
+
AI_PROMPT_TPL=帮我归纳总结一下Keywords,尽可能全一点,这是内容:{content}
|
|
326
|
+
|
|
327
|
+
# 请求超时(秒)
|
|
328
|
+
AI_TIMEOUT=300
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### 工作原理
|
|
332
|
+
|
|
333
|
+
1. whisper 识别完成 → 得到文本(存入 `content` 列)
|
|
334
|
+
2. 将文本替换 `{content}` 占位符 → 发送到 AI API
|
|
335
|
+
3. AI 返回关键词归纳 → 写入 `keywords` 列
|
|
336
|
+
|
|
337
|
+
> **提示词模板可自由定制**:只需保留 `{content}` 占位符,提示词内容可改为翻译、摘要、分类等任意任务。
|
|
338
|
+
|
|
339
|
+
### 单独运行
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
# 已有识别文本,只跑 AI 分析
|
|
343
|
+
python process_videos.py --sheet "普诺赛中文站" --id 427 --step analyze
|
|
344
|
+
|
|
345
|
+
# 单独跑 analyze 超过 16 条不会写入 Excel
|
|
346
|
+
# 要想写入 Excel 跑完整流程 --step analyze
|
|
347
|
+
python process_videos.py --sheet "YouTube视频" --step analyze --concurrency 2
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### 禁用 AI 分析
|
|
351
|
+
|
|
352
|
+
设置 `AI_ENABLED=false`,识别完成后跳过 AI 分析步骤。
|
|
353
|
+
|
|
354
|
+
---
|
|
355
|
+
|
|
356
|
+
## 文件名去重
|
|
357
|
+
|
|
358
|
+
脚本默认使用 `COL_ID`(即 `extra.id`)作为文件名 stem。当同一个 sheet 内出现重复 id 时,自动应用以下去重策略:
|
|
359
|
+
|
|
360
|
+
| 优先级 | 格式 | 示例 |
|
|
361
|
+
|--------|------|------|
|
|
362
|
+
| 1 | `{id}` | `2143` |
|
|
363
|
+
| 2 | `{id}_{title}` | `2143_产品介绍` |
|
|
364
|
+
| 3 | `{id}_{title}_{platformVid}` | `2143_产品介绍_BV1xx4y1z7Ab` |
|
|
365
|
+
|
|
366
|
+
> 去重仅在同 sheet 内生效,不同 sheet 之间允许同名文件(存放在不同子目录)。
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## 进度显示
|
|
373
|
+
|
|
374
|
+
执行时会同时展示**总体进度**和**单视频进度**:
|
|
375
|
+
|
|
376
|
+
```text
|
|
377
|
+
[1/91] [2143] 开始处理 (sheet=YouTube视频, platform=youtubeId, title=xxx)
|
|
378
|
+
[2143] 开始下载 (平台=youtubeId)
|
|
379
|
+
[2143] https://youtu.be/zzJmKPX8a3c
|
|
380
|
+
[2143] 解析页面...
|
|
381
|
+
[2143] 15.2% 2.50MiB/s ETA 00:17 ← 下载实时进度
|
|
382
|
+
[2143] 45.8% 3.12MiB/s ETA 00:08
|
|
383
|
+
[2143] 下载完成 -> 2143.mp4
|
|
384
|
+
[2143] 开始转码 -> 2143.wav
|
|
385
|
+
[2143] 25.3% (38s/150s) ← 转码进度 + 时长比
|
|
386
|
+
[2143] 50.1% (75s/150s)
|
|
387
|
+
[2143] 转码完成
|
|
388
|
+
[2143] 开始识别 (文件 45.2MB)...
|
|
389
|
+
[2143] 识别中... 5s ← 识别每 5s 报时
|
|
390
|
+
[2143] 识别完成 (22s, 1234 字符)
|
|
391
|
+
|
|
392
|
+
[总进度 1/91 (1.1%)] ✅1 ❌0 ⚠️0 ⏭️0 ← 每完成一个刷新
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
| 层级 | 显示内容 |
|
|
396
|
+
|---|---|
|
|
397
|
+
| 总体进度 | 完成/总任务数、百分比、✅成功 ❌失败 ⚠️部分 ⏭️无视频 四维计数 |
|
|
398
|
+
| 下载 | yt-dlp 实时百分比 + 速度 + ETA |
|
|
399
|
+
| 转码 | 先 ffprobe 取时长,再实时解析 `time=` 算百分比(如 `25.3% (38s/150s)`) |
|
|
400
|
+
| 识别 | 每 5s 打印已用时间,完成时显示总耗时和文本长度 |
|
|
401
|
+
|
|
402
|
+
多线程并发时使用打印锁保证输出不交错。
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## 报告格式
|
|
407
|
+
|
|
408
|
+
执行后在 `reports/` 生成 `report_YYYYMMDD_HHMMSS.json`:
|
|
409
|
+
|
|
410
|
+
```json
|
|
411
|
+
{
|
|
412
|
+
"timestamp": "2026-06-10T14:30:00",
|
|
413
|
+
"config": { "concurrency": 3, "retry": 3 },
|
|
414
|
+
"summary": {
|
|
415
|
+
"total": 91,
|
|
416
|
+
"success": 85,
|
|
417
|
+
"partial": 3,
|
|
418
|
+
"failed": 2,
|
|
419
|
+
"no_video": 1
|
|
420
|
+
},
|
|
421
|
+
"failed_items": [
|
|
422
|
+
{
|
|
423
|
+
"sheet": "普诺赛中文站",
|
|
424
|
+
"id": "427",
|
|
425
|
+
"title": "xxx视频",
|
|
426
|
+
"download_error": "HTTP Error 403",
|
|
427
|
+
"transcode_error": null,
|
|
428
|
+
"transcribe_error": null
|
|
429
|
+
}
|
|
430
|
+
]
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
- **success**:下载 + 转码 + 识别全部成功(AI 分析失败不影响此状态)
|
|
435
|
+
- **partial**:下载 + 转码成功,识别或 AI 分析失败
|
|
436
|
+
- **failed**:下载或转码失败
|
|
437
|
+
- **no_video**:该行无可用视频 ID
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## 典型工作流
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
# 1. 干跑预览
|
|
445
|
+
python process_videos.py --dry-run
|
|
446
|
+
|
|
447
|
+
# 2. 单条验证
|
|
448
|
+
python process_videos.py --sheet "YouTube视频" --id 2143 --retry 2
|
|
449
|
+
|
|
450
|
+
# 3. 全量执行
|
|
451
|
+
python process_videos.py --concurrency 3 --retry 3
|
|
452
|
+
|
|
453
|
+
# 4. 查看报告,重跑失败项
|
|
454
|
+
python process_videos.py --retry-failed reports/report_xxx.json --concurrency 2 --retry 3
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
---
|
|
458
|
+
|
|
459
|
+
## 平台适配说明
|
|
460
|
+
|
|
461
|
+
脚本支持四个视频平台的下载,各有不同的反爬配置:
|
|
462
|
+
|
|
463
|
+
| 平台 | 字段 | 反爬措施 |
|
|
464
|
+
|---|---|---|
|
|
465
|
+
| B站 (bilibili) | `extra.bilibiliBvid` | Chrome UA + Referer 头 + 有效 cookie + 并发分片 |
|
|
466
|
+
| YouTube | `extra.youtubeId` | Chrome UA + Firefox cookie 直读 + 代理 + Node.js 解 n-sig |
|
|
467
|
+
| 腾讯视频 | `extra.tencentVid` | 无需特殊配置 |
|
|
468
|
+
| 优酷 | `extra.youkuId` | 无需特殊配置(部分视频需会员) |
|
|
469
|
+
|
|
470
|
+
> YouTube 反爬最强:需要 **代理** + **登录态 cookie** + **JS runtime 解 n-sig** 三者配合。
|
|
471
|
+
> 脚本会自动给 yt-dlp 及其 node/ejs 子进程注入 `HTTPS_PROXY` 环境变量,确保所有流量走代理。
|
|
472
|
+
|
|
473
|
+
### 各平台 URL 格式与视频 ID 提取
|
|
474
|
+
|
|
475
|
+
脚本通过 `{平台}_URL_TPL` 生成下载链接,支持 yt-dlp 能识别的所有 URL 格式。
|
|
476
|
+
下表列出各平台「标准页面 / 内嵌链接 / 短链接」格式及视频 ID 提取正则,方便从完整 URL 中解析视频 ID。
|
|
477
|
+
|
|
478
|
+
#### YouTube
|
|
479
|
+
|
|
480
|
+
| 格式类型 | URL 示例 | 视频 ID 提取正则 |
|
|
481
|
+
|---|---|---|
|
|
482
|
+
| 标准观看页 | `https://www.youtube.com/watch?v=VIDEO_ID` | `youtube\.com/watch\?v=([a-zA-Z0-9_-]{11})` |
|
|
483
|
+
| 短链接 | `https://youtu.be/VIDEO_ID` | `youtu\.be/([a-zA-Z0-9_-]{11})` |
|
|
484
|
+
| Shorts | `https://www.youtube.com/shorts/VIDEO_ID` | `youtube\.com/shorts/([a-zA-Z0-9_-]{11})` |
|
|
485
|
+
| 内嵌页 | `https://www.youtube.com/embed/VIDEO_ID` | `youtube\.com/embed/([a-zA-Z0-9_-]{11})` |
|
|
486
|
+
| 直播 | `https://www.youtube.com/live/VIDEO_ID` | `youtube\.com/live/([a-zA-Z0-9_-]{11})` |
|
|
487
|
+
|
|
488
|
+
- **视频 ID 格式**:11 位字符(大小写字母 + 数字 + `-` + `_`)
|
|
489
|
+
- **统一提取正则**(覆盖所有格式):
|
|
490
|
+
```
|
|
491
|
+
(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/|live\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})
|
|
492
|
+
```
|
|
493
|
+
- **格式互转**:
|
|
494
|
+
- 标准 → 短链接:提取 `VIDEO_ID` → `https://youtu.be/VIDEO_ID`
|
|
495
|
+
- 标准 → 内嵌:提取 `VIDEO_ID` → `https://www.youtube.com/embed/VIDEO_ID`
|
|
496
|
+
- Shorts → 标准:提取 `VIDEO_ID` → `https://www.youtube.com/watch?v=VIDEO_ID`
|
|
497
|
+
|
|
498
|
+
#### B站(bilibili)
|
|
499
|
+
|
|
500
|
+
| 格式类型 | URL 示例 | 视频 ID 提取正则 |
|
|
501
|
+
|---|---|---|
|
|
502
|
+
| 标准页(BV 号) | `https://www.bilibili.com/video/BV1xx411c7mD` | `bilibili\.com\/video\/(BV[a-zA-Z0-9]{10})` |
|
|
503
|
+
| 标准页(av 号) | `https://www.bilibili.com/video/av170001` | `bilibili\.com\/video\/av(\d+)` |
|
|
504
|
+
| 短链接 | `https://b23.tv/BV1xx411c7mD` | `b23\.tv\/(BV[a-zA-Z0-9]{10})` |
|
|
505
|
+
| 内嵌页 | `https://player.bilibili.com/player.html?bvid=BV1xx411c7mD&cid=CID` | `bvid=(BV[a-zA-Z0-9]{10})` |
|
|
506
|
+
| 移动端 | `https://m.bilibili.com/video/BV1xx411c7mD` | `m\.bilibili\.com\/video\/(BV[a-zA-Z0-9]{10})` |
|
|
507
|
+
|
|
508
|
+
- **BV 号格式**:`BV` + 10 位字符(大小写敏感)
|
|
509
|
+
- **统一提取正则**:
|
|
510
|
+
```
|
|
511
|
+
bilibili\.com\/video\/(BV[a-zA-Z0-9]{10})|bvid=(BV[a-zA-Z0-9]{10})
|
|
512
|
+
```
|
|
513
|
+
- **格式互转**:
|
|
514
|
+
- 标准 → 内嵌:提取 `BV_ID` 后需通过 B站 API 获取 `cid`
|
|
515
|
+
→ `https://api.bilibili.com/x/player/pagelist?bvid=BV_ID` 获取 cid
|
|
516
|
+
→ 内嵌 URL:`https://player.bilibili.com/player.html?bvid=BV_ID&cid=CID&page=1`
|
|
517
|
+
- BV 号 → av 号:需调用 API(`https://api.bilibili.com/x/web-interface/view?bvid=BV_ID` 返回 `aid`)
|
|
518
|
+
|
|
519
|
+
#### 腾讯视频
|
|
520
|
+
|
|
521
|
+
| 格式类型 | URL 示例 | 视频 ID 提取正则 |
|
|
522
|
+
|---|---|---|
|
|
523
|
+
| 标准页(x/page) | `https://v.qq.com/x/page/VIDEO_ID.html` | `v\.qq\.com\/x\/page\/([a-zA-Z0-9]+)\.html` |
|
|
524
|
+
| 标准页(x/cover) | `https://v.qq.com/x/cover/COVER/VIDEO_ID.html` | `v\.qq\.com\/x\/cover\/[^\/]+\/([a-zA-Z0-9]+)\.html` |
|
|
525
|
+
| 内嵌页 | `https://v.qq.com/txp/iframe/player.html?vid=VIDEO_ID` | `[?&]vid=([a-zA-Z0-9]+)` |
|
|
526
|
+
| 移动端 | `https://m.v.qq.com/x/mv.xhtml?vid=VIDEO_ID` | `[?&]vid=([a-zA-Z0-9]+)` |
|
|
527
|
+
|
|
528
|
+
- **视频 ID 格式**:字母 + 数字组合(如 `o0325y3hqh`,长度不固定)
|
|
529
|
+
- **统一提取正则**:
|
|
530
|
+
```
|
|
531
|
+
v\.qq\.com\/(?:x\/page\/|x\/cover\/[^\/]+\/)([a-zA-Z0-9]+)\.html|[?&]vid=([a-zA-Z0-9]+)
|
|
532
|
+
```
|
|
533
|
+
- **格式互转**:
|
|
534
|
+
- 标准 → 内嵌:提取 `VIDEO_ID` → `https://v.qq.com/txp/iframe/player.html?vid=VIDEO_ID`
|
|
535
|
+
|
|
536
|
+
#### 优酷(Youku)
|
|
537
|
+
|
|
538
|
+
| 格式类型 | URL 示例 | 视频 ID 提取正则 |
|
|
539
|
+
|---|---|---|
|
|
540
|
+
| 标准页(v_show) | `https://v.youku.com/v_show/id_VIDEO_ID.html` | `v\.youku\.com\/v_show\/id_([a-zA-Z0-9=]+)\.html` |
|
|
541
|
+
| 标准页(video) | `https://v.youku.com/video/VIDEO_ID` | `v\.youku\.com\/video\/([a-zA-Z0-9=]+)` |
|
|
542
|
+
| 标准页(www) | `https://www.youku.com/v_show/id_VIDEO_ID.html` | `www\.youku\.com\/v_show\/id_([a-zA-Z0-9=]+)\.html` |
|
|
543
|
+
|
|
544
|
+
- **视频 ID 格式**:旧格式 `X` + Base64 字符串(可能含 `=` 填充);新格式长度不固定
|
|
545
|
+
- **统一提取正则**:
|
|
546
|
+
```
|
|
547
|
+
v\.youku\.com\/v_show\/id_([a-zA-Z0-9=]+)\.html|v\.youku\.com\/video\/([a-zA-Z0-9=]+)
|
|
548
|
+
```
|
|
549
|
+
- **格式互转**:
|
|
550
|
+
- 优酷内嵌格式较复杂,建议直接使用标准页链接(`{YOUKU_URL_TPL}`)
|
|
551
|
+
|
|
552
|
+
> **脚本使用提示**:Excel 中只需填入视频 ID(如 `zzJmKPX8a3c`、`BV1pg411b7Ug`、`o0325y3hqh`、`XMzgxNzExNTY4MA==`),脚本自动替换 URL 模板中的 `{youtubeId}`、`{bilibiliBvid}` 等占位符生成下载链接。
|
|
553
|
+
|
|
554
|
+
### 常见下载错误
|
|
555
|
+
|
|
556
|
+
| 错误 | 平台 | 原因 | 解决方案 |
|
|
557
|
+
|---|---|---|---|
|
|
558
|
+
| `Sign in to confirm you're not a bot` | YouTube | cookie 过期或无效 | 检查 Firefox 登录态,或重新导出 cookie 文件 |
|
|
559
|
+
| `cookies does no longer seem to be valid` | YouTube | cookie 文件超过 48h | 用 Firefox cookies-from-browser 方案(免维护) |
|
|
560
|
+
| `Unable to download webpage: HTTP Error 403` | YouTube | IP 被识别为非 YouTube 地区 | 确保代理运行(端口 7897),检查 `YOUTUBE_PROXY` |
|
|
561
|
+
| `n challenge solving failed` | YouTube | 无 JS 运行时 | 安装 Node.js,确保 `YOUTUBE_JS_RUNTIMES=node` |
|
|
562
|
+
| `Requested format is not available` | YouTube | n-sig 未解开,格式不可用 | 同上,安装 JS 运行时 |
|
|
563
|
+
| `HTTP Error 412` | B站 | 缺少 Chrome UA 或 cookie 过期 | 重新导出 `cookies/bilibili.txt` |
|
|
564
|
+
| `HTTP Error 403` | B站 | 地区限制或视频已删除 | 检查视频是否可访问 |
|
|
565
|
+
| `dpapi decryption failed` | YouTube | Windows Chrome cookie 加密 | **改用 Firefox**(`.env` 中设 `YOUTUBE_COOKIES_FROM_BROWSER=firefox`) |
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
## 换电脑使用
|
|
570
|
+
|
|
571
|
+
1. 安装上述所有必装工具,确保 `yt-dlp`、`ffmpeg`、`ffprobe`、`node` 均在 PATH
|
|
572
|
+
2. `pip install pandas openpyxl requests python-dotenv`
|
|
573
|
+
3. `cp .env.example .env`,根据实际情况修改 `.env` 中的路径、代理端口和字段映射
|
|
574
|
+
4. 用 Firefox 登录 YouTube,设置 `YOUTUBE_COOKIES_FROM_BROWSER=firefox`
|
|
575
|
+
5. B站 cookie 仍需手动导出 `cookies/bilibili.txt`
|
|
576
|
+
6. 启动代理(Clash Verge 等),确认端口匹配 `YOUTUBE_PROXY`
|
|
577
|
+
7. `python process_videos.py --dry-run` 验证
|
|
578
|
+
|
|
579
|
+
## 适配其他 Excel
|
|
580
|
+
|
|
581
|
+
如果需要用这套脚本处理**其他项目的 Excel**(列名不同、平台不同):
|
|
582
|
+
|
|
583
|
+
1. 复制 `.env.example` 为新 `.env`(或修改现有 `.env`)
|
|
584
|
+
2. 修改 `EXCEL_FILE` 指向新 Excel
|
|
585
|
+
3. 修改列映射(`COL_ID`、`COL_TITLE`、`COL_CONTENT` 及各平台列名)
|
|
586
|
+
4. 修改 `VIDEO_SHEETS` 为新的 sheet 名称
|
|
587
|
+
5. 如需新平台,在 `PLATFORM_PRIORITY` 中添加 key,并配置对应的 `{KEY}_URL_TPL`
|
|
588
|
+
6. `python process_videos.py --dry-run` 验证配置
|
|
589
|
+
7. 跑全量
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "video-pipeline",
|
|
3
|
+
"version": "1.0.4",
|
|
4
|
+
"description": "视频下载、转码、文本识别、AI 关键词分析一体化流程 CLI 工具",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"video",
|
|
7
|
+
"download",
|
|
8
|
+
"transcode",
|
|
9
|
+
"whisper",
|
|
10
|
+
"transcribe",
|
|
11
|
+
"ai",
|
|
12
|
+
"keywords",
|
|
13
|
+
"yt-dlp",
|
|
14
|
+
"ffmpeg",
|
|
15
|
+
"bilibili",
|
|
16
|
+
"youtube",
|
|
17
|
+
"tencent",
|
|
18
|
+
"youku"
|
|
19
|
+
],
|
|
20
|
+
"homepage": "https://gitee.com/siriussupreme/yt-dlp_ffmpeg_whisper_memo-ai",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://gitee.com/siriussupreme/yt-dlp_ffmpeg_whisper_memo-ai.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://gitee.com/siriussupreme/yt-dlp_ffmpeg_whisper_memo-ai/issues"
|
|
27
|
+
},
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"author": "",
|
|
30
|
+
"type": "module",
|
|
31
|
+
"main": "process_videos.js",
|
|
32
|
+
"bin": {
|
|
33
|
+
"video-pipeline": "process_videos.js"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"process_videos.js",
|
|
37
|
+
".env.example",
|
|
38
|
+
"README.md",
|
|
39
|
+
"CHANGELOG.md",
|
|
40
|
+
"LICENSE"
|
|
41
|
+
],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"start": "node process_videos.js",
|
|
47
|
+
"test": "node -c process_videos.js",
|
|
48
|
+
"lint:commit": "commitlint --edit",
|
|
49
|
+
"release": "node scripts/release.js",
|
|
50
|
+
"release:dry": "node scripts/release.js --dry-run",
|
|
51
|
+
"prepare": "husky"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@inquirer/prompts": "^8.5.2",
|
|
55
|
+
"commander": "^15.0.0",
|
|
56
|
+
"dotenv": "^17.4.2",
|
|
57
|
+
"p-limit": "^7.3.0",
|
|
58
|
+
"xlsx": "^0.18.5"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@commitlint/cli": "^21.0.2",
|
|
62
|
+
"@commitlint/config-conventional": "^21.0.2",
|
|
63
|
+
"@release-it/conventional-changelog": "^11.0.1",
|
|
64
|
+
"husky": "^9.1.7",
|
|
65
|
+
"release-it": "^20.2.0"
|
|
66
|
+
}
|
|
67
|
+
}
|