dsh-bili-watch 0.2.26 → 0.2.28

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/index.js CHANGED
@@ -433,7 +433,11 @@ function pickFolderDialog() {
433
433
  * 缓存自动清理:先按天数删过期文件,再按总量从最旧删到阈值内。
434
434
  * 返回被删统计,供面板显示。
435
435
  */
436
- function autoCleanCache() {
436
+ let lastCleanAt = 0
437
+
438
+ function autoCleanCache(opts = {}) {
439
+ // T31-P2-6:面板每 8 秒轮询 /status,不许每次都全量 walk 缓存目录
440
+ if (!opts.force && Date.now() - lastCleanAt < 10 * 60 * 1000) return { removed: 0, bytes: 0, skipped: true }
437
441
  if (!existsSync(CACHE)) return { removed: 0, bytes: 0 }
438
442
  const { days, maxMb } = cacheCfg()
439
443
  const cutoff = Date.now() - days * 86400000
@@ -466,6 +470,7 @@ function autoCleanCache() {
466
470
  if (total <= limit) break
467
471
  }
468
472
  }
473
+ lastCleanAt = Date.now()
469
474
  return { removed, bytes }
470
475
  }
471
476
 
@@ -756,7 +761,7 @@ async function lookup({ command, bv, extra = [], badOut = false }) {
756
761
  function apply(ctx) {
757
762
  // 启动时按「天数 + 总量阈值」清一次缓存,不点按钮也不会无限涨
758
763
  try {
759
- const r = autoCleanCache()
764
+ const r = autoCleanCache({ force: true })
760
765
  if (r.removed) ctx.logger?.info?.(`[dsh-bili-watch] 自动清理缓存 ${r.removed} 个文件 / ${(r.bytes / 1048576).toFixed(1)} MB`)
761
766
  } catch {}
762
767
 
@@ -780,6 +785,7 @@ function apply(ctx) {
780
785
  try {
781
786
  if (createUserMessage && uiCfg().autoHint !== false) {
782
787
  const hinted = new Map() // message.id → true,同一条消息只提示一次
788
+ const HINT_CAP = 500 // T31-P2-5:常驻 Map 不能无限涨,满了整体清空(只影响重复提示)
783
789
  const hint = (msg) => {
784
790
  const { ok, text } = textOfUserMessage(msg)
785
791
  if (!ok) return null
@@ -806,6 +812,7 @@ function apply(ctx) {
806
812
  const extra = hint(msg)
807
813
  if (!extra) continue
808
814
  hinted.set(msg.id, true)
815
+ if (hinted.size > HINT_CAP) hinted.clear()
809
816
  fresh.push(extra)
810
817
  }
811
818
  if (!fresh.length) return decision
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-bili-watch",
3
- "version": "0.2.26",
3
+ "version": "0.2.28",
4
4
  "private": false,
5
5
  "description": "把 B 站视频读成文本给 DSH agent 用:元数据、评论、弹幕、字幕、本地转写、抽帧 OCR;支持 b23.tv 短链与 av 号,抽帧缓存目录可换。只读命令零依赖。",
6
6
  "type": "module",
package/tools/look.mjs CHANGED
@@ -93,7 +93,17 @@ const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
93
93
  const jitter = (base, spread) => base + Math.floor(Math.random() * spread)
94
94
 
95
95
  async function jget(url, headers = H, timeoutMs = 30000) {
96
- const r = await fetch(url, { headers, signal: AbortSignal.timeout(timeoutMs) })
96
+ // T36-P1-2:只对网络层抖动重试一次(B 站偶发 ECONNRESET);业务 code(-352/-404)不重试
97
+ let r
98
+ for (let attempt = 0; ; attempt++) {
99
+ try {
100
+ r = await fetch(url, { headers, signal: AbortSignal.timeout(timeoutMs) })
101
+ if (!(r.status >= 500) || attempt >= 1) break
102
+ } catch (e) {
103
+ if (attempt >= 1) throw e
104
+ }
105
+ await sleep(300 + Math.floor(Math.random() * 400))
106
+ }
97
107
  const t = await r.text()
98
108
  if (!r.ok) return { code: -r.status, message: `HTTP ${r.status}` }
99
109
  try {
@@ -1366,6 +1376,18 @@ async function cmdSheet(bv, opts = {}) {
1366
1376
  mkdirSync(dirname(out), { recursive: true })
1367
1377
  execFileSync(bin, ['-y', '-threads', '2', '-i', join(tmp, '%02d.jpg'), '-vf', `scale=640:-1,tile=${cols}x${rows}`, '-frames:v', '1', out], { stdio: 'ignore', timeout: 300000 })
1368
1378
  console.log(`拼图: ${out}(${(statSync(out).size / 1024).toFixed(0)} KiB,共 ${picked.length} 帧)`)
1379
+ // T42:九宫格没有时间轴,模型看图不知道「第几格是第几秒」;写一份 manifest 并打一行
1380
+ const stepUsed = Number((/-([0-9]+)s$/.exec(r.dir) || [])[1] || opts.step || 0)
1381
+ const cellSec = (f, i) => {
1382
+ const m = /-([0-9]+)\.jpg$/.exec(f)
1383
+ const n = m ? Number(m[1]) : i + 1
1384
+ return stepUsed > 0 ? Math.max(0, (n - 1) * stepUsed) : null
1385
+ }
1386
+ const cells = picked.map((f, i) => { const sec = cellSec(f, i); return { cell: i + 1, file: basename(f), sec, mmss: sec == null ? '?' : fmtSec(sec) } })
1387
+ try {
1388
+ writeFileSync(out.replace(/\.jpg$/, '.manifest.json'), JSON.stringify({ bv, grid, step: stepUsed || null, cells }, null, 2) + '\n', 'utf8')
1389
+ } catch {}
1390
+ console.log('每格时间:' + cells.map((x) => `#${x.cell}=${x.mmss}`).join(' '))
1369
1391
  console.log('读这张图就能过一遍画面;要精确读字用 ocr <BV>。')
1370
1392
  }
1371
1393
 
@@ -1781,7 +1803,7 @@ async function cmdText(bv, credPath, opts = {}) {
1781
1803
  // 口播类正常 4–6 字/秒;低于 2 字/秒多半是背景音乐、外语歌或纯画面,
1782
1804
  // 这时候转写出来的「字」只是把杂音听成了字,继续往下走 OCR 更靠谱。
1783
1805
  const rate = v.dur > 0 ? a.chars / v.dur : 0
1784
- if (rate >= 2) {
1806
+ if (rate >= 2 && !(a.verify && a.verify.suspect)) {
1785
1807
  // T40:ASR 也会把杂音/背景音听成字,主入口同样要过诱饵信号(原先只有字幕分支有)
1786
1808
  const asrBait = baitSignals(a.text, v, credibility(v, a.text))
1787
1809
  if (asrBait.suspect) {
@@ -1800,6 +1822,8 @@ async function cmdText(bv, credPath, opts = {}) {
1800
1822
  printText(a.text, opts, bv)
1801
1823
  done()
1802
1824
  return
1825
+ } else if (a.verify && a.verify.suspect) {
1826
+ say(`[track] 转写: ⚠ 疑似幻觉(${(a.verify.reasons || []).join(';')}),降级到抽帧/OCR`)
1803
1827
  }
1804
1828
  say(`[track] 转写: 质量低(${a.chars} 字,${rate.toFixed(1)} 字/秒),继续往下试`)
1805
1829
  } else {