@pls-compile-owo/fisheye 0.8.0 → 0.8.2

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/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  本项目遵循 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/) 规范与 [语义化版本](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [0.8.2] - 2026-11-14
6
+
7
+ ### Fixed
8
+
9
+ - **广告段检测在稀疏弹幕视频上误报**:弹幕密度低于 0.2 条/秒时密度统计不可靠(艺术短片《利马》127 条/856s 被误标 3 段「低互动段」),改为直接跳过检测(宁漏勿错)。
10
+ - golden fixture 补充人工真值标注(利马三段确认非广告)。
11
+
12
+ ## [0.8.1] - 2026-11-14
13
+
14
+ ### Added
15
+
16
+ - **golden case 验收集**(`test/golden/`):4 条真实 B站 视频(含字幕/无字幕/高密度弹幕/利马定格片)的字幕与弹幕样本落盘为 fixture,离线验证字幕质量判定、广告段检测、取帧计划的输出与真实回放一致。
17
+ - 字幕质量判定提取为纯函数 `assessSubtitleQuality`(bili.mjs),供单测与 golden 用。
18
+
5
19
  ## [0.8.0] - 2026-11-14
6
20
 
7
21
  ### Changed
package/README.md CHANGED
@@ -405,7 +405,7 @@ B 站的弹幕密度本身就是内容信号。实测一条 40 分钟视频:
405
405
  ## 开发
406
406
 
407
407
  ```bash
408
- node --test test/ # 单元测试(全部模块:46 个用例)
408
+ node --test test/ # 单元测试 + golden 验收集(真实视频样本,离线可跑)
409
409
  node test-danmaku-filter.js # 弹幕过滤规则测试(51 条用例)
410
410
  node fisheye.mjs selftest --deep # 含抽帧/字幕带实测的自检
411
411
  ```
package/lib/bili.mjs CHANGED
@@ -76,6 +76,25 @@ export function pickSubtitleTracks(subs, preferZh = true) {
76
76
  return { main: foreign, translate: zh }
77
77
  }
78
78
 
79
+ /**
80
+ * 字幕质量判定(纯函数,供单测/golden case 用)。
81
+ * 中文 AI 字幕在纯音乐/音效视频上会输出大量 ♪ 噪声或重复行——质量太差不能当台词。
82
+ * @param {string[]} contents 每行字幕文本(含 ♪ 符号也行)
83
+ * @returns {{ quality: 'ok'|'noisy', avgEff: number, uniqueRatio: number }}
84
+ */
85
+ export function assessSubtitleQuality(contents) {
86
+ const body = Array.isArray(contents) ? contents.filter((c) => typeof c === 'string' && c.trim()) : []
87
+ if (!body.length) return { quality: 'noisy', avgEff: 0, uniqueRatio: 0 }
88
+ const eff = body.map((c) => c.replace(/[♪\s]/g, '').length)
89
+ const avgEff = eff.reduce((a, b) => a + b, 0) / body.length
90
+ const uniqueRatio = new Set(body.map((c) => c.replace(/[♪\s]/g, ''))).size / body.length
91
+ return {
92
+ quality: avgEff < 2 || uniqueRatio < 0.4 ? 'noisy' : 'ok',
93
+ avgEff: Number(avgEff.toFixed(1)),
94
+ uniqueRatio: Number(uniqueRatio.toFixed(2)),
95
+ }
96
+ }
97
+
79
98
  export async function getOfficialSubs(bvid, cid, range = null) {
80
99
  try {
81
100
  const nav = await (await fetchWithRetry('https://api.bilibili.com/x/web-interface/nav',
package/lib/danmaku.mjs CHANGED
@@ -6,6 +6,10 @@ export const AD_SEGMENT_SIGNAL = /金主|恰饭|商单|硬广|软广|赞助|推
6
6
 
7
7
  export function detectAdSegments(danmaku, duration, bucketSec = 30, stepSec = 10) {
8
8
  if (!danmaku?.length || duration < 120) return []
9
+ // 弹幕太稀疏(如艺术短片 127 条 / 856s ≈ 0.15 条/s)时密度统计不可靠:
10
+ // 中位数本身极低,「低于中位数 45%」的段落只会是「观众不爱发弹幕的正常段落」。
11
+ // 低于 0.2 条/秒直接不判广告(宁漏勿错,低互动段标签也会误导)。
12
+ if (danmaku.length / duration < 0.2) return []
9
13
  const dens = []
10
14
  for (let s = 0; s + bucketSec <= duration; s += stepSec) {
11
15
  let n = 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pls-compile-owo/fisheye",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "Token-budget-aware video understanding for AI agents: official subtitles -> burned-in subtitle band -> danmaku highlights -> adaptive keyframe grids -> targeted refine.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,27 @@
1
+ {
2
+ "bvid": "BV1Cc411Q78T",
3
+ "title": "起床,老婆,亲亲,三连【返场真喵真事】",
4
+ "duration": 30,
5
+ "hasOfficialSubs": true,
6
+ "expectedQuality": "ok",
7
+ "subtitleLines": [
8
+ "♪ 我想睡到自然醒 ♪",
9
+ "♪ 换到今天星期几 ♪",
10
+ "♪ 忘掉所有烦恼和无聊的问题 ♪",
11
+ "♪ 写着那首的厨艺 ♪",
12
+ "♪ 想你 ♪",
13
+ "♪ 忙碌了自己在追半个地燃烧我的卡路里 ♪"
14
+ ],
15
+ "expectedAdSegments": 0,
16
+ "danmaku": [
17
+ {
18
+ "t": 0,
19
+ "text": "猫界周棋洛doge"
20
+ },
21
+ {
22
+ "t": 3.431,
23
+ "text": "死亡音乐"
24
+ }
25
+ ],
26
+ "danmakuCount": 2
27
+ }
@@ -0,0 +1,521 @@
1
+ {
2
+ "bvid": "BV1kr4y1x7DU",
3
+ "title": "屡获大奖!唯美催泪定格动画《利马》",
4
+ "duration": 856,
5
+ "hasOfficialSubs": false,
6
+ "expectedQuality": null,
7
+ "subtitleLines": [],
8
+ "expectedAdSegments": 0,
9
+ "danmaku": [
10
+ {
11
+ "t": 79.295,
12
+ "text": "我独自一人"
13
+ },
14
+ {
15
+ "t": 279.727,
16
+ "text": "你在吗"
17
+ },
18
+ {
19
+ "t": 367.006,
20
+ "text": "应该是父亲吧…"
21
+ },
22
+ {
23
+ "t": 811.499,
24
+ "text": "无须找寻,它自会来"
25
+ },
26
+ {
27
+ "t": 95.985,
28
+ "text": "我在陪你看"
29
+ },
30
+ {
31
+ "t": 420.144,
32
+ "text": "hello 10"
33
+ },
34
+ {
35
+ "t": 492.372,
36
+ "text": "这鸟有点意思"
37
+ },
38
+ {
39
+ "t": 780.839,
40
+ "text": "有暴风雨他去不了山顶看。于是雨停了。夜幕印在的水洼上或水塘。"
41
+ },
42
+ {
43
+ "t": 238.537,
44
+ "text": "他变成了星星。"
45
+ },
46
+ {
47
+ "t": 217.427,
48
+ "text": "果然。"
49
+ },
50
+ {
51
+ "t": 157.318,
52
+ "text": "呃呃。"
53
+ },
54
+ {
55
+ "t": 464.697,
56
+ "text": "感觉是缅怀父亲"
57
+ },
58
+ {
59
+ "t": 278.88,
60
+ "text": "对的"
61
+ },
62
+ {
63
+ "t": 103.536,
64
+ "text": "有人在嘛"
65
+ },
66
+ {
67
+ "t": 68.412,
68
+ "text": "2月6日晚在嘛"
69
+ },
70
+ {
71
+ "t": 48.639,
72
+ "text": "嗨"
73
+ },
74
+ {
75
+ "t": 780.291,
76
+ "text": "萤火虫又来了"
77
+ },
78
+ {
79
+ "t": 642.594,
80
+ "text": "好像我老家的房子啊,这种感觉很熟悉"
81
+ },
82
+ {
83
+ "t": 240.207,
84
+ "text": "是思亲吧"
85
+ },
86
+ {
87
+ "t": 240.207,
88
+ "text": "前面的小孩长大了,来到多年前和父亲晚上出来的那个地方,放了当时在父亲手中划过的萤火虫"
89
+ },
90
+ {
91
+ "t": 175.257,
92
+ "text": "恐怖来源于自己的内心和想象吧"
93
+ },
94
+ {
95
+ "t": 55.832,
96
+ "text": "我爱刘有生"
97
+ },
98
+ {
99
+ "t": 521.146,
100
+ "text": "木雕定格吧"
101
+ },
102
+ {
103
+ "t": 273.184,
104
+ "text": "这是定格吧"
105
+ },
106
+ {
107
+ "t": 77.34,
108
+ "text": "你哥"
109
+ },
110
+ {
111
+ "t": 103.44,
112
+ "text": "那样我就不孤独了"
113
+ },
114
+ {
115
+ "t": 93.595,
116
+ "text": "睡不着"
117
+ },
118
+ {
119
+ "t": 76.319,
120
+ "text": "是我呀!"
121
+ },
122
+ {
123
+ "t": 191.003,
124
+ "text": "角色形象和打光确实有点阴森"
125
+ },
126
+ {
127
+ "t": 84.455,
128
+ "text": "有人吗?"
129
+ },
130
+ {
131
+ "t": 94.372,
132
+ "text": "我也是。"
133
+ },
134
+ {
135
+ "t": 247.781,
136
+ "text": "好漂亮"
137
+ },
138
+ {
139
+ "t": 288.178,
140
+ "text": "我在"
141
+ },
142
+ {
143
+ "t": 243.262,
144
+ "text": "有点点忧伤"
145
+ },
146
+ {
147
+ "t": 101.566,
148
+ "text": "还有我"
149
+ },
150
+ {
151
+ "t": 47.764,
152
+ "text": "。"
153
+ },
154
+ {
155
+ "t": 565.465,
156
+ "text": "子欲养而亲不待,孤独的人渴望幸福"
157
+ },
158
+ {
159
+ "t": 305.283,
160
+ "text": "实在有点像蚯蚓的头"
161
+ },
162
+ {
163
+ "t": 173.543,
164
+ "text": "这建模建的有点奇怪"
165
+ },
166
+ {
167
+ "t": 664.779,
168
+ "text": "回忆 过去 现实"
169
+ },
170
+ {
171
+ "t": 159.025,
172
+ "text": "为什么我感觉好恐怖。。。"
173
+ },
174
+ {
175
+ "t": 570.58,
176
+ "text": "有一点点淡淡的压抑"
177
+ },
178
+ {
179
+ "t": 393.624,
180
+ "text": "什么意思呢"
181
+ },
182
+ {
183
+ "t": 368.993,
184
+ "text": "他爸好吧"
185
+ },
186
+ {
187
+ "t": 362.886,
188
+ "text": "?"
189
+ },
190
+ {
191
+ "t": 64.459,
192
+ "text": "在这纳"
193
+ },
194
+ {
195
+ "t": 816.318,
196
+ "text": "明月有情应照我"
197
+ },
198
+ {
199
+ "t": 122.6,
200
+ "text": "他在干吗??"
201
+ },
202
+ {
203
+ "t": 69.042,
204
+ "text": "有"
205
+ },
206
+ {
207
+ "t": 56.9,
208
+ "text": "有人吗"
209
+ },
210
+ {
211
+ "t": 174.217,
212
+ "text": "我还是更喜欢那个导盲犬的动画"
213
+ },
214
+ {
215
+ "t": 80.316,
216
+ "text": "是我"
217
+ },
218
+ {
219
+ "t": 108.073,
220
+ "text": "mc?"
221
+ },
222
+ {
223
+ "t": 6.398,
224
+ "text": "尼玛"
225
+ },
226
+ {
227
+ "t": 55.799,
228
+ "text": "前面的我在这里"
229
+ },
230
+ {
231
+ "t": 81.559,
232
+ "text": "自己人"
233
+ },
234
+ {
235
+ "t": 809.262,
236
+ "text": "空巢老人了"
237
+ },
238
+ {
239
+ "t": 718.602,
240
+ "text": "他没有这种记忆"
241
+ },
242
+ {
243
+ "t": 456.782,
244
+ "text": "日月如梭"
245
+ },
246
+ {
247
+ "t": 105.762,
248
+ "text": "他美好的记忆几乎没有吧"
249
+ },
250
+ {
251
+ "t": 835.988,
252
+ "text": "谁能想到这是用♂做的动漫"
253
+ },
254
+ {
255
+ "t": 792.974,
256
+ "text": "扭曲的窗子"
257
+ },
258
+ {
259
+ "t": 744.434,
260
+ "text": "没做什么大错,却要很多磨难的人有很多"
261
+ },
262
+ {
263
+ "t": 681.814,
264
+ "text": "果然还是一个人呆着是最舒适的"
265
+ },
266
+ {
267
+ "t": 653.214,
268
+ "text": "阿凡提"
269
+ },
270
+ {
271
+ "t": 522.294,
272
+ "text": "阿凡提风格"
273
+ },
274
+ {
275
+ "t": 473.894,
276
+ "text": "书包?"
277
+ },
278
+ {
279
+ "t": 365.242,
280
+ "text": "不是伴侣,是他爸"
281
+ },
282
+ {
283
+ "t": 284.302,
284
+ "text": "根据我上早读,同桌捣乱,多嘈杂也能静下来的经验,可能会有些干扰,不过应该也差不多"
285
+ },
286
+ {
287
+ "t": 572.83,
288
+ "text": "小鸟陪着他呢"
289
+ },
290
+ {
291
+ "t": 507.97,
292
+ "text": "变成鸟飞走了"
293
+ },
294
+ {
295
+ "t": 70.215,
296
+ "text": "黑的只能看到我自己"
297
+ },
298
+ {
299
+ "t": 374.03,
300
+ "text": "老爹的老爹"
301
+ },
302
+ {
303
+ "t": 792.679,
304
+ "text": "一边看着外边的风景,一边警示着外边"
305
+ },
306
+ {
307
+ "t": 696.819,
308
+ "text": "大鼻子啊"
309
+ },
310
+ {
311
+ "t": 649.559,
312
+ "text": "阿梵达风格既视感"
313
+ },
314
+ {
315
+ "t": 2.153,
316
+ "text": "利马=nm"
317
+ },
318
+ {
319
+ "t": 75.899,
320
+ "text": "这里"
321
+ },
322
+ {
323
+ "t": 73.379,
324
+ "text": "我"
325
+ },
326
+ {
327
+ "t": 74.838,
328
+ "text": "是谁"
329
+ },
330
+ {
331
+ "t": 73.754,
332
+ "text": "我。。。"
333
+ },
334
+ {
335
+ "t": 51.781,
336
+ "text": "前面wei的,我在这儿"
337
+ },
338
+ {
339
+ "t": 84.434,
340
+ "text": "你不是一个人"
341
+ },
342
+ {
343
+ "t": 7.923,
344
+ "text": "nm"
345
+ },
346
+ {
347
+ "t": 120.659,
348
+ "text": "讲大青蛙的吗?"
349
+ },
350
+ {
351
+ "t": 249.779,
352
+ "text": "场景很美"
353
+ },
354
+ {
355
+ "t": 77.429,
356
+ "text": "我"
357
+ },
358
+ {
359
+ "t": 240.26,
360
+ "text": "他刚刚在怀念小时候跟爸爸"
361
+ },
362
+ {
363
+ "t": 560.322,
364
+ "text": "好孤单"
365
+ },
366
+ {
367
+ "t": 245.322,
368
+ "text": "耐心看完"
369
+ },
370
+ {
371
+ "t": 209.042,
372
+ "text": "眼睛"
373
+ },
374
+ {
375
+ "t": 251.459,
376
+ "text": "就是想他爹了吧"
377
+ },
378
+ {
379
+ "t": 81.485,
380
+ "text": "我"
381
+ },
382
+ {
383
+ "t": 76.739,
384
+ "text": "鬼和你一起看"
385
+ },
386
+ {
387
+ "t": 818.682,
388
+ "text": "蜡烛熄了,他也去了另一个世界"
389
+ },
390
+ {
391
+ "t": 79.202,
392
+ "text": "我"
393
+ },
394
+ {
395
+ "t": 100.059,
396
+ "text": "还有我"
397
+ },
398
+ {
399
+ "t": 235.796,
400
+ "text": "所以有没人来告诉我它讲得啥"
401
+ },
402
+ {
403
+ "t": 638.061,
404
+ "text": "我瞎了.非要搞得阴暗神秘"
405
+ },
406
+ {
407
+ "t": 77.078,
408
+ "text": "我"
409
+ },
410
+ {
411
+ "t": 82.997,
412
+ "text": "还有我"
413
+ },
414
+ {
415
+ "t": 74.387,
416
+ "text": "我"
417
+ },
418
+ {
419
+ "t": 74.019,
420
+ "text": "我"
421
+ },
422
+ {
423
+ "t": 43.339,
424
+ "text": "wei'"
425
+ },
426
+ {
427
+ "t": 675.739,
428
+ "text": "无助"
429
+ },
430
+ {
431
+ "t": 646.316,
432
+ "text": "孤独 寂寞 冷"
433
+ },
434
+ {
435
+ "t": 567.623,
436
+ "text": "他也老了"
437
+ },
438
+ {
439
+ "t": 359.29,
440
+ "text": "逝去的伴侣"
441
+ },
442
+ {
443
+ "t": 828.642,
444
+ "text": "4"
445
+ },
446
+ {
447
+ "t": 77.511,
448
+ "text": "不止一个"
449
+ },
450
+ {
451
+ "t": 450.247,
452
+ "text": "距离以一颗观月的心"
453
+ },
454
+ {
455
+ "t": 69.45,
456
+ "text": "是谁和我一起看呢。"
457
+ },
458
+ {
459
+ "t": 423.437,
460
+ "text": "你好"
461
+ },
462
+ {
463
+ "t": 438.201,
464
+ "text": "思念是一条长河"
465
+ },
466
+ {
467
+ "t": 514.953,
468
+ "text": "这不是木雕动画吗"
469
+ },
470
+ {
471
+ "t": 423.853,
472
+ "text": "迎客松~"
473
+ },
474
+ {
475
+ "t": 263.437,
476
+ "text": "pump stars"
477
+ },
478
+ {
479
+ "t": 802.209,
480
+ "text": "哇"
481
+ },
482
+ {
483
+ "t": 597.169,
484
+ "text": "小心啊"
485
+ },
486
+ {
487
+ "t": 101.369,
488
+ "text": "da打开就看不到星空啦"
489
+ },
490
+ {
491
+ "t": 495.651,
492
+ "text": "哈哈哈这只鸟还会眨眼睛"
493
+ },
494
+ {
495
+ "t": 189.205,
496
+ "text": "白蚁?"
497
+ },
498
+ {
499
+ "t": 698.899,
500
+ "text": "哈喽5"
501
+ },
502
+ {
503
+ "t": 409.939,
504
+ "text": "哈喽,10"
505
+ },
506
+ {
507
+ "t": 344.56,
508
+ "text": "去月球"
509
+ },
510
+ {
511
+ "t": 166.497,
512
+ "text": "塌鼻格魯"
513
+ },
514
+ {
515
+ "t": 98.354,
516
+ "text": "好想把灯打开"
517
+ }
518
+ ],
519
+ "danmakuCount": 127,
520
+ "groundTruthNote": "2026-11-14 人工确认:04:50-05:50 / 08:50-10:40 / 11:40-13:00 三段均非广告(艺术短片,无赞助),修复稀疏弹幕误报后应检测为 0 段"
521
+ }