把 B 站视频转成模型可读的文本与画面
29.5/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/11/2 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add neil-ji/dsh-bili-watch-desktop该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
是什么
dsh 原生插件 · platform
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 6 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 91 处,展示前 67 条)
- 发起网络请求client.js:335
response = await fetch(API + path, init) - 读取本地文件lib/cache.js:35
if (!existsSync(mark)) { - 写入本地文件lib/cache.js:36
writeFileSync(mark, JSON.stringify({ version: 1, createdAt: new Date().toISOString() }), 'utf8') - 读取本地文件lib/cache.js:51
try { if (existsSync(join(dir, CACHE_MARK))) return true } catch {} - 读取本地文件lib/cache.js:53
for (const entry of readdirSync(dir, { withFileTypes: true })) { - 读取本地文件lib/cache.js:56
try { if (existsSync(join(dir, entry.name, 'meta.json'))) return true } catch {} - 读取本地文件lib/cache.js:77
try { entries = readdirSync(path, { withFileTypes: true }) } catch { return false } - 读取本地文件lib/cache.js:98
const value = JSON.parse(readFileSync(file, 'utf8')) - 删除本地文件递归lib/cache.js:189
rmSync(path, { recursive: true, force: true, maxRetries: 3 }) - 删除本地文件lib/cache.js:198
rmSync(path, { force: true }) - 删除本地文件lib/cache.js:230
try { rmdirSync(dir) } catch {} - 删除本地文件lib/cache.js:255
rmSync(entry.path, { force: true }) - 删除本地文件lib/cache.js:268
rmSync(entry.path, { force: true }) - 读取本地文件lib/deps.js:34
return paths.find((path) => path && existsSync(path)) || '' - 读取本地文件lib/deps.js:42
for (const name of readdirSync(join(root, 'lib'))) { - 读取本地文件lib/deps.js:58
const names = readdirSync(dir) - 读取本地文件lib/deps.js:81
for (const model of readdirSync(hub)) { - 读取本地文件lib/deps.js:84
for (const snapshot of readdirSync(snapshots)) { - 读取本地文件lib/deps.js:85
const files = readdirSync(join(snapshots, snapshot)) - 写入本地文件lib/deps.js:262
writeFileSync(file, Buffer.concat([Buffer.from([0xef, 0xbb, 0xbf]), bytes])) - 写入本地文件lib/deps.js:270
appendFileSync(DOWNLOAD_LOG, `[${new Date().toISOString()}] ${text}\n`, 'utf8') - 执行外部命令来自配置/变量lib/deps.js:307
child = spawn(command, args, { - 删除本地文件递归lib/deps.js:507
rmSync(path, { recursive: true, force: true, maxRetries: 3 }) - 执行外部命令固定命令lib/deps.js:526
execFile('ffmpeg', ['-version'], { timeout: 2500, windowsHide: true }, (error) => { - 写入本地文件lib/config.js:60
mkdirSync(dir, { recursive: true }) - 写入本地文件lib/config.js:69
writeFileSync(tmp, JSON.stringify(value, null, 2), 'utf8') - 写入本地文件lib/config.js:70
renameSync(tmp, path) - 删除本地文件lib/config.js:72
try { rmSync(tmp, { force: true }) } catch {} - 读取本地文件lib/config.js:84
raw = readFileSync(path, 'utf8') - 写入本地文件lib/config.js:96
renameSync(path, backup) - 读取本地文件lib/config.js:243
const value = JSON.parse(readFileSync(LAST_RUN, 'utf8')) - 读取本地文件lib/config.js:269
try { return statSync(path) } catch { return null } - 读取本地文件lib/config.js:273
try { return existsSync(path) } catch { return false } - 写入本地文件lib/look.js:33
mkdirSync(root, { recursive: true }) - 读取本地文件lib/look.js:35
for (const entry of readdirSync(root)) { - 写入本地文件lib/look.js:40
mkdirSync(dir, { recursive: true }) - 写入本地文件lib/look.js:63
writeFileSync(file, value, 'utf8') - 执行外部命令固定命令lib/look.js:86
execFileSync('taskkill', ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' }) - 执行外部命令来自配置/变量lib/look.js:122
child = spawn(process.execPath, [LOOK, ...args], { - 写入本地文件lib/look.js:173
try { writeFileSync(join(videoDir(entry.bv), 'last-error.json'), `${JSON.stringify(entry, null, 2)}\n`, 'utf8') } catch {} - 写入本地文件lib/look.js:176
mkdirSync(RUNS_DIR(), { recursive: true }) - 读取本地文件lib/look.js:179
const stat = statSync(file) - 读取本地文件lib/look.js:180
const lines = Math.max(1, readFileSync(file, 'utf8').split('\n').length - 1) - 删除本地文件lib/look.js:182
try { rmSync(`${file}.1`, { force: true }) } catch {} - 写入本地文件lib/look.js:183
try { renameSync(file, `${file}.1`) } catch {} - 读取本地文件lib/look.js:193
const lines = readFileSync(RUNS_FILE(), 'utf8').trim().split('\n').slice(-limit) - 读取本地文件lib/look.js:203
return existsSync(LOOK) - 读取本地文件lib/routes.js:135
return JSON.parse(readFileSync(`${HERE}/package.json`, 'utf8')).version || '0.0.0' - 读取本地文件lib/visual.js:82
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) - 读取本地文件lib/visual.js:88
if (frameDir && existsSync(frameDir)) { - 读取本地文件lib/visual.js:92
if (!existsSync(join(frameDir, file))) break - 读取本地文件lib/visual.js:104
if (existsSync(join(frameDir, file))) out.push(file) - 读取本地文件lib/visual.js:220
.filter((path) => path && existsSync(path)) - 读取本地文件lib/visual.js:293
if (!existsSync(path)) continue - 写入本地文件lib/visual.js:307
mkdirSync(dir, { recursive: true }) - 写入本地文件lib/visual.js:308
appendFileSync(join(dir, 'runs.jsonl'), `${JSON.stringify({ - 读取本地文件lib/cred.js:30
if (!existsSync(CRED_FILE)) return { exists: false, state: 'missing', updatedAt: null, ageDays: null, path: CRED_FILE } - 读取本地文件lib/cred.js:32
const value = JSON.parse(readFileSync(CRED_FILE, 'utf8')) - 读取本地文件lib/cred.js:33
const updatedAt = value?.updatedAt || statSync(CRED_FILE).mtime.toISOString() - 读取本地文件lib/cred.js:45
try { return JSON.parse(readFileSync(CRED_FILE, 'utf8')) } catch { return null } - 读取本地文件lib/cred.js:49
if (!existsSync(CRED_FILE)) return { ok: true, removed: false } - 删除本地文件lib/cred.js:51
rmSync(CRED_FILE, { force: true }) - 写入本地文件lib/cred.js:59
mkdirSync(dirname(CRED_FILE), { recursive: true }) - 写入本地文件lib/cred.js:60
writeFileSync(CRED_FILE, JSON.stringify({ - 发起网络请求lib/cred.js:89
const response = await fetch(FINGER_SPI, { headers: { 'User-Agent': UA }, signal: AbortSignal.timeout(15_000) }) - 发起网络请求lib/cred.js:104
const response = await fetch(QR_GENERATE, { headers: { 'User-Agent': UA }, signal: AbortSignal.timeout(15_000) }) - 发起网络请求lib/cred.js:125
response = await fetch(QR_POLL(clean), {
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
DSH 桌面端优先的 B 站视频阅读插件(基于 Sacredbluedragon/dsh-bili-watch 重构):侧栏面板、可信的安装进度、更安全的端点
README
由 DeepSeek 最新模型翻译生成dsh-bili-watch(桌面端重构版)
把 B 站视频读成模型能用的内容:元数据、评论、弹幕热区、AI 字幕、本地转写、抽帧 OCR;以及一条 visual:一次调用同时返回「文本稿 + 网格画面」,给多模态模型直接看图。
提供 /bili 斜杠命令、bili_watch 工具,以及一个桌面端优先的侧栏面板(装依赖、扫码登录、管缓存、调参数)。
本项目是 Sacredbluedragon/dsh-bili-watch(0.3.3,MIT)的衍生重构版,不是另起炉灶。原作者的 CLI 核心、三级降级策略、缓存锁协议、第三方内容来源标注都被完整保留;我改动的是宿主(index.js/lib/)与界面(client.js),以及一批在桌面端实际使用中暴露出来的缺陷。
原作者在 README 里把「看视频」拆成「字幕 → 转写 → OCR → 读帧」,并明确要求「降级时要说明原因,不能静默跳过」——这个设计是这一版能站住的地基。原项目的版权声明与许可完整保留在 LICENSE。
为什么会有这一版
不是为了加功能,而是原版在 DSH 桌面端里落不了地。三个具体问题(都可复现):
1. 入口埋得太深。面板挂在「插件详情页」里,日常要用得先开设置 → 插件 → 找到卡片 → 进详情页;一页纵向排了 20 多个控件,没有分组。
2. 「一键下载」点了没反应。
展开完整文档(README 剩余 1 节)
dsh-bili-watch(桌面端重构版)
把 B 站视频读成模型能用的内容:元数据、评论、弹幕热区、AI 字幕、本地转写、抽帧 OCR;以及一条 visual:一次调用同时返回「文本稿 + 网格画面」,给多模态模型直接看图。
提供 /bili 斜杠命令、bili_watch 工具,以及一个桌面端优先的侧栏面板(装依赖、扫码登录、管缓存、调参数)。
本项目是 Sacredbluedragon/dsh-bili-watch(0.3.3,MIT)的衍生重构版,不是另起炉灶。原作者的 CLI 核心、三级降级策略、缓存锁协议、第三方内容来源标注都被完整保留;我改动的是宿主(index.js/lib/)与界面(client.js),以及一批在桌面端实际使用中暴露出来的缺陷。
原作者在 README 里把「看视频」拆成「字幕 → 转写 → OCR → 读帧」,并明确要求「降级时要说明原因,不能静默跳过」——这个设计是这一版能站住的地基。原项目的版权声明与许可完整保留在 LICENSE。
为什么会有这一版
不是为了加功能,而是原版在 DSH 桌面端里落不了地。三个具体问题(都可复现):
1. 入口埋得太深。面板挂在「插件详情页」里,日常要用得先开设置 → 插件 → 找到卡片 → 进详情页;一页纵向排了 20 多个控件,没有分组。
2. 「一键下载」点了没反应。spawn('powershell.exe', …, { detached: true }) 在 Windows 的 DETACHED_PROCESS 下没有控制台,PowerShell 会在 ~60 ms 内直接退出(退出码 0、零输出、什么都没装),而代码把 code 0 当成安装成功——于是「安装」永远无效,状态一闪而过。
3. 为了补状态而干扰宿主。为了提示缺依赖,客户端往 shell.overlay 塞横幅,并每 2 秒遍历、改写 所有插件横幅的 top;宿主侧 GET /status 每 8 秒顺手清缓存、用 execFileSync 探 ffmpeg,阻塞事件循环。
这一版的目标就三句话:入口要顺手、状态要可信、不碰别人的东西。 顺手修掉的还有 18 项安全问题和 16 项功能缺陷(逐条见文末「审计」)。
我做了哪些优化
一、界面:从「插件详情页里的表单」变成「侧栏里的一个页面」
- 独立面板:sidebar.panellist 图标 + main 页面,一处入口;设置页与插件详情页不再插入任何条目 (之前那两张摘要卡反而是重复信息)。
- 信息架构重排:概览三卡(账号 / 依赖 / 缓存)→ 运行依赖(逐行 + 一键安装)→ 抽帧缓存 → 账号 → 参数(常用 2 项 + 折叠的高级 15 项)→ 最近一次调用(附 /bili 用法示例)。
- 只用主题令牌(--dsw-alias-):浅色/深色跟随宿主。原来硬编码的 #fffbeb/#d97706 在深色主题下很刺眼。
- 交互细节:二维码从「服务端 SVG 字符串 + dangerouslySetInnerHTML」改成 PNG data: URL + ;删除确认从 window.confirm 换成支持 Esc / 点遮罩关闭的弹窗;改缓存目录改用宿主自带的
uiWorkspace.pickDirectory(),不再弹一个宿主窗口之外的 PowerShell 目录框。
- 就绪判定不再是「文件存在」:venv 刚建好、pip 还没装完时,旧逻辑已经报「已就绪」。
现在要核对 site-packages 里真有 faster_whisper / rapidocr_onnxruntime,模型要核对 snapshots/<hash>/ 里真有权重文件。
二、可靠性:安装看得见、失败说得出
- 逐项进度条 + 阶段文本:接管安装脚本的 stdout/stderr,识别 ==> …、下载 …、pip 的
Collecting/Installing、Successfully installed、huggingface 的 tqdm 百分比,配合「已用时间 / 预估时长」平滑推进;yt-dlp 这类直接写目标文件的还会显示已下载 X MB。
- 进度只出现一次:早期版本顶部有一张「正在安装」卡片、下面依赖行又显示一遍同样的 8%,属于重复;现在只在对应那一行里显示,卡片位置换成标题旁一句「安装中 N 项」。
- 成功要核对:脚本退出码 0 也要检查产物是否真的出现,没有就按失败报——这正是问题 2 的直接补丁。
- 失败可自救:保留真实错误行(如 curl: (56) Recv failure: Connection was reset),并给「重试 / 用国内镜像重试」两个按钮(-Mirror cn:GitHub 走 gh-proxy、PyPI 走清华、模型走 hf-mirror)。
实测在本机 GitHub 直连被重置,点镜像后 5 秒内装好 yt-dlp。
- 轮询无副作用:GET /status 不再清缓存、不再 execFileSync;ffmpeg 只在启动时异步探一次。
状态计算全部走 20–60 秒的惰性缓存,面板 2.5–10 秒轮询也不会把宿主拖慢。
- 原子写:配置文件先写临时文件再 rename;损坏的 JSON 只备份一次并按默认值继续,不会「改一个参数整份配置被清空」。
三、安全:把边界补上
| 问题 | 处理 |
| /api/bili-watch 只靠「回环 + Host + Origin」判断,本机任意进程可删凭证/改配置/起安装 | 每个宿主进程生成 32 字节随机页面令牌,经 webServer.tapIndex 注入 index.html;端点优先校验令牌(常量时间比较),回环判据仅作兼容回退 |
| 静默复制另一个产品(bili-duty)的登录 cookie | 删掉这条路径,只认本插件自己的 credential.json / --cred |
| 转写模型默认从第三方镜像拉取 | 默认端点改回 huggingface.co,镜像需显式设置 |
| --out 可写 UNC(\\host\share)与开机自启目录、后缀不限 | 拒绝 UNC / 自启动目录 / 可执行后缀;sub --out 补上自动建目录 |
| 落盘文本里的 | --format json 是唯一跳过注入扫描的分支 | 扫描移到底层输出之前,只写 stderr,不污染 JSON |
| 整段视频读进内存(上限可配到 4 GB) | 改流式落盘 .part,超限立即中止 |
四、工程:可维护、可测试
- 宿主半端拆成 lib/ 八个模块(config / cache / deps / cred / look / tool / routes / hints),每个模块只做一件事,index.js 只负责装配。
- 去掉 @deepseek-ai/dsh-tools / dsh-llm 硬依赖:工具用 registry 直收的 JSON Schema 定义,两个可选包缺席时不会静默少一个工具(原版缺 dsh-tools 时 bili_watch 会消失)。
- tools/look.mjs 上做了 12 处低风险补丁:数值参数校验(不再产生 pNaN 缓存目录)、pickEvenly 空列表、帧清单上限硬编码、ffmpeg 探测缓存与超时、流式下载、--out 收口、缺参退出码、若干 fetch 超时、默认模型端点、注入扫描顺序、围栏自适应、sub --out 建目录。
- 安装脚本执行前兜底补 UTF-8 BOM:PowerShell 5.1 会把无 BOM 的 .ps1 按 ANSI 解码,中文注释里的引号会把整份脚本解析成 UnexpectedToken(这个坑在开发中真实踩到过)。
功能
面板(左侧栏图标 B 站视频)
- 运行依赖:ffmpeg / yt-dlp / 转写环境 / OCR 环境 / 转写模型 / 登录,逐行显示路径、体积、缺失原因;单项安装或「安装缺失项」,安装中显示进度条、百分比与脚本当前阶段。
- 抽帧缓存:占用条、位置(宿主目录选择器)、立即清理、按天数/总量的自动清理规则。
- 账号:扫码登录(PNG 二维码,确认后自动刷新)、退出登录。
- 参数:常用两项常显,其余收进「高级设置」;另有「链接自动提示」开关。
- 最近一次调用:时间、命令、成败、耗时,以及 /bili 的用法示例。
命令与工具
text
/bili text BV1xx411c7mD # 字幕 → 本地转写 → 抽帧 OCR,自动降级 /bili visual BV1xx411c7mD --step 20 --quality 720 --extra-frames 2 # 文本 + 网格 + 2 张 720p 单帧 /bili frames BV1xx411c7mD --quality 1080 # 只抽帧,1080p 走 dash(常需登录) /bili frames BV1xx411c7mD --start 1:00 --end 2:30 --step 10 # 只看这一段 /bili peek BV1xx411c7mD # 先判断是口播还是画面(需下载 360p) /bili search DeepSeek --days 3 # 最近 3 天发布 /bili comments BV1xx411c7mD 20 # 评论区 /bili danmaku BV1xx411c7mD # 弹幕热区 /bili login # 生成二维码(也可在面板里扫) /bili clean # 清空抽帧缓存
模型侧工具 bili_watch,command 支持 search|hot|peek|view|sub|comments|danmaku|asr|frames|sheet|substrip|ocr|text|visual。
图片帧 + 文本稿(visual)
jsonc
{
"command": "visual",
"bv": "BV1xx411c7mD",
"step": 20, // 抽帧间隔;不传按视频时长自适应(实际值会写进结果) "grid": "3x3", // 网格布局,上限 4x4 "quality": 720, // 360 | 480 | 720 | 1080(默认 360) "extraFrames": 2, // 额外附带 0–3 张单帧(默认 0) "start": "1:00", // 只看这一段(90 / "1:30" / "1m30s" 都认) "end": "2:00", "attach": true, // false = 只给路径,不附图(省上下文) "transcript": "auto" // auto=只试 B站字幕(快,默认)|full=没有字幕再跑本地转写|off=只要画面 }
一次调用返回的内容:
1. 一段文本:标题 / UP / 时长、实际用的网格与间隔(自动推算的也会写明)、覆盖区间、画质、每格与每张单帧对应第几秒(与图片顺序一一对应)、字幕全文(超长自动落盘 + 摘要);
2. 一张网格图(1 张覆盖 N 个场景,最省视觉 token);
3. 可选 N 张单帧(对某几格看细节)。
两个「不再悄悄改参数」的约定:
- 帧数不够铺满网格时(如 49s 视频 step=10 只有 5 帧,却要 3x3):不再拼黑格,改为直接附这几张帧,并在文本里写明「请求 3x3,只有 5 帧,已改为直接附 5 张帧」。
- 单帧上限 3、网格上限 4x4、--limit 上限 20:超出会截断,但结果里会写「请求 X,上限 Y,已截断」。
画质说明:
| quality | 来源 | 说明 |
| 360(默认) | durl | 匿名可用,640×360 单帧 |
| 480 / 720 | durl | 多数视频匿名也能拿到 720p;单帧 1280×720 |
| 1080 | dash(仅视频流) | 常需登录;单帧 1920×1080,3 分钟视频约 88 MB |
网格每格固定按 640 宽拼接,所以画质主要影响单帧;要看清小字/表情,给 extraFrames 加 1–2 张并配 quality 720/1080。整张网格还会被模型按长边缩放,格数越多单格越小。
实现上,宿主在 execute() 里把图片保存成 DSH 附件(attachments.saveImage),再由 render() 以 ContentBlock[] 的形式和文本一起返回——图片是工具结果的一部分,模型不需要再逐张 read_image。网格每格的时间戳与画面同源,模型可以直接说「第 80 秒那个镜头」。
1080p 走 dash(音视频分离),只取视频流,所以 3 分钟片约 88 MB;长视频请先用 360/720 定位。
画面类内容优先用网格定位,再对 1–2 帧单独看。
安装
本仓库是标准 DSH bundle(dsh.bundle.patch + dsh.client),无构建步骤: text plugin_manager install_bundle target = "<本目录的绝对路径>"
安装后重启 DSH(或刷新窗口)即可。也可以把它放进 profile 的 node_modules/@local/dsh-bili-watch 并加入 dsh.profile.bundles。
依赖(ffmpeg / yt-dlp / Python 环境 / 转写模型)默认装到本包 deps/ 下,在面板里点选安装;缓存与登录凭证在 $DSH_HOME/bili-watch/(DSH_HOME 默认 ~/.dsh),卸载插件不会丢。
和上游 0.3.3 的差异(速查)
| 上游 | 这一版 |
| 面板在插件详情页,20+ 控件纵向排列 | 左侧栏独立面板;设置页/插件详情页零插入 |
| shell.overlay 横幅 + 改写其他插件 DOM | 没有全局横幅,不碰任何其他插件 |
| 硬编码颜色 | 只用 --dsw-alias- 令牌 |
| 8 秒轮询 + 路由内清缓存 + 同步探 ffmpeg | 10 秒(安装中 2.5 秒),GET 无副作用 |
| Windows 上一键下载静默失效 | 不再 detached;进度条 + 阶段文本 + 产物核对 + 重试/镜像重试 |
| 设置页里的摘要卡 | 已移除(与侧栏入口重复) |
| SVG 二维码 + dangerouslySetInnerHTML | PNG data URL + |
| PowerShell 目录选择框 | 宿主 uiWorkspace.pickDirectory() |
| @deepseek-ai/dsh-tools 缺失时工具静默消失 | 工具注册无外部依赖 |
验证到什么程度
- 10 个源文件 node --check 全过;bundle 安装/启用 application: applied。
- 槽位:sidebar.panellist(order 20)、main key bili-watch 均 active; settings.section 与 plugins.bundle.config 已确认没有本插件占用。
- 端点:GET /status 200 且两次调用零副作用;参数读写、缓存清理、下载白名单、跨源/非 JSON 请求 403、局域网不可达(服务只绑回环)逐项实测。
- 安装:GitHub 直连失败时保留真实错误行;镜像重试 5 秒装好 yt-dlp.exe(23.5 MB)并通过产物核对。
- 端到端:bili_watch command=hot limit=3 返回真实热榜并写入 last-run.json;view 返回标题/简介/热评。
- visual 实测:横屏(鹅城 FUNK)与竖屏(奶奶买手机)各一条——一次调用分别返回 1 张网格图 + 字幕文本;竖屏拼图修正了 scale=640:-1 的放大会变成 1080×1920(原来被拉到 1920×3414,白烧视觉 token)。
- 画质实测:--quality 720 单帧 1280×720(durl,3 分钟片 44 MB);--quality 1080 单帧 1920×1080 (dash 视频流,88 MB,输出标注「dash,无音轨」)。
- 未验证:面板的实际渲染与浅/深色观感(没有浏览器控制权,只能确认槽位注册与数据链路)。
对 2026-10-29 测试报告的回应
一份 8 样本 / 12 次调用的实测报告指出「默认值不安全:静默 clamp、均匀抽帧、无时间范围、截断不声明」。
这一轮先把「工具替你做决定且不告诉你」的四类问题处理掉:
| 报告问题 | 现在 |
| step=0 悄悄按 2s 跑 | frames 直接打「间隔: 每 2s 一帧(自动推算)」,visual 文本同样写明 |
| extraFrames=5 悄悄给 3 帧 | 结果里写「请求 5 张,上限 3,已截断」 |
| grid=5x5 帧不够 → 黑格 | 帧数 < 格数时改为直接附帧(不拼黑格);grid > 4x4 直接拒绝并说明单格会小到读不清 |
| 长视频没有时间范围 | 新增 start / end(90、1:30、1m30s),帧时间戳带起始偏移,目录按范围隔离 |
| comments limit 被忽略 | 工具 --limit 与 CLI 位置参数都认;结果写「取到 N 条(请求 M 条…≠ 评论区只有这么多)」 |
| danmaku 没有完整性字段 | 文本与 JSON 都带「时间覆盖 / 共 N 条 / 只代表这一次返回的分片」 |
| 「官方字幕」标签不可信 | 改为「B站字幕接口(不区分 UP 上传 / 自动生成)」 |
| substrip 糊、且与 visual 不一致 | 未修(需要字幕行定位 + 清晰度校验,见下) |
| OCR 跨帧重复前缀 | 未修(渐进型手写内容需要跨帧合并/去重,见下) |
| 每次 visual 都重传图片 | 新增 attach:false:只给路径不附图 |
仍未处理的(按报告优先级):
1. substrip 的硬字幕定位:现在只是「裁底部 20% 竖拼」,运动画面会双影;要做对得先检测字幕行位置与稳定区间。
2. text 的 OCR 跨帧合并:手写/PPT 逐帧累加时,前 30 帧是同一句话的前缀,需要按相似度去重并保留最长版本。
3. 默认均匀 vs 场景抽帧:scene=true 已可用,但默认仍是均匀;自动判「画面是否在变」(帧哈希/差异)再决定,是下一步。
4. 首帧黑场/虚焦:会从 0s 抽到片头黑帧;start 可绕过,但没有自动跳过。
5. 搜索结果时长与真实时长不一致:列表接口返回的是列表口径,覆盖分析应以 view 的时长为准。
已知限制与安全提示
1. 依赖安装仍不做完整性校验:脚本从 GitHub Releases / PyPI 下载并执行,-Mirror cn 会经过第三方中转。
这一版把默认端点改回官方、把镜像变成显式选择、在 UI 里明示风险,但没有内置 SHA256 表——这是下一步该做的事。
2. 凭证是明文:$DSH_HOME/bili-watch/credential.json,0600 在 Windows 上不生效,同机其他账户可能读到。
3. 通用 URL 不做内网过滤:/bili view <非 B 站 URL> 交给 yt-dlp,别指向内网服务或云元数据地址。
4. 注入防护是启发式的:来源标签 + 自适应围栏 + 关键词扫描都只是「提示」,不是隔离。
5. 依赖装在插件包内:重装插件会丢 deps/(缓存与凭证在 $DSH_HOME,不受影响)。
审计
重构前我把整份代码读了一遍(宿主 1873 行 + 命令行 3577 行),顺手做了一次安全与缺陷梳理: 18 项安全问题、16 项功能缺陷。这些绝大多数是「桌面端真正用起来才会暴露」的工程问题,不是设计问题——原版的降级链、缓存锁、来源标注经得起看,真正麻烦的是安装链路、 --out 的路径收口、注入扫描的覆盖范围,以及宿主侧的端点与轮询写法。
每一项都在上文对应位置说明了「原来怎样、现在怎样」,没有埋在提交历史里。
许可与致谢
- 上游:dsh-bili-watch — 作者 Sacredbluedragon。
本仓库保留其 MIT 许可与版权声明,tools/look.mjs、scripts/fetch-deps.*、deps/_bili-asr.py 基本原样沿用(仅做本文列出的补丁)。如果这些改动里有上游愿意吸收的部分,也欢迎以 patch 形式回流,而不是长期分叉。
- 上游 README 中致谢的两个同类项目: dsh-bilibili-video-summary(Aurum807)与 dsh-bilibili-subtitles(Chenfan2),以及 ASR 幻觉证伪方法来源 video-watch-plus(MIT)。
- 第三方组件许可见 THIRD_PARTY_NOTICES.md。
本项目同样以 MIT 分发,见 LICENSE。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
neil-ji/dsh-bili-watch-desktop
把 B 站视频转成模型可读的文本与画面
相关插件 · 平台集成
智能体工程课walkinglabs/learn-harness-engineering
一门项目制课程,教你搭建环境、管理状态、做验证、加控制机制,让 AI 编程智能体稳定干活。
★ 21,155已汉化DSH
71.7
视频翻译配音liuzhao1225/YouDub-webui
开源视频本地化工作台:导入视频,识别、翻译并逐句克隆原声,生成字幕和配音成片。
★ 5,792已汉化Cordis
71.4
失败经验库Ikalus1988/MisakaNet
📚 零依赖、由 git 支撑的微课程库,让 AI 智能体异步分享并检索经过验证的调试经验。| https://misakanet.org
★ 550已汉化DSH
71.2
IM 机器人接入xmanrui/dsh-im
让 DeepSeek Harness 触手可及 Connecting DeepSeek Harness
★ 3,170已汉化DeepSeek 客户端
71
桌面智能体工作区freestylefly/wesight
WeSight 是开源桌面控制台,面向本地 AI 智能体。它帮你安装或复用 Claude…
★ 933已汉化MCP
71
中国平台研究引擎Jesseovo/last30days-skill-cn
给 AI Agent 的中文互联网研究 Skill:检索 8 大平台最近 30…
★ 2,041已汉化DSH
70.7
这是你的仓库?认领条目