🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

视频离线转写 wqzhellohhwy/dsh-video-transcribe

DeepSeek Harness
wqzhellohhwy21 天前有提交已提供中文文档扫描:含敏感能力

贴视频链接,本机下载音轨并离线转成文稿

29.9/100
综合分
0
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/12 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add wqzhellohhwy/dsh-video-transcribe
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:待复验
是什么
dsh 原生插件 · other
装得上吗
曾在 dsh 0.2.0-rc.2 下真实安装成功,但当前 dsh 已更新至 0.2.1-rc.5,本站尚未在新版下复验
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 21 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件
查看扫描证据(共 21 处,展示前 20 条)
  • 读取本地文件
    index.js:179
    if (!existsSync(path)) missing.push(`${label}: ${path}`)
  • 执行外部命令来自配置/变量
    index.js:204
    child = spawn(command, args, { cwd, windowsHide: true })
  • 写入本地文件
    index.js:528
    await mkdir(jobDir(job.id), { recursive: true })
  • 写入本地文件
    index.js:529
    await writeFile(jobFile(job.id), `${JSON.stringify(job, null, 2)}\n`, 'utf8')
  • 读取本地文件
    index.js:538
    const text = await readFile(jobFile(id), 'utf8')
  • 读取本地文件
    index.js:555
    const entries = await readdir(cfg.jobsDir, { withFileTypes: true })
  • 读取本地文件
    index.js:606
    const files = await readdir(dir)
  • 读取本地文件
    index.js:614
    const info = JSON.parse(await readFile(join(dir, infoName), 'utf8'))
  • 写入本地文件
    index.js:667
    await mkdir(chunksDir, { recursive: true })
  • 写入本地文件
    index.js:706
    await writeFile(job.textPath, `${text}\n`, 'utf8')
  • 写入本地文件
    index.js:710
    await writeFile(join(dir, 'transcript_raw.txt'), outcome.raws.join('\n'), 'utf8')
  • 写入本地文件
    index.js:721
    await writeFile(job.segmentsPath, `${JSON.stringify(job.segments, null, 2)}\n`, 'utf8')
  • 删除本地文件递归
    index.js:736
    await rm(join(dir, 'chunks'), { recursive: true, force: true })
  • 读取本地文件
    segment.js:247
    const { sampleRate, durationSec, samples } = parsePcmWav(await readFile(inputPath))
  • 写入本地文件
    segment.js:254
    await writeFile(path, buildPcmWav(slice, sampleRate))
  • 读取本地文件
    e2e.mjs:124
    check('返回里带每片字数文件', typeof job.segmentsPath === 'string' && existsSync(job.segmentsPath))
  • 读取本地文件
    e2e.mjs:125
    check('片段文件已清理(不留几百 MB 垃圾)', !existsSync(join(job.dir, 'chunks')))
  • 读取本地文件
    e2e.mjs:128
    if (typeof job.segmentsPath === 'string' && existsSync(job.segmentsPath)) {
  • 读取本地文件
    e2e.mjs:129
    const segments = JSON.parse(readFileSync(job.segmentsPath, 'utf8'))
  • 写入本地文件
    selftest.mjs:446
    writeFileSync(file, buildPcmWav(makeSamples(7), 16000))
扫描于 2026/11/1(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

功能亮点

  • 贴链接下载音轨
  • 离线转写为文稿
  • 长音频自动分片

DeepSeek Harness 插件:贴一个视频链接,用本机工具链下载音轨并离线转写为文稿(yt-dlp + ffmpeg + sherpa-onnx/SenseVoice),长音频自动分片,零云端 API。

README

由 DeepSeek 最新模型翻译生成
dsh-video-transcribe 给 DeepSeek Harness 补上「贴一个视频链接 → 本地出文稿」的能力。 全程本机推理,不调用任何云端转写服务,媒体不出本机。 为什么需要它 DSH 的网络面只有 web_search 和 web_fetch,两者都是文本通道:没有浏览器、没有播放器,视频的画面和音轨都拿不到 —— 所以它天然不能「看视频」。 但本机其实已经躺着一条完整的离线语音识别链路:~/.dsh/speech-to-text/sensevoice 下的 SenseVoice int8 模型(语音输入功能用的那份)配 sherpa-onnx 引擎。缺的只是「把音轨取下来」这一段。 本插件把三段接起来,注册成一个工具 video_transcribe: yt-dlp 取音轨(并写一份 info.json,标题/时长从那里来) → ffmpeg 转 16 kHz 单声道 PCM wav(SenseVoice 的输入规格) → 分片(约 30 秒一片,切点吸附到静音处) → sherpa-onnx + SenseVoice 本地推理出文稿(一次调用吃下整组片段) ⚠️ 长音频必须分片(实测结论,不是预防性设计) SenseVoice 只见过 30 秒以内的语音。
展开完整文档(README 剩余 1 节)
dsh-video-transcribe

给 DeepSeek Harness 补上「贴一个视频链接 → 本地出文稿」的能力。
全程本机推理,不调用任何云端转写服务,媒体不出本机。

为什么需要它

DSH 的网络面只有 web_search 和 web_fetch,两者都是文本通道:没有浏览器、没有播放器,视频的画面和音轨都拿不到 —— 所以它天然不能「看视频」。

但本机其实已经躺着一条完整的离线语音识别链路:~/.dsh/speech-to-text/sensevoice 下的 SenseVoice int8 模型(语音输入功能用的那份)配 sherpa-onnx 引擎。缺的只是「把音轨取下来」这一段。

本插件把三段接起来,注册成一个工具 video_transcribe:

yt-dlp 取音轨(并写一份 info.json,标题/时长从那里来) → ffmpeg 转 16 kHz 单声道 PCM wav(SenseVoice 的输入规格) → 分片(约 30 秒一片,切点吸附到静音处) → sherpa-onnx + SenseVoice 本地推理出文稿(一次调用吃下整组片段)

⚠️ 长音频必须分片(实测结论,不是预防性设计)

SenseVoice 只见过 30 秒以内的语音。把整段长音频一次性交给 sherpa-onnx-offline,它退出码是 0,但内容被静默丢掉。 用同一批真实音频做的受控实验(只改输入长度,内容不变):

| 输入长度 | 产出字数 | 字/秒 | 峰值内存 |
| 30 s | 193 | 6.43 | — |
| 60 s | 329 | 5.48 | — |
| 120 s | 467 | 3.89 | 557 MB |
| 240 s | 212 | 0.88 | 1,189 MB |
| 360 s | 159 | 0.44 | 2,194 MB |
| 611 s | 173 | 0.28 | 5,490 MB |

前 60 秒单独转写的字数比整段 611 秒还多 —— 子集产出超过全集。退化随长度单调加重,内存近似平方增长,到 69 分钟那集直接返回空文本而退出码仍是 0。

修复方式是先切片再识别,效果(同一批音频):

| 视频 | 单遍 | 30 秒分片 | 变化 |
| 10 分 11 秒 | 173 字 / 68.6 s / 5,490 MB | 3,816 字 / 11.2 s / 845 MB | 内容 ×22,内存 ÷6.5 |
| 18 分 09 秒 | 794 字 / 206.8 s / 16,613 MB | 6,386 字 / 18.4 s / 1,393 MB | 内容 ×8.0,内存 ÷12 |
| 69 分 06 秒 | 0 字 / 482 s / 58,620 MB | 25,500 字 / 69.8 s / 6,927 MB | 从「什么都没有」到 2.5 万字 |

片长扫描(611 秒音频):15 s → 3,932 字;30 s → 3,816;60 s → 3,346;120 s → 2,329;240 s → 1,027。15~30 秒之间已经到顶(约 6.4 字/秒,就是这段中文口播的真实密度),再往下切收益很小、切点却翻倍,所以缺省取 30 秒。切点会在 ±1.5 秒内吸附到能量最低处,实测比固定切点多留 3.8% 的内容。

一次 sherpa-onnx-offline 调用可以吃多个 wav(实测一个输入文件对应一份 JSON 输出、顺序一致),所以分片只付一次模型加载 —— 69 分钟视频 139 片只用了 2 次调用。

依赖

| 依赖 | 缺省位置 | 说明 |
| yt-dlp.exe | <DSH_HOME>/video-transcribe/bin/ | 取音轨 |
| ffmpeg.exe | 同上 | 转码 |
| sherpa-onnx-offline.exe | 同上 | 推理引擎(sherpa-onnx v1.13.8 官方预编译静态版) |
| model.int8.onnx | <DSH_HOME>/speech-to-text/sensevoice/models/sensevoice-onnx/ | 复用本机已有模型,无需下载 |
| tokens.txt | 同上 | 同上 |

前三个不在位时插件仍然注册,但调用会明确告诉你缺什么、以及去哪放 —— 一个说不清缺什么的工具比没有工具更糟。

工具接口

唯一的工具:video_transcribe。

| 参数 | 适用 action | 说明 |
| action | 全部(必填) | start / status / list / cancel |
| url | start | 视频/音频页面链接,仅接受 http(s) |
| jobId | status / cancel | start 或 list 返回的作业 id |
| language | start | auto(缺省)/ zh / en / ja / ko / yue |
| label | start | 作业标签,只影响 list 展示 |
| waitSeconds | start | 最多当场等多少秒,缺省 45、上限 120;0 = 立即返回 |

典型用法

video_transcribe({ action: "start", url: "https://www.bilibili.com/video/BV...", language: "zh" })

start 会等一小会儿:等到了就直接返回全文,没等到就返回 jobId —— 之后:

video_transcribe({ action: "status", jobId: "20261008-002519-349225" })

status 在作业完成时返回完整文稿,所以模型拿到文字后自己做摘要/翻译/结构化即可 —— 本工具只负责出文字。

为什么是「作业」而不是「一次调用」

16 分钟音频的转写在 8 线程 CPU 上要跑 3~6 分钟,而 DSH 的工具调用有超时策略 (tool-call-timeout-policy 会按工具给 exec.signal 装截止时间)。作业制让长任务不撞超时:

- 作业状态落盘在 <DSH_HOME>/video-transcribe/jobs/<id>/job.json,DSH 重启后仍可查;
- 每个阶段(download → convert → segment → transcribe)都会更新 phase,所以随时能答「卡在哪一步」;
- 作业串行执行:sherpa 推理峰值内存按 GB 计(单遍 69 分钟音频实测峰值 58 GB,分片后 7 GB), 不允许并发。

配置

写在 profile 的 cordis.patch.yml(同 id 按「完整替换语义」覆盖 bundle 层):

- id: video-transcribe
name: dsh-video-transcribe
config:
language: zh
threads: 8
maxDurationSec: 7200
chunkSeconds: 30

| 字段 | 类型 | 缺省 | 说明 |
| root | string | <DSH_HOME>/video-transcribe | 工作根目录 |
| ytDlpPath / ffmpegPath / sherpaPath | string | <root>/bin/ | 三个可执行文件 |
| modelDir | string | 本机 SenseVoice 目录 | 模型所在目录 |
| modelPath / tokensPath | string | 同上 | 单独覆盖模型或词表 |
| language | string | auto | 缺省识别语言 |
| threads | number | min(8, 核数-1) | 推理线程数 |
| waitSeconds | number | 45 | start 的缺省等待秒数 |
| maxWaitSeconds | number | 120 | 等待上限 |
| maxDurationSec | number | 14400 | 视频时长上限,超过直接拒绝 |
| chunkSeconds | number | 30 | 分片目标片长。别调到 120 以上,实测 240 秒就开始丢内容 |
| minChunkSeconds | number | 5 | 片段长度下限;尾巴不足这个长度就并进前一片 |
| snapSeconds | number | 1.5 | 切点在多大范围内吸附到最安静处;0 = 关掉吸附用固定切点 |
| segmentBatch | number | 100 | 一次 sherpa 调用最多带几个片段 |
| keepSegments | boolean | false | 是否保留切片文件(缺省成功后清理) |
| lowYieldThreshold | number | 1 | 产出密度低于这个值(字/秒)就在结果里告警 |

已知限制

1. 标点不完整。SenseVoice 用的是 --sense-voice-use-itn=true,中文会带逗号句号,但断句不可靠、英文基本没标点。要干净的段落划分得由模型后处理。
2. 专有名词常有音近错字。实测 llama.cpp 被识别成 "Lama do c" / "Lama cP"。
引用术语前务必核对原视频。
3. 不做画面。这是音频路线:纯画面、无解说的视频它给不出内容。
4. 不绕过登录。取不到的音轨(需登录/付费/DRM)就取不到,插件不做凭据注入。
5. 串行。同一时间只跑一个作业,后面的排队。
6. 只出文字,不摘要。摘要/翻译/结构化交给调用它的模型。
7. 分片是硬要求,不是优化。如果切片本身失败(比如 wav 头损坏),作业直接失败而不是回落成单遍 —— 单遍在这条链路上就是「静默丢内容」,宁可响亮地失败。

验证

node selftest.mjs # 39 个用例:纯函数 + 分片 + 注册契约 + 无损 JSON,不联网
node e2e.mjs "https://www.bilibili.com/video/BV..." # 走真实工具入口跑完一次完整转写
node e2e.mjs # 缺省跑那个 69 分钟视频(修复前产出 0 字)

e2e.mjs 除了跑完一次真实转写,还会断言:长音频必须走分片路径、文稿量达到健康水平、片段文件已清理、以及片段列表里塞一个不存在的文件时整篇文稿不会被毁掉、也不会静默少一段。

selftest.mjs 里的注册契约用例是刻意的:宿主缺 output 元数据会拒绝注册,而单测的假 register 不校验,所以那几条断言是自己在把关。

实测数据(参考)

| 项 | 值 |
| 视频 | 16 分 41 秒 中文播客(音轨 AAC 44.1 kHz 立体声,8.58 MiB) |
| 下载 + 转码 + 转写总耗时 | 178 秒(约音频时长的 0.18×) |
| 产出 | 12,192 字符 / 2,396 词 |
| 69 分钟视频(分片后) | 137 秒内出 25,500 字符,峰值内存 6.9 GB |
| 推理引擎 | sherpa-onnx v1.13.8 静态版 + SenseVoice int8,8 线程 |
| 云端调用 | 零 |

安装到 profile

插件走标准的 bundle 机制:profile 的 package.json 里加依赖 + 加进 dsh.profile.bundles ——本插件自带 dsh.bundle.patch,会自己 insert 那一行,不需要手写 cordis.patch.yml。

{
"dependencies": {
"dsh-video-transcribe": "link:<本仓库绝对路径>" },
"dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "dsh-video-transcribe"] } }
}
然后在该 profile 目录跑 pnpm install(link 依赖不联网),重启实例生效。

注意:桌面版(Electron)的 desktop profile 由应用独占管理,dsh plugin --profile desktop 会被 CLI 显式拒绝。写 package.json 的方式有效,但插件要重启桌面版才会上线。

设计约定

- 只 import node: 内置模块,不 import 任何 @deepseek-ai/(第三方插件解析不到,本地副本会造成双实例)。
- 工具返回值必须是「无损 JSON」。宿主会校验,一个值为 undefined 的键就让整次调用被判 value is not lossless JSON 而拒收 —— 注意这是运行时校验,假 register 的单测覆盖不到(本插件第一版就是这样在真机上被打回来的)。所以所有出口统一过 compact(),而不是在每个构造点记得删字段;selftest.mjs 里有一条对真实磁盘作业的无损 JSON 断言守着它。
- 子进程参数一律走数组、不经 shell,所以 URL 里的引号不会变成命令注入。
- URL 只放行 http(s),挡掉 file: 一类本地读取面。
- 任何一步失败都返回结构化错误码(download_failed / segment_failed / transcribe_empty …),不是一句「失败了」。
- 静默丢内容比报错更糟 —— 这是本插件最重要的一条设计原则。 具体落成四件事:
1. 长音频强制分片(整段交给引擎就会静默丢内容);
2. 分片失败时直接失败,不回落成单遍(宁可响亮地失败,也不产出残缺文稿);
3. 事后体检:产出密度低于 lowYieldThreshold、或有 ≥10 秒的片段一个字都没出来,就在结果里打 ⚠️ —— 只告警不判失败,因为误报一条告警的代价远小于让模型把残缺文稿当完整文稿用;
4. 每片字数落盘到 segments.json,异常一眼可见。
- 分片是独立模块 segment.js,里面全是可单测的纯函数(planSegments / parsePcmWav / buildPcmWav / joinSegments),不掺任何子进程或网络。

开发笔记:改了代码怎么生效

DSH 的 hmr 服务能监听插件目录并热重载 Host 模块,但需要 profile 显式配好 base / root:

- id: hmr
name: '@deepseek-ai/dsh-hmr'
config:
base: <本插件目录的绝对路径>
root: ["."]

没配的话(hmr.root 为空),整个进程会缓存这份 Host 模块:改 index.js 不起作用,重启才会重新 import。注意 link: 依赖让源码改动立即可见,但那只是文件层面的可见 —— 模块缓存归模块缓存,两者是两回事。

另外确认:桌面版(Electron)的 desktop profile 由应用独占管理,dsh plugin --profile desktop 会被 CLI 显式拒绝;写 package.json 的方式有效,但同样要重启桌面版才会上线。
预览分享卡片长什么样 ↓wqzhellohhwy/dsh-video-transcribe
分享 · DeepSeek Harness Hub
wqzhellohhwy/dsh-video-transcribe

贴视频链接,本机下载音轨并离线转成文稿

回到顶部