@pls-compile-owo/fisheye 0.0.0-stage → 0.2.1
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/LICENSE +27 -0
- package/README.en.md +93 -0
- package/README.md +284 -2
- package/analyze_subs.py +126 -0
- package/credentials.example.json +5 -0
- package/danmaku-filter.mjs +54 -0
- package/docs/CONFIG.md +138 -0
- package/docs/DESIGN.md +144 -0
- package/docs/EXPERIMENTS.md +785 -0
- package/docs/REVIEW.md +139 -0
- package/fisheye.mjs +1535 -0
- package/package.json +43 -4
- package/test-danmaku-filter.js +56 -0
package/docs/CONFIG.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# 配置与故障排查
|
|
2
|
+
|
|
3
|
+
## 依赖解析顺序
|
|
4
|
+
|
|
5
|
+
fisheye 不写死任何绝对路径。ffmpeg / python / 凭证按这个顺序找:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
1. 环境变量 FFMPEG / PYTHON / BILI_CRED
|
|
9
|
+
2. 配置文件 ~/.config/fisheye/config.json
|
|
10
|
+
3. 项目内 <项目>/bin/ffmpeg(.exe)、<项目>/credentials.json、<项目>/.venv/
|
|
11
|
+
4. PATH 系统 PATH 里的 ffmpeg / python
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
全找不到时直接报错,并给出具体修复命令。
|
|
15
|
+
|
|
16
|
+
## 环境变量
|
|
17
|
+
|
|
18
|
+
| 变量 | 作用 | 示例 |
|
|
19
|
+
|---|---|---|
|
|
20
|
+
| `FFMPEG` | ffmpeg 可执行文件 | `C:/tools/ffmpeg/bin/ffmpeg.exe` |
|
|
21
|
+
| `PYTHON` | python(字幕带检测用,可选) | `C:/Python312/python.exe` |
|
|
22
|
+
| `BILI_CRED` | B站登录态 JSON | `D:/secrets/bili.json` |
|
|
23
|
+
| `FISHEYE_HOME` | 数据目录(cache/out 放哪) | `D:/video-cache` |
|
|
24
|
+
|
|
25
|
+
临时覆盖:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Windows (PowerShell)
|
|
29
|
+
$env:FFMPEG = "C:/tools/ffmpeg/bin/ffmpeg.exe"; node fisheye.mjs selftest
|
|
30
|
+
|
|
31
|
+
# Linux/macOS
|
|
32
|
+
FFMPEG=/usr/bin/ffmpeg node fisheye.mjs selftest
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 配置文件
|
|
36
|
+
|
|
37
|
+
`~/.config/fisheye/config.json`(Windows 上是 `C:\Users\<你>\.config\fisheye\config.json`):
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"ffmpeg": "C:/tools/ffmpeg/bin/ffmpeg.exe",
|
|
42
|
+
"python": "C:/Python312/python.exe",
|
|
43
|
+
"credentials": "D:/secrets/bili.json"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
> ⚠️ 用记事本保存可能带 UTF-8 BOM,fisheye 会自己剥掉,但更推荐用 VS Code / 命令行写。
|
|
48
|
+
|
|
49
|
+
## 凭证文件
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"sessdata": "xxxxx%2C1xxxxx",
|
|
54
|
+
"bili_jct": "xxxxxxxxxxxxxxxxxxxxxxxxxxxx",
|
|
55
|
+
"buvid3": "XXXX-XXXX-XXXX-XXXX"
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
获取步骤:
|
|
60
|
+
|
|
61
|
+
1. 浏览器登录 B 站
|
|
62
|
+
2. F12 → Application(应用)→ Cookies → `https://www.bilibili.com`
|
|
63
|
+
3. 复制 `SESSDATA` / `bili_jct` / `buvid3` 三个值
|
|
64
|
+
|
|
65
|
+
**注意**:
|
|
66
|
+
- SESSDATA 有时效,过期后需要重新复制(表现为 selftest 的「视频 API」失败)
|
|
67
|
+
- 不要在公共电脑保存,不要提交到 git
|
|
68
|
+
|
|
69
|
+
## 故障排查
|
|
70
|
+
|
|
71
|
+
### 先跑自检
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
node fisheye.mjs selftest --deep
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
每一项都会告诉你哪里坏了。
|
|
78
|
+
|
|
79
|
+
### 常见错误对照
|
|
80
|
+
|
|
81
|
+
| 现象 | 原因 | 处理 |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `找不到 ffmpeg` | 没装 / 不在 PATH | 装 ffmpeg,或设 `FFMPEG` 环境变量 |
|
|
84
|
+
| `找不到 B站登录态文件` | 没有 credentials.json | 按上面步骤创建 |
|
|
85
|
+
| `视频 API 请求失败` | 网络 / 接口变更(这个接口是公开的,**不吃 cookie**) | 先查网络;再确认 bilibili 接口是否改了 |
|
|
86
|
+
| `官方字幕 API` 显示 `[--]` | 这条视频没字幕轨,**或** SESSDATA 过期(匿名拿不到 AI 字幕) | 换一条有字幕的视频交叉验证;若多条都拿不到,重新登录复制 cookies |
|
|
87
|
+
| 播放直链拿不到 / 403 | SESSDATA 过期(playurl 接口要登录态) | 重新登录复制 cookies |
|
|
88
|
+
| `字幕带实测` 失败 | python 缺 numpy/Pillow | `pip install numpy pillow` |
|
|
89
|
+
| 渲染出的拼图是黑图 | ffmpeg seek 失败 / 视频编码特殊 | 反馈时附上视频 BV 号和 ffmpeg 版本 |
|
|
90
|
+
|
|
91
|
+
### 性能调优
|
|
92
|
+
|
|
93
|
+
**视频太长跑不动?**
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# 分段看
|
|
97
|
+
node fisheye.mjs auto BV1xxx --from 0:00 --to 10:00
|
|
98
|
+
node fisheye.mjs auto BV1xxx --from 10:00 --to 20:00
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**缓存太大?**
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Windows
|
|
105
|
+
rmdir /s /q cache
|
|
106
|
+
# Linux/macOS
|
|
107
|
+
rm -rf cache
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`cache/` 存的是下载的原始 mp4,40 分钟视频约 235 MB。
|
|
111
|
+
|
|
112
|
+
**想提高密度?**
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
node fisheye.mjs auto BV1xxx --density 4 # 强制 4 秒/帧
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
注意:密度提高 = 张数增加 = token 成本增加。竖屏视频尤其贵(单张 3793 token)。
|
|
119
|
+
|
|
120
|
+
**token 预算怎么估?**
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
总成本 ≈ 官方字幕字数
|
|
124
|
+
+ 拼图张数 × 单张成本
|
|
125
|
+
+ 精读帧数 × 1300
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
单张拼图成本按面积算:1920×1080 = 1200 token;竖屏 1080×1920 = 3793 token。
|
|
129
|
+
|
|
130
|
+
## Python 可选依赖
|
|
131
|
+
|
|
132
|
+
字幕带定位需要:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
pip install numpy pillow
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
不装的话:`[字幕]` 那一步会告警并降级为「只看画面」,其他功能不受影响。
|
package/docs/DESIGN.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# 设计决策
|
|
2
|
+
|
|
3
|
+
> 这份文档解释 fisheye 为什么这么做。想知道怎么用,看 [`../README.md`](../README.md)。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. 为什么按「信息源」排序,而不是统一抽帧
|
|
8
|
+
|
|
9
|
+
核心观察:**同样一份信息,用不同媒介读的成本差一个数量级。**
|
|
10
|
+
|
|
11
|
+
| 信息 | 文本形式 | 图片形式 |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| 一句台词 | ~10 token | 一张字幕带图 ~1000 token |
|
|
14
|
+
| 一个关键画面 | 无法表达 | ~300 token(拼图里一格) |
|
|
15
|
+
|
|
16
|
+
所以顺序是:**能读文本就不读图,必须读图就拼在一起读。**
|
|
17
|
+
|
|
18
|
+
这不是「优化技巧」,是成本结构决定的——官方字幕 12410 字约 12410 token,而 40 分钟视频按 1fps 抽帧是 60 万 token。
|
|
19
|
+
|
|
20
|
+
## 2. 取帧:等间隔骨架 + 吸附画面变化点
|
|
21
|
+
|
|
22
|
+
**只等间隔的问题**:关键画面可能只出现 1~2 秒,等间隔采样会漏掉。
|
|
23
|
+
|
|
24
|
+
**只吸附关键帧的问题**:转场往往集中在开头/高潮,中段会大量空档。
|
|
25
|
+
|
|
26
|
+
**混合方案**:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
base[i] = 段起点 + (i+0.5) × 段长 / n ← 等间隔骨架,保证覆盖
|
|
30
|
+
每个 base 在 ±半格内找 scene_score 最高的变化点 → 吸附过去
|
|
31
|
+
阈值阶梯 0.3 → 0.15 → 0.08,≥75% 帧吸附成功就停
|
|
32
|
+
去重:吸附后相邻帧距离 < 帧间隔×0.4 时退回骨架
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
**双保险**:覆盖靠骨架,关键帧靠吸附。
|
|
36
|
+
|
|
37
|
+
### 踩过的坑
|
|
38
|
+
|
|
39
|
+
- **阈值太高**:0.3 档吸附率低时,一度以为「场景检测失效」,实际是这类视频(冲浪、慢节奏)变化点本来就少。
|
|
40
|
+
- **去重下限**:最初没有,导致多帧挤在同一秒。
|
|
41
|
+
- **三帧吸附同一变化点**:吸附时取「窗口内最强」,多个基准点会指向同一个强变化点——靠去重(`pickTimes` 里的 `minGap`)把后一个退回骨架才解决。
|
|
42
|
+
|
|
43
|
+
## 3. 字幕带:为什么不用 OCR
|
|
44
|
+
|
|
45
|
+
**为什么不 OCR**:
|
|
46
|
+
- 中文 OCR 在低分辨率、描边、花哨排版下错误率高
|
|
47
|
+
- 需要额外依赖(PaddleOCR / RapidOCR 都是几百 MB)
|
|
48
|
+
- 多模态模型读字幕带几乎免费(图里已经有字,不需要二次识别)
|
|
49
|
+
|
|
50
|
+
**为什么切「带」而不是整帧**:
|
|
51
|
+
- 字幕只占画面底部 10%,整帧读浪费 90% token
|
|
52
|
+
- 竖排 N 行字幕 → 一张图拿到 N 句台词
|
|
53
|
+
|
|
54
|
+
**怎么定位字幕带(不靠 OCR)**:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
抽 24 帧 → 每帧算逐行梯度(字幕边缘会产生高频)
|
|
58
|
+
→ 跨帧取 30 分位(字幕「每帧都在」→ 低分位也高;画面内容不会稳定在固定行)
|
|
59
|
+
→ 找峰值 → 向两侧扩展到阈值以下
|
|
60
|
+
→ 高度限制(不超过搜索区 1/4)+ 置信度(字幕区强度 vs 该帧自身)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 踩过的坑
|
|
64
|
+
|
|
65
|
+
- **抖音式动态字幕**:位置漂移、不是每帧都有 → 检测出的「带」其实是画面内容。加了置信度门槛(<0.5 跳过)。
|
|
66
|
+
- **竖屏高分辨率**:阈值扩展会连成 531px 的大块(实际字幕 60px)→ 加高度上限。
|
|
67
|
+
- **区间模式**:`--from/--to` 时只采样区间,字幕位置被画面边缘抢走 → 改为**始终采样全片**(字幕位置是视频级属性)。
|
|
68
|
+
|
|
69
|
+
## 4. 弹幕:为什么它能当信号
|
|
70
|
+
|
|
71
|
+
**B 站的弹幕密度本身就是内容信号**,而且能看到画面和字幕都没有的东西:
|
|
72
|
+
|
|
73
|
+
- **热区 = 观众高光**:一条 LOL 视频 01:20-01:30 弹幕密度是平均的 3~5 倍,内容是团战高潮
|
|
74
|
+
- **密度低谷 = 广告 / 无趣段**:40 分钟视频的广告段密度只有 31%(观众跳过)
|
|
75
|
+
|
|
76
|
+
### 为什么要过滤广告弹幕
|
|
77
|
+
|
|
78
|
+
弹幕里混着「加微信」「私信我」这类垃圾。但**过滤的原则是宁漏勿错**——
|
|
79
|
+
|
|
80
|
+
踩过三次误伤:
|
|
81
|
+
- 「再也不怕**看这**个场面了」→ 被「看+这」规则误判广告
|
|
82
|
+
- 「**666**」→ 被「无意义符号」规则误杀
|
|
83
|
+
- 「**???**」→ 被「纯符号」规则误杀
|
|
84
|
+
|
|
85
|
+
最终规则只认**完整短语**(加微信、私信我、关注我主页…),并且:
|
|
86
|
+
- 数字重复(6666/1111)保留——是梗
|
|
87
|
+
- 中文重复(哈哈哈哈)保留——是情绪
|
|
88
|
+
- 纯情绪标点(???/!!!)保留
|
|
89
|
+
|
|
90
|
+
48 个测试用例固定了这些边界。
|
|
91
|
+
|
|
92
|
+
## 5. 密度与张数决策
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
变化率 rate = 显著变化次数 / 秒
|
|
96
|
+
rate > 1.2 → 4 秒/帧(快剪/MV/游戏)
|
|
97
|
+
rate > 0.5 → 6 秒/帧(vlog/教程)
|
|
98
|
+
rate > 0.15 → 9 秒/帧(口播/慢节奏)
|
|
99
|
+
else → 13 秒/帧(静态/录屏)
|
|
100
|
+
|
|
101
|
+
张数 = ceil(时长 / 帧间隔 / 9),再被预算封顶,硬上限 12 张
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
**为什么 4~13 秒**:经验值。人眼对「同一场景内的连续动作」可以合并理解,但场景切换必须分开。4 秒/帧在高动态视频里够用,13 秒/帧对静态内容不浪费。
|
|
105
|
+
|
|
106
|
+
**为什么硬上限 12 张**:108 帧已经是 40 分钟视频的极限(22 秒/帧)。再稀释就没意义了,不如用 `--from/--to` 分段看。
|
|
107
|
+
|
|
108
|
+
### 踩过的坑
|
|
109
|
+
|
|
110
|
+
- 多 P 视频用 `view.duration`(全部分 P 总和)算密度 → 应该用 `pages[p-1].duration`
|
|
111
|
+
- 竖屏 token 低估:1920×1920 的拼图是 2133 token,不是 1200
|
|
112
|
+
|
|
113
|
+
## 6. 广告段:分级而不武断
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
30 秒滑窗算弹幕密度
|
|
117
|
+
→ 低于中位数 45% 的连续窗口(≥60s)→ 候选段
|
|
118
|
+
→ 附近 ±20s 有关键词(金主/恰饭/赞助…)→ confidence='high'
|
|
119
|
+
→ 没有关键词 → confidence='low'(低互动段,未必是广告)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**为什么分级**:纯密度低谷也可能是「纯画面段」「BGM 段」「慢镜头」——贸然跳过会丢内容。只有「密度低谷 **且** 有广告关键词」才敢动手。
|
|
123
|
+
|
|
124
|
+
**高置信度广告段的处理**:从时间轴上压缩掉,剩余有效时间重新均分。帧数不变,内容密度提升。
|
|
125
|
+
|
|
126
|
+
**但只压缩 `confidence='high'` 的段**——`low`(只有密度低谷、无关键词)仍然占帧。因为纯密度低谷可能是画面段/慢镜头,贸然压缩会丢内容。
|
|
127
|
+
|
|
128
|
+
## 7. 不做的事(以及为什么)
|
|
129
|
+
|
|
130
|
+
| 没做的功能 | 原因 |
|
|
131
|
+
|---|---|
|
|
132
|
+
| 多模态视频模型(Video-LLaVA 等) | 需要本地 GPU / 大量显存;云端 API 更贵且不可控 |
|
|
133
|
+
| 无监督关键帧聚类 | 场景检测已经够用,聚类增加复杂度但收益不明显 |
|
|
134
|
+
| 自动生成视频摘要 | 这是 agent 的活,工具只负责「给材料」——摘要质量取决于模型,不该硬编码在工具里 |
|
|
135
|
+
| OCR 画面文字 | 字幕带 + 多模态读图已经覆盖;OCR 增加大依赖 |
|
|
136
|
+
| 支持 YouTube | 接口层不同(字幕格式、弹幕不存在),但核心逻辑可复用,欢迎 PR |
|
|
137
|
+
|
|
138
|
+
## 8. 一条经验
|
|
139
|
+
|
|
140
|
+
**这个工具最值钱的部分不是代码,是那些踩过的坑。**
|
|
141
|
+
|
|
142
|
+
每次「看起来只是加个参数」的功能(弹幕过滤、字幕带定位),实际都在和一个具体的中文视频生态妥协:动态字幕、刷屏文化、广告话术、B 站接口加密。
|
|
143
|
+
|
|
144
|
+
设计文档能写出来的「为什么」,都是踩着具体案例改出来的。
|