给模型看 B 站视频的 DSH 插件:字幕、转写、抽帧 OCR、评论与弹幕;支持 b23.tv 短链,抽帧缓存目录可换。
36.7/100
综合分
6
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/31 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-bili-watchnpm 源 · dsh-bili-watch 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · platform
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 1 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-bili-watch @ 0.3.8
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖要求 >=0.2.0-rc.2 <0.3.0-0 · 最新 0.2.1-rc.5 兼容
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/30 21:12:11
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 358 处,展示前 60 条)
- 写入本地文件index.js:60
mkdirSync(CACHE, { recursive: true }) - 读取本地文件index.js:63
for (const d of readdirSync(CACHE)) { - 写入本地文件index.js:68
mkdirSync(d, { recursive: true }) - 写入本地文件index.js:102
writeFileSync(f, s, 'utf8') - 读取本地文件index.js:163
const st = statSync(src) - 写入本地文件index.js:165
mkdirSync(dst, { recursive: true }) - 读取本地文件index.js:166
for (const e of readdirSync(src, { withFileTypes: true })) copyTreeSync(join(src, e.name), join(dst, e.name)) - 写入本地文件index.js:168
copyFileSync(src, dst) - 读取本地文件index.js:181
if (existsSync(DEPS_MOVED_MARK)) return DEPS_PREF - 读取本地文件index.js:183
if (existsSync(DEPS_PREF)) return DEPS_PREF - 读取本地文件index.js:185
try { srcHas = existsSync(DEPS_LOCAL) && readdirSync(DEPS_LOCAL).length > 0 } catch {} - 写入本地文件index.js:186
if (!srcHas) { try { mkdirSync(DEPS_PREF, { recursive: true }) } catch {} ; return DEPS_PREF } - 删除本地文件递归index.js:201
try { rmSync(tmp, { recursive: true, force: true }) } catch {} - 删除本地文件递归index.js:203
try { rmSync(DEPS_PREF, { recursive: true, force: true }) } catch {} - 删除本地文件递归index.js:208
try { rmSync(tmp, { recursive: true, force: true }) } catch {} - 删除本地文件递归index.js:537
try { rmSync(p, { recursive: true, force: true }); files += st.files; bytes += st.bytes } catch (err) { failed.push({ name: e.name, why: String(err?.m - 删除本地文件index.js:539
try { const st = lstatSync(p); rmSync(p, { force: true }); files += 1; bytes += st.size } catch (err) { failed.push({ name: e.name, why: String(err?.m - 删除本地文件index.js:544
try { const st = lstatSync(p); rmSync(p, { force: true }); files += 1; bytes += st.size } catch (err) { failed.push({ name: e.name, why: String(err?.m - 执行外部命令来自配置/变量index.js:707
execFileSync(c, ['-version'], { stdio: 'ignore', timeout: 1200, windowsHide: true }) - 执行外部命令固定命令index.js:839
execFileSync('taskkill', ['/PID', String(c.pid), '/T', '/F'], { windowsHide: true }) - 执行外部命令来自配置/变量index.js:864
child = spawn(process.execPath, [LOOK, ...args], { - 发起网络请求index.js:928
const r = await fetch(SPI, { headers: { 'User-Agent': UA }, signal: AbortSignal.timeout(8000) }) - 发起网络请求index.js:940
const r = await fetch(QR_GEN, { headers: { 'User-Agent': UA }, signal: AbortSignal.timeout(8000) }) - 发起网络请求index.js:960
const res = await fetch(QR_POLL(key), { headers: { 'User-Agent': UA }, redirect: 'manual', signal: AbortSignal.timeout(8000) }) - 执行外部命令固定命令index.js:1162
const ps = spawn('powershell.exe', ['-NoProfile', '-STA', '-Command', script], { windowsHide: false }) - 执行外部命令来自配置/变量index.js:1295
ver = String(execFileSync(ytdlp, ['--version'], { encoding: 'utf8', timeout: 5000, windowsHide: true }) || '').trim().split(/ +/)[0] - 执行外部命令来自配置/变量index.js:1358
execFile(file, args, opts, (err, stdout) => resolveP({ ok: !err, stdout: String(stdout ?? ''), err })) - 发起网络请求client.js:89
r = await fetch(API + p, { credentials: 'same-origin', signal: guard.signal, ...init }) - 读取本地文件tools/look.mjs:51
if (existsSync(pref)) return pref - 读取本地文件tools/look.mjs:52
return existsSync(local) ? local : pref - 读取本地文件tools/look.mjs:62
raw = readFileSync(p, 'utf8') - 写入本地文件tools/look.mjs:74
try { renameSync(p, bak); note = `, backed up as ${basename(bak)}` } catch { note = ', backup failed' } - 读取本地文件tools/look.mjs:81
const pick = (...cands) => cands.find((p) => existsSync(p)) ?? cands[cands.length - 1] - 读取本地文件tools/look.mjs:102
for (const e of readdirSync(dir, { withFileTypes: true })) { - 读取本地文件tools/look.mjs:112
else { try { bytes += lstatSync(p).size } catch {}; files += 1 } - 删除本地文件递归tools/look.mjs:191
try { rmSync(p, { recursive: true, force: true }); files += st.files; bytes += st.bytes } catch (err) { failed.push({ name: e.name, why: String(err?.m - 删除本地文件tools/look.mjs:193
try { const st = lstatSync(p); rmSync(p, { force: true }); files += 1; bytes += st.size } catch (err) { failed.push({ name: e.name, why: String(err?.m - 删除本地文件tools/look.mjs:198
try { const st = lstatSync(p); rmSync(p, { force: true }); files += 1; bytes += st.size } catch (err) { failed.push({ name: e.name, why: String(err?.m - 写入本地文件tools/look.mjs:246
mkdirSync(want, { recursive: true }) - 写入本地文件tools/look.mjs:249
writeFileSync(mark, JSON.stringify({ version: 1, createdAt: new Date().toISOString() }), 'utf8') - 写入本地文件tools/look.mjs:255
writeFileSync(probe, 'ok') - 删除本地文件tools/look.mjs:256
rmSync(probe, { force: true }) - 写入本地文件tools/look.mjs:262
try { mkdirSync(defaultDir, { recursive: true }) } catch {} - 写入本地文件tools/look.mjs:267
if (!existsSync(m)) writeFileSync(m, JSON.stringify({ version: 1, createdAt: new Date().toISOString(), fallback: true }), 'utf8') - 发起网络请求tools/look.mjs:457
r = await fetch(url, { headers, signal: AbortSignal.timeout(timeoutMs) }) - 发起网络请求tools/look.mjs:798
r = await fetch(url, { headers: { ...H, Referer: REF }, signal: AbortSignal.timeout(120000) }) - 删除本地文件tools/look.mjs:899
unlinkSync(f) - 删除本地文件tools/look.mjs:912
unlinkSync(f) - 执行外部命令来自配置/变量tools/look.mjs:975
execFileSync(c, ['-version'], { stdio: 'ignore', timeout: 300000 }) - 执行外部命令来自配置/变量tools/look.mjs:1002
return execFileSync(bin, args, opts) - 执行外部命令来自配置/变量tools/look.mjs:1696
const r2 = spawnSync(ASR_PY, [ASR_SCRIPT, wav, '-o', cacheTxtTmp, '--model', model, '--device', tuning().asrDevice, '--json'], { - 执行外部命令来自配置/变量tools/look.mjs:1790
const out = execFileSync(OCR_PY, args, { - 请求外部主机comment.bilibili.comtools/look.mjs:2163
const r = await fetch(`https://comment.bilibili.com/${cid}.xml`, { - 发起网络请求tools/look.mjs:2163
const r = await fetch(`https://comment.bilibili.com/${cid}.xml`, { - 发起网络请求tools/look.mjs:2643
const r = await fetch(SPI, { headers: { 'User-Agent': UA }, signal: AbortSignal.timeout(15000) }) - 发起网络请求tools/look.mjs:2652
const res = await fetch(qrPoll(key), { headers: { 'User-Agent': UA }, redirect: 'manual', signal: AbortSignal.timeout(15000) }) - 发起网络请求tools/look.mjs:2737
const r = await fetch(QR_GEN, { headers: { 'User-Agent': UA }, signal: AbortSignal.timeout(15000) }) - 执行外部命令来自配置/变量tools/look.mjs:3286
const out = execFileSync(ffprobe, ['-v', 'error', '-show_entries', 'format=duration', '-of', 'csv=p=0', file], { - 请求外部主机api.bilibili.comtools/look.mjs:3641
const r = await fetch(`https://api.bilibili.com/x/web-interface/view?aid=${encodeURIComponent(aid)}`, { - 执行外部命令来自配置/变量tools/look.mjs:3740
execFileSync(c, ['--version'], { stdio: 'ignore', timeout: 300000 })
扫描于 2026/10/31(1 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
涉及外部主机:comment.bilibili.com、api.bilibili.com
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh@deepseek-ai/dsh-tools@deepseek-ai/dsh-llm给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
最近更新
在 GitHub 查看全部 →v0.3.92026-10-31
· 新增
· text / sub 输出带逐句时间码:纯文本每行前面多了 [mm:ss],可以直接定位到某一刻;不想要时间码加 --no-time,退回和以前一样的纯文本。
· peek 不再每次下视频:先取 B 站官方截帧接口,拿到就直接用;拿不到才回落下载 360p 抽帧。多数情况下同样是 5 帧,省掉一次下载。
· frames --at 精看某一刻或某一段:--at 750(秒)、--at 12:30(分:秒)、--at 12:30-13:10(区间,默认每 5 秒一张)。
· 抽帧索引格式统一:现在是 r1c1 00:00-00:05 @00:02 这种写法,图上的编号和索引里的编号是同一套,不用再自己数。
· 修复
· --help 会真的执行那个命令。之前 clean --help 想看一眼用法,结果直接把缓存清了。现在带 --help 只打印帮助,任何命令都不执行。
· frames --at 换一个时刻,仍然返回上一次的帧。之前不同时刻会落到同一份缓存,退出码 0、输出看着正常、给的是错的那批图。
· peek 拼版图上的时间码是估算的(按百分比推算),和帧的实际时刻对不上,越靠后偏得越多;另外最后一帧会取到拼版图的黑色空格。现在用接口给的真实时刻,格号也对齐了。
· peek 遇到非方形的拼版图(不是 10×10)会把行列算错,取到相邻格的图。
· GPU 探测失败时只说「运行库未装」,实际可能是探测超时;判错之后还会把这个结论缓存 5 分钟,装好了也不会自己恢复。现在会报具体的失败原因,不确定的结论不再写进缓存。
· 文档
· --at 只对 frames 生效(之前写成了 frames / sheet,sheet 实际不认这个参数)。
· --head 与 --out 按实际行为改写:不传 --head 是按 800 字 / 400 字的规则自动截断,不是输出全文(要全文用 --full);--out 只在部分命令上有效,sheet / ocr / substrip 固定写缓存目录。
· 补一句缓存目录的归属:clean 和面板的「立即清除」是按名字整棵删 _logs frames sheets sheet-tmp substrip-tmp _misc asr frames-dl audio-dl _hot 这 10 个子目录,里面放什么都不会保留。别把自己的文件放进这些名字的目录。放在缓存根根目录下、名字不在上表里的文件不会被删。
· 另外改正了几处与代码对不上的说法:deps/ 在 $DSH_HOME/bili-watch/deps 而不是跟着卸载删、运行时参数是 18 项不是 17 项、BILI_ASR_DEVICE 环境变量不生效(要改配置里的 asrDevice)。
v0.3.82026-10-30
· 凭证只认自己那份
删掉了「跟随同机其它工具」整套逻辑:原来 watch 会去读同机某个业务工具的凭证文件,
谁的文件更新就用谁的,还会把其中一个小号复制回来。对没装那个工具的人是死分支,
对装了的人则是一个隐形的共享登录态——面板里删了凭证,下次仍然有登录。
现在只认 $DSH_HOME/bili-watch/credential.json(旧位置的凭证会一次性迁移过来)。
面板与 CLI 两侧同一套判据。
· 修掉的实质问题
· ASR 一跑就崩:asr 命令没有内存闸门,机器剩 0.8 GB 时直接进 faster-whisper,
撞 mkl_malloc 挂掉,崩之前还承诺「3 分钟片约 30–45 秒」。现在先算时长再决定跑不跑,
内存不够时给替代方案(调 --asr-max 或关掉转写)。
· --out 能写进系统目录:--out C:/Windows/Temp/x.json 会被自己的错误处理器绕开黑名单,
拒绝原因照样写进那个文件。现在被拒就退回 stdout,目标位置一个字节都不落。
· 整条链接当 BV 用:CLI 传 时,缓存目录会建成
httpswwwwbilibilicomvideoBV...,连自己都认不出来。现在两处都先抽 BV。
· clean 清不掉自己的垃圾:login-qr.png 和「建了但没写 meta 的空 BV 目录」永远留在缓存里,
每次都进「不是 bili-watch 的缓存条目,不动」。
· 删凭证只说成功:面板删了凭证,实际上业务侧那份还在被用,界面只报「已删除」。
· 其它
· 批量下载失败时能看到真实原因(原来只显示「后端未受理」)。
· /status 的缓存占用加了 30 秒节流,8 秒轮询不再反复全盘 walk。
· 坏掉的配置文件先留档 .broken- 再回默认值,不再静默擦掉用户设置。
· 自动清理与手点清理用同一套「这是我的目录」判据:只认带 meta.json 的 BV 目录,
你在缓存根里自己建的 BV... 目录不会被误删。
---
感谢 @ppppiii 在 #4 里找到 Windows 一键下载从来没真正执行过
(spawn 的 detached: true 会让 powershell 起来就退出、脚本一行不跑,日志还写「成功」)——
这是这一版所有「报成功但没做」类修复的起点。
v0.3.72026-10-30
· 这一版主要是把外部贡献合进来
感谢 @ppppiii 的 PR #4——他复现并修掉了一个从 0.2.x 就存在、我们所有代码审查都没发现的根因。
P0:Windows「一键下载」从来没真正执行过
· index.js 的 runDownload() 用 spawn(..., { detached: true }) 启 powershell。Windows 上 libuv 把 detached 映射成 DETACHED_PROCESS,powershell 起来约 0.5 秒就以 exit code 0 退出,-File 指定的脚本一行都不执行,stdout/stderr 全空
· 更糟的是 0.3.5/0.3.6 把子进程 stdio 重定向进日志并监听 exit,于是日志会写「结束,退出码 0(成功)」——看起来成功了,其实什么都没跑
· 修法:Windows 上不再 detached(Windows 本来就不会因父进程退出杀子进程);子进程输出改写到 deps\download.out.log,deps\download.log 只留宿主侧结构化事件(开始 / pid / detached / shell / 退出码)
P0:开着系统代理的机器,脚本能跑了也会失败
· curl.exe / pip / huggingface_hub 都不读 Windows「Internet 选项」里的系统代理,报 curl: (52) Empty reply from server
· 修法:新增代理探测链 -ProxyUrl > BILI_WATCH_PROXY > HTTPS_PROXY / HTTP_PROXY > 注册表 WinINet,并给 pip / huggingface_hub / uv 设好对应环境变量
Python 选择链重做
· 不再尝试 3.14:onnxruntime 目前没有 cp314 wheel,装了也跑不了 OCR
· 顺序改为 -PythonPath > uv 3.12/3.13 > py 3.13/3.12/3.11/3.10 > PATH
· uv venv 默认不装 pip(venv 里 python -m pip 不可用),失败时自动回退系统 Python
发布脚本的版本闸门(本版自查发现)
· package.json 不在 files 数组里(npm 自动打包它),所以发布闸的 md5 比对永远比不到它——只改 dev 那份版本号不会触发同步,发布树仍是旧版,npm publish 报 cannot publish over the previously published versions
· 修法:单独比 version 字段(不能进 md5 清单,sync 会用 ConvertTo-Json 重排格式),并给 sync-release.ps1 显式传 -Version
· 验证
· 作者侧:控制变量(detached true/false)、PS5.1 与 pwsh7 两种 shell、真跑 ytdlp/ocr/asr 三个目标、失败路径(curl 52)、rebase 到 0.3.6 后重跑全过
· 本机侧:隔离目录 -Target ytdlp -Force 真跑,download.log 写出完整链路(脚本确实执行了);-Mirror cn 下载成功(yt-dlp.exe 23,575,744 字节)
· 已知未修
· -Mirror auto 只探 huggingface.co 的 443,不探 github.com:huggingface 通、github 不通的机器会被判成直连而超时
· 0.3.6 留的六条小项(isOwnBvDir 零覆盖、面板报 skipped、USAGE_ONLY 漏参数错误、两处 dirStat 链接口径、探针路径假阴性、改缓存位置后旧提示最多晚 10 分钟消失)
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。
README
由 DeepSeek 最新模型翻译生成dsh-bili-watch
DSH 插件 / 命令行工具:把 B 站视频解析成模型能直接消费的结构化内容(元数据、评论、弹幕、AI 字幕、本地 ASR 转写、抽帧 OCR)。
为什么用它
- 本地跑不上传:ASR 转写 + OCR 抽帧全在本机,不依赖云 API、不花钱
- 防字幕诱饵:语速异常或字幕自称「实验/钓鱼」即判可疑;关键词命中率只在语速也异常时才作补充;可疑时加 --verify 抽 5 帧 OCR 比对画面(不加只提示一句)
- 二次读瞬时:缓存目录 + 文件锁,同一视频二次读取秒出
- 为 AI 设计:manifest 落盘 + 摘要输出,智能体直接消费不用再解析
特性
- 支持 b23.tv 短链、BV 号、av 号及通用 URL(经 yt-dlp)
- 降级链:AI 字幕 → 本地 ASR → 抽帧 OCR → 读图,四级全 miss 时退出码为 1
- 匿名可用:元数据、评论、弹幕、搜索、热榜无需登录
- 可选依赖:ffmpeg(抽帧)、Python venv(ASR/OCR)、faster-whisper(转写模型)
- Web 面板:依赖管理、扫码登录、缓存清理、18 项运行时参数可调
安装
DSH 插件
dsh plugin --profile web add dsh-bili-watch
重启 DSH 后生效。
展开完整文档(README 剩余 1 节)
dsh-bili-watch
DSH 插件 / 命令行工具:把 B 站视频解析成模型能直接消费的结构化内容(元数据、评论、弹幕、AI 字幕、本地 ASR 转写、抽帧 OCR)。
为什么用它
- 本地跑不上传:ASR 转写 + OCR 抽帧全在本机,不依赖云 API、不花钱
- 防字幕诱饵:语速异常或字幕自称「实验/钓鱼」即判可疑;关键词命中率只在语速也异常时才作补充;可疑时加 --verify 抽 5 帧 OCR 比对画面(不加只提示一句)
- 二次读瞬时:缓存目录 + 文件锁,同一视频二次读取秒出
- 为 AI 设计:manifest 落盘 + 摘要输出,智能体直接消费不用再解析
特性
- 支持 b23.tv 短链、BV 号、av 号及通用 URL(经 yt-dlp)
- 降级链:AI 字幕 → 本地 ASR → 抽帧 OCR → 读图,四级全 miss 时退出码为 1
- 匿名可用:元数据、评论、弹幕、搜索、热榜无需登录
- 可选依赖:ffmpeg(抽帧)、Python venv(ASR/OCR)、faster-whisper(转写模型)
- Web 面板:依赖管理、扫码登录、缓存清理、18 项运行时参数可调
安装
DSH 插件
dsh plugin --profile web add dsh-bili-watch
重启 DSH 后生效。DSH ≥ 0.2.0-rc.2 自动写入 dsh.profile.bundles;更早版本需手动在 profile package.json 的 dsh.profile.bundles 数组追加 "dsh-bili-watch"。
装完重启即可用。 本插件的 bundle patch 不带 disabled,装进 profile 的 dsh.profile.bundles 后重启 DSH 就生效,命令直接存在。
0.3.1 及更早的版本不是这样:包在装的那一刻就被写进 dsh.profile.bundles(reconcile() 干的),所以插件页显示「已启用」,但实际不加载——因为层里那行 disabled: true 是插件页开关碰不到的位置。症状是「显示已启用但命令不存在,点开关也没用」。那个 disabled 是 0.2.17 应急修 bug 时误加的,带了 7 个版本,0.3.2 已去掉。
从旧版本升级的注意:reconcile() 只在「新增依赖」时跑,所以以前手动从 dsh.profile.bundles 删过包名的人,pnpm update 到 0.3.2 之后包名不会自动补回来,需要重跑一次 dsh plugin --profile web add dsh-bili-watch。
验证:dsh --profile web --dump-config 输出中可检索到 dsh-bili-watch。
亦可直接放入 profiles/<name>/node_modules/dsh-bili-watch。
插件依赖 @deepseek-ai/dsh-tools(peer, optional)。缺失时 bili_watch 工具不注册,/bili 命令与面板仍可用。
命令行全局安装
npm i -g dsh-bili-watch
bili-watch view BV1EPnG6DEJC
或 clone 后直接运行:
node tools/look.mjs view BV1EPnG6DEJC
用法
命令一览
| 命令 | 说明 |
| view <target> | 元数据 + 简介 + 热评。不下载、不依赖 ffmpeg,匿名可用 |
| peek <target> | 元数据 + 热评 + 5 帧画面。先取 B 站官方截帧接口,不下视频;取不到才回落下载 360p 抽帧(约 1 MB/min);OCR 环境就绪时顺带识别。仅 B 站 |
| sub <BV或链接> [--format srt\|json\|md\|txt] [--out <path>] | AI 字幕。--format 输出 SRT / JSON(含时间戳)/ Markdown(含跳转链接)/ 纯文本 txt。需登录 |
| comments <bv> [N] | 评论区,默认按热度排序 |
| asr <target> | 本地 ASR 转写,附「可信 / 疑似幻觉」判定 |
| frames <target> [--step <sec>] [--ocr] | 抽帧;--ocr 附带画面文字识别 |
| ocr <bv> | 抽帧并 OCR |
| text <target> [--format json\|md] | 四级降级链:字幕 → 转写 → 抽帧 OCR → 读图。全 miss 退出码 1 |
| sheet <bv> [--grid 3x3] | 均匀抽帧拼图,默认 9 帧 |
| substrip <bv> [--step <sec>] | 底部字幕竖拼 |
| danmaku <bv> | 弹幕热区(按时间桶统计峰值) |
| local <path> [--ocr] [--frames] | 本地视频文件走降级链(从转写层起)。仅命令行 |
| search <关键词> [--days 3] [--limit 10] [--order pubdate\|click] | 视频搜索。--days 生效时最多抓 3 页,结果仍按 --limit 截断 |
| hot [--limit 10] | 综合热门榜 |
| login [--check] [--poll] [--key <key>] | 扫码登录。--check 校验凭证有效性;--key 需配合 --poll |
| clean | 清空缓存根。任务进行中(锁文件存活)时拒绝并返回非零退出码 |
<target> 接受 BV 号、b23.tv 短链、av 号或 URL。
通用参数
| 参数 | 类型 | 默认值 | 说明 |
| --format | enum | — | 输出格式。text / sub / comments / danmaku / local 落盘时必须配合此参数,单独指定 --out 返回退出码 1 |
| --out <path> | string | — | 落盘路径,相对当前工作目录(cwd),目录不存在时自动创建。text / sub / comments / danmaku / local 写文件;frames 用它指定输出目录。sheet / ocr / substrip 固定写缓存目录,不收 --out |
| --ffmpeg <path> | string | — | ffmpeg 可执行文件路径 |
| --cred <path> | string | — | 凭证 JSON 路径,格式 {sessdata, bili_jct, buvid3, buvid4} |
| --asr-max <ms> | number | 1800000 | 转写时长闸门(毫秒)。视频长于此值时仅输出画面描述 |
| --head <n> | number | 0(按自动规则) | 只给前 n 字。不传或传 0 时按「正文超过 800 字只给开头 400 字 + 落盘路径」处理;要全文用 --full |
| --full | flag | — | 输出全文(不截断) |
| --verify | flag | — | 字幕可疑时抽 5 帧 OCR 交叉校验。字幕正常时不触发 |
| --fresh | flag | — | 忽略缓存强制重跑 |
| --mark-seen | flag | — | 标记为已读 |
| --no-time | flag | — | text/sub 默认输出带 [mm:ss] 逐句时间码,加这个退回纯文本(不带时间戳) |
| --at <时刻> | string | — | frames 精看某一刻,支持三种写法:750(秒)、12:30(分:秒)、12:30-13:10(区间) |
完整参数列表见 bili-watch help。
长文本保护
正文超过 800 字时,终端仅返回前 400 字摘要 + 落盘路径,全文写入缓存目录。工具层(bili_watch)阈值为 2000 字(摘要 800 字)。
面板
装进 DSH 后,在插件页点 dsh-bili-watch 卡片进详情页,里面有一块面板(不用先进设置),日常操作不用去对话里敲命令:
- 依赖状态:ffmpeg、yt-dlp、ASR 环境、OCR 环境、转写模型、B 站登录凭证逐项列出是否就绪、实际路径、占用体积。缺的点行末按钮装(走内置 fetch-deps,体积是估算值,实际以跑完为准)。下载期间要保持 DSH 开着——宿主退出会连下载进程一起结束,重进面板再点一次即可,已经下完的项会自动跳过。
- 扫码登录:未登录显示「去扫码登录」,已登录是「重新扫码」。二维码在面板里出,手机 B 站扫完自动刷新。凭证存在本机 $DSH_HOME/bili-watch/credential.json。
- 哪些能删:yt-dlp、转写模型、登录凭证可以直接删;ffmpeg / ASR 环境 / OCR 环境不给删除按钮——环境删了要重跑 fetch-deps 装回来,代价比省下的空间大,面板只在旁边标出实际位置。
- 批量操作:标题行的「批量下载 / 批量清理」打开勾选清单,删除前会再确认一次。
- 抽帧缓存:显示当前占用与文件数,「立即清除」一键清空;不点则按规则自动清理(保留天数、总量上限),超过从最旧的删起。规则存 deps/cache-config.json,启动时与打开面板时各查一次。
- 缓存位置:默认 $DSH_HOME/bili-watch/cache($DSH_HOME 是 DSH 家目录,Windows 下通常是用户目录里的 .dsh)。登录凭证是它的兄弟文件,清缓存时别把上一层一起删。换位置用环境变量 BILI_WATCH_CACHE,或 deps/deps-config.json 里的 "cache";指到还不是缓存根的目录时,插件会在里面建 bili-watch-cache/,不会直接拿你的目录当缓存根。clean 与面板发现旧位置还有东西、又没本工具的标记时会提示一句——本版不动那些文件,要清理得你自己删。
- 运行时参数:转写闸门、抽帧张数上限这类常改的放常规组,其余收在「高级设置」里(默认收起),每项都标了范围和「调大/调小会怎样」。
- 最近一次调用:显示命令、时间、成败,用来判断上一次是不是卡住了。
0.3.2 起装完重启就是启用状态,不用再去插件页手动打开。
配置
环境变量
| 变量 | 默认值 | 说明 |
| BILI_WATCH_PROJ | deps/ | ASR/OCR 环境目录 |
| BILI_WATCH_FFMPEG | — | ffmpeg 可执行文件路径 |
| BILI_WATCH_YTDLP | — | yt-dlp 可执行文件路径 |
| BILI_WATCH_SEEN | deps/seen.md | 已读标记文件路径 |
| BILI_WATCH_CACHE | $DSH_HOME/bili-watch/cache | 抽帧缓存根目录。优先级:环境变量 > deps/deps-config.json > 默认值 |
| BILI_WATCH_MIRROR | — | 设为 cn 时 fetch-deps 走国内镜像 |
| HF_ENDPOINT | https://hf-mirror.com | 转写模型下载端点 |
| BILI_ASR_MODEL | small | faster-whisper 模型名 |
运行时参数(面板可调)
以下参数通过 Web 面板或 deps/deps-config.json 调整,保存后立即生效:
| 参数 | 类型 | 默认值 | 说明 |
| asrMemMB | number (MB) | 1536 | 本地 ASR 内存门槛。低于此值跳过转写 |
| lockTtlMin | number (min) | 30 | 残留锁过期时长 |
| unitLimit | number (chars) | 5000 | 单条评论/弹幕截断阈值 |
| textLimit | number(字符数) | 800 | 文本层单次返回正文上限 |
| toolTextCap | number(字符数) | 2000 | 工具层单次返回正文总上限 |
| toolHead | number(字符数) | 800 | 工具层默认返回头部字数 |
| dmBucketSec | number(秒) | 30 | 弹幕分桶宽度 |
| dmTop | number(桶数) | 5 | 弹幕热区输出条数 |
| downloadMaxMB | number(MB) | 512 | 单文件下载体积上限 |
| subRateMin10 | number(×10 字符/秒) | 20 | 字幕语速下限(2.0 字/秒) |
| subRateMax10 | number(×10 字符/秒) | 85 | 字幕语速上限(8.5 字/秒) |
| textBrief | number(字符数) | 400 | 超长文本摘要长度 |
| asrMaxSec | number(秒) | 1800 | 转写时长闸门 |
| maxFrames | number | 500 | 抽帧张数上限 |
| asrTimeoutSec | number(秒) | 900 | ASR 进程超时 |
| ffmpegTimeoutSec | number(秒) | 300 | ffmpeg 进程超时 |
| cmdTimeoutSec | number(秒) | 180 | 通用命令超时 |
| asrDevice | enum | auto | ASR 推理设备:auto(有 CUDA 就用,失败回落 CPU)/ cpu(强制 CPU)。面板上是「转写时用 GPU」开关 |
缓存路径
默认缓存根:$DSH_HOME/bili-watch/cache($DSH_HOME 默认为用户目录下 .dsh)。
- 登录凭证:$DSH_HOME/bili-watch/credential.json(缓存根的兄弟文件,清理缓存时勿误删上级目录)
- 自定义缓存位置:设置 BILI_WATCH_CACHE 环境变量,或在 deps/deps-config.json 写入 "cache": "<dir>"。指向外部目录时插件自动创建 bili-watch-cache/ 子目录作为缓存根
- 修改后需重启 DSH 生效
- 自动清理规则(保留天数 / 总量上限)存于 deps/cache-config.json,默认保留 7 天 / 上限 2048 MB,启动时与打开面板时各检查一次
- 缓存根归插件专用:clean 与面板「立即清除」按名字整棵删这 10 个子目录 —— _logs frames sheets sheet-tmp substrip-tmp _misc asr frames-dl audio-dl _hot,里面的东西一律不保留。别把自己的文件放进这些名字的目录;放在缓存根根目录下、名字不在上表的文件不会被删(clean 会跳过并列进「不是 bili-watch 的缓存条目」)
卸载
dsh plugin --profile web remove dsh-bili-watch
卸载前备份
0.3.4 起依赖与配置都在 $DSH_HOME/bili-watch/deps(包外),卸载不会删掉。只有首次启动还没把包内 deps/ 搬完的用户(这时仍在用包内那份)才需要提前拷出以下 4 份文件,重装后放回原位:
1. deps/deps-config.json — 运行时参数与自定义缓存路径
2. deps/cache-config.json — 自动清理规则
3. deps/ui-config.json — 面板显示开关
4. deps/seen.md — 已读标记(或将 BILI_WATCH_SEEN 指向包外路径)
卸载后残留
- 登录凭证 $DSH_HOME/bili-watch/credential.json 不在包内,卸载不删除;如需清除手动删除
- 缓存根 $DSH_HOME/bili-watch/cache 不在包内,卸载不删除;仅清理 cache/ 子目录,勿删上级凭证文件
依赖
| 能力 | 依赖 | 缺失行为 |
| 元数据 / 评论 / 弹幕 / 搜索 / 热榜 | 无(匿名 HTTP) | — |
| 抽帧 / 九宫格 / 字幕带 | ffmpeg | 报错并提示安装方式 |
| AI 字幕 | B 站登录态(SESSDATA) | 返回无字幕轨,降级至下一级 |
| 本地 ASR | Python 3.10–3.14 + faster-whisper(deps/.venv-asr)+ VC++ 运行库 | 跳过,降级 |
| 画面 OCR | Python 3.10–3.14 + rapidocr_onnxruntime(deps/.venv-ocr)+ VC++ 运行库 | 跳过,仅基础抽帧 |
| 通用 URL | yt-dlp | 仅支持 B 站 |
环境要求:Node.js ≥ 22。
VC++ 运行库:ASR 与 OCR 用的 faster-whisper / onnxruntime 是 C++ 编译的 wheel,运行时要 vcruntime140.dll。Windows 10/11 一般自带,但精简过的企业镜像可能没有——症状是 pip 装得上、import 时报 DLL load failed while importing …。这时装一遍 Microsoft Visual C++ 2015–2022 Redistributable (x64),与 Python 版本无关,不必重装 venv。
ffmpeg 查找顺序:--ffmpeg > BILI_WATCH_FFMPEG > deps/deps-config.json > deps/ffmpeg.exe > PATH。
一键安装依赖
Windows
powershell -ExecutionPolicy Bypass -File scripts\fetch-deps.ps1 -Target all
powershell -ExecutionPolicy Bypass -File scripts\fetch-deps.ps1 -Target all -Mirror cn
单项:ffmpeg / ytdlp / asr / ocr / model
powershell -ExecutionPolicy Bypass -File scripts\fetch-deps.ps1 -Target ytdlp
bash
macOS / Linux
bash scripts/fetch-deps.sh all
BILI_WATCH_MIRROR=cn bash scripts/fetch-deps.sh all
依赖装在哪(0.3.4 起):默认放 $DSH_HOME/bili-watch/deps(DSH_HOME 默认 ~/.dsh),不在插件包目录里——这样 npm uninstall / 重装插件不会把两个 venv 和 460 MB 模型一起删掉。
从旧版升级时,首次启动会自动把包内 deps/ 搬过去(复制完成后才切换,中途失败继续用包内那份,面板会给出提示);包内旧的 deps/ 不再使用,确认新位置正常后可以手动删除。
想放到别的盘:设环境变量 BILI_WATCH_DEPS 指向目标目录即可(CLI、面板、一键脚本三处同源)。
GPU 加速(可选)
只有本地 ASR 转写用得上 GPU,OCR 走 onnxruntime 的 CPU 推理,与显卡无关。
| 你的机器 | 面板「GPU 运行库」一行 | 转写 |
| 有 NVIDIA 卡 + 已装运行库 | 绿点,显示 cublas64_12.dll | 走 GPU,比 CPU 快 2–8 倍 |
| 有 NVIDIA 卡 + 没装运行库 | 灰点,提示「装上快 2-8 倍」 | 走 CPU(功能一样,只是慢些) |
| 没有 NVIDIA 卡 | 灰点,说明没有 nvidia-smi | 走 CPU |
怎么装:面板上点那一行的「下载」,或命令行: powershell powershell -ExecutionPolicy Bypass -File scripts\fetch-deps.ps1 -Target cuda
装的是 pip 上的三个 wheel(nvidia-cublas-cu12 + nvidia-cudnn-cu12 + nvidia-cuda-nvrtc-cu12),约 500 MB,装在 ASR 的 venv 里,不需要装整套 CUDA Toolkit。装完脚本会真加载一次 cublas64_12.dll 校验——pip 装成功不代表 DLL 能加载(缺 VC++ Redist 时会 DLL load failed)。
为什么第一次慢:-Target all 不会自动装它(不问、不装 500 MB);首次转写还要加载模型到显存,比后续调用慢十几秒。
怎么关:面板「转写与抽帧」里的「转写时用 GPU」取消勾选(存的是 asrDevice: cpu),或把 deps/deps-config.json 里的 asrDevice 写成 cpu。删掉运行库也能自动退回 CPU,不会报错。
没有卡能不能开:能,但开了也走 CPU——auto 的语义是「有可用 CUDA 就用,没有或显存不足就回落 CPU」。
故障排查
凭证
仅 AI 字幕需要登录。凭证优先级:--cred > $DSH_HOME/bili-watch/credential.json > 匿名。
watch 只读自己这一份,不会去跟随同机其它工具写的凭证(旧版会按 mtime 挑最新的那份,容易出现「面板显示已退出、实际还在用别的身份」)。早期版本留下的旧位置凭证(deps/ 下那份)会在第一次读到时一次性复制到 $DSH_HOME/bili-watch/credential.json,之后不再回读。
凭证文件等同账号登录态,禁止外传或提交版本库。
转写可信度
本地 ASR 输出附判定字段:
- 可信:VAD 检测到足够人声,无跨窗重复模板句
- 疑似幻觉:VAD 人声 < 0.5 秒却输出文本、跨 2 秒以上窗口重复文本占比 > 50%、no_speech_prob > 0.5 分段占比过半
判为疑似幻觉时应通过抽帧 OCR 交叉验证,不可直接引用为台词。
降级链行为
AI 字幕(需登录) ↓ 无字幕轨本地 ASR(可选) ↓ 未安装 / 语速 < 2 字/秒抽帧 + OCR(可选) ↓ 未安装读图(模型直接看帧)
降级时输出原因,不静默跳过。四级全 miss 退出码 1。
字幕诱饵检测
text 命令自动检测以下可疑信号:
- 字幕语速不在 2–8.5 字/秒区间
- 字幕与标题 / UP 主关键词重合度过低(需与语速异常同时触发)
- 字幕包含「实验 / 钓鱼 / 测试」字样
指定 --verify 时额外抽 5 帧 OCR 比对画面文字。仅在可疑信号触发时生效,正常字幕不触发。
限制
- B 站侧只读:不支持点赞、评论、发弹幕、历史记录
- 通用 URL 走 yt-dlp 时不做内网地址过滤,禁止指向内网服务
- 无内置限速,批量调用需自行 sleep
- 工具名 bili_watch 与命令 /bili 若与其他插件冲突,后注册者覆盖前者
成本
- 字幕接口带一次重试;text 最多尝试两次(先匿名,有凭证再试一次)。
- 抽帧要下载视频(360p)再跑 ffmpeg,--step 调大能少抽;脚本另外限了 -threads 2,不会把机器吃满。
- 拼图(sheet)和字幕带(substrip)把多帧压成一张图给模型看,省 token。
- 本地转写、OCR 都在本机跑,不上传、不按次计费;只有取 B 站数据的接口走网络。
模块架构 mermaid
flowchart TD
subgraph L1["index.js(DSH 进程内)"]
DT["defineTool 注册 bili_watch"]
CR["commands.register 注册 /bili"]
WS["webServer 路由 / 面板"]
end
subgraph L2["tools/look.mjs(子进程)"]
LOOK["node tools/look.mjs <cmd>"]
end
subgraph L3["外部依赖(可选)"]
PY["deps/_bili-asr.py ASR"]
FF["ffmpeg 抽帧"]
YT["yt-dlp 下载"]
end
DT -->|spawn| LOOK
CR -->|spawn| LOOK
WS -.->|面板写凭证 / 内容命令 spawn| LOOK
LOOK -->|spawn| PY
LOOK -->|spawn| FF
LOOK -->|spawn| YT
致谢
- dsh-bilibili-video-summary(Aurum807):单链接自动提示形态
- dsh-bilibili-subtitles(Chenfan2):多格式导出形态
- video-watch-plus(MIT):ASR 幻觉证伪方法
License
MIT预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
Sacredbluedragon/dsh-bili-watch
给模型看 B 站视频的 DSH 插件:字幕、转写、抽帧 OCR、评论与弹幕;支持 b23.tv 短链,抽帧缓存目录可换。
相关插件 · 平台集成
智能体工程课walkinglabs/learn-harness-engineering
一门项目制课程,教你搭建环境、管理状态、做验证、加控制机制,让 AI 编程智能体稳定干活。
★ 21,155已汉化DSH
71.7
视频翻译配音liuzhao1225/YouDub-webui
开源视频本地化工作台:导入视频,识别、翻译并逐句克隆原声,生成字幕和配音成片。
★ 5,778已汉化Cordis
71.4
失败经验库Ikalus1988/MisakaNet
📚 零依赖、由 git 支撑的微课程库,让 AI 智能体异步分享并检索经过验证的调试经验。| https://misakanet.org
★ 549已汉化DSH
71.2
IM 机器人接入xmanrui/dsh-im
让 DeepSeek Harness 触手可及 Connecting DeepSeek Harness
★ 3,011已汉化DeepSeek 客户端
71
桌面智能体工作区freestylefly/wesight
WeSight 是开源桌面控制台,面向本地 AI 智能体。它帮你安装或复用 Claude…
★ 933已汉化MCP
71
中国平台研究引擎Jesseovo/last30days-skill-cn
给 AI Agent 的中文互联网研究 Skill:检索 8 大平台最近 30…
★ 1,969已汉化DSH
70.7