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

视频蒸馏 yiz-dev/dsh-video-distill

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

拆解视频口播与镜头结构,套用模板生成同款文案

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

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

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

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么[目录]—
dsh 原生插件 · other
暂无时间戳·判据 →
装得上吗[实装验证]—
本站尚未做安装检查
暂无时间戳·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 7 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

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

验证判据与等级说明 →

可信度证据

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

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

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 85 处,展示前 69 条)
  • 读取本地文件
    index.js:39
    try { return String(JSON.parse(readFileSync(join(PLUGIN_DIR, 'package.json'), 'utf8')).version || '') } catch { return '' }
  • 读取本地文件
    index.js:93
    try { workCount = readdirSync(WORK_DIR).length } catch { workCount = 0 }
  • 读取本地文件
    index.js:99
    dirExists: cfg.dir ? existsSync(cfg.dir) : false,
  • 读取本地文件
    index.js:251
    if (!existsSync(dir)) return { ok: false, error: `目录不存在:${dir}`, hint: '让使用者确认路径(Windows 路径要带盘符,别用 ~)' }
  • 读取本地文件
    index.js:253
    try { st = statSync(dir) } catch (e) { return { ok: false, error: `读不了这个目录:${e?.message ?? e}` } }
  • 写入本地文件
    index.js:400
    try { mkdirSync(folder, { recursive: true }) } catch (e) { return { ok: false, error: `建不了目录:${e?.message ?? e}`, dir: folder } }
  • 读取本地文件
    index.js:411
    while (existsSync(file)) {
  • 写入本地文件
    index.js:417
    writeFileSync(file, body, 'utf8')
  • 读取本地文件
    tools/cli.mjs:98
    if (!existsSync(dir)) return out(`目录不存在:${dir}`)
  • 读取本地文件
    tools/cli.mjs:99
    if (!statSync(dir).isDirectory()) {
  • 读取本地文件
    tools/cli.mjs:119
    if (!existsSync(file)) return out(`文件不存在:${file}`)
  • 写入本地文件
    lib/env.mjs:47
    try { mkdirSync(d, { recursive: true }) } catch { }
  • 读取本地文件
    lib/env.mjs:55
    return existsSync(win) ? win : existsSync(nix) ? nix : win
  • 读取本地文件
    lib/env.mjs:67
    if (!existsSync(py)) return { ready: false, python: '', reason: 'venv 还没建' }
  • 读取本地文件
    lib/env.mjs:69
    try { mt = statSync(py).mtimeMs } catch { mt = 0 }
  • 读取本地文件
    lib/env.mjs:161
    const raw = readFileSync(CONFIG_FILE, 'utf8')
  • 写入本地文件
    lib/env.mjs:186
    writeFileSync(CONFIG_FILE, JSON.stringify(next, null, 2) + '\n', 'utf8')
  • 读取本地文件
    lib/env.mjs:198
    const j = JSON.parse(readFileSync(SECRET_FILE, 'utf8'))
  • 读取本地文件
    lib/env.mjs:207
    try { Object.assign(j, JSON.parse(readFileSync(SECRET_FILE, 'utf8'))) } catch { }
  • 写入本地文件
    lib/env.mjs:210
    writeFileSync(SECRET_FILE, JSON.stringify(j, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 })
  • 执行外部命令来自配置/变量
    lib/env.mjs:227
    const r = spawnSync(cmd, args, { encoding: 'utf8', timeout, windowsHide: true })
  • 执行外部命令来自配置/变量
    lib/env.mjs:242
    const r = spawnSync(process.platform === 'win32' ? 'where' : 'which', [name], { encoding: 'utf8', timeout: 6000, windowsHide: true })
  • 执行外部命令来自配置/变量
    lib/env.mjs:315
    const v = spawnSync(cmd, [...args, '-c', 'import sys;print("%d.%d"%sys.version_info[:2])'], {
  • 写入本地文件
    lib/env.mjs:352
    mkdirSync(dirname(file), { recursive: true })
  • 写入本地文件
    lib/env.mjs:353
    writeFileSync(file, JSON.stringify(value, null, 2) + '\n', 'utf8')
  • 执行外部命令来自配置/变量
    lib/media.mjs:24
    const r = spawnSync(cmd, args, {
  • 读取本地文件
    lib/media.mjs:53
    try { entries = readdirSync(d, { withFileTypes: true }) } catch (e) {
  • 读取本地文件
    lib/media.mjs:66
    try { st = statSync(p) } catch { continue }
  • 写入本地文件
    lib/media.mjs:153
    mkdirSync(join(outWav, '..'), { recursive: true })
  • 读取本地文件
    lib/media.mjs:160
    const size = statSync(outWav).size
  • 写入本地文件
    lib/media.mjs:189
    mkdirSync(framesDir, { recursive: true })
  • 读取本地文件
    lib/media.mjs:200
    try { if (statSync(out).size > 512) got.push({ t, index: i + 1, path: out }) } catch { }
  • 写入本地文件
    lib/media.mjs:215
    mkdirSync(join(sheetPath, '..'), { recursive: true })
  • 删除本地文件递归
    lib/media.mjs:251
    try { rmSync(dir, { recursive: true, force: true }) } catch { }
  • 读取本地文件
    lib/media.mjs:256
    try { return readdirSync(dir).length } catch { return 0 }
  • 读取本地文件
    lib/distill.mjs:114
    if (!existsSync(file)) return { ok: false, error: `文件不存在:${file}` }
  • 读取本地文件
    lib/distill.mjs:116
    try { st = statSync(file) } catch (e) { return { ok: false, error: `读不了文件:${e?.message ?? e}` } }
  • 读取本地文件
    lib/distill.mjs:127
    if (!opts.force && existsSync(bundleFile)) {
  • 写入本地文件
    lib/distill.mjs:140
    mkdirSync(workDir, { recursive: true })
  • 写入本地文件
    lib/distill.mjs:244
    writeFileSync(join(workDir, 'transcript.md'), renderTranscriptMd(bundle), 'utf8')
  • 写入本地文件
    lib/distill.mjs:247
    writeFileSync(join(workDir, 'bundle.json'), JSON.stringify({ ...bundle, briefing: undefined }, null, 2) + '\n', 'utf8')
  • 删除本地文件
    lib/distill.mjs:253
    try { unlinkSync(wavPath) } catch { }
  • 读取本地文件
    lib/asr.mjs:90
    if (existsSync(p)) {
  • 读取本地文件
    lib/asr.mjs:96
    try { entries = readdirSync(dir) } catch { entries = [] }
  • 读取本地文件
    lib/asr.mjs:109
    if (statSync(p).size > 2 * 1024 * 1024) return null
  • 读取本地文件
    lib/asr.mjs:110
    const raw = readFileSync(p, 'utf8')
  • 读取本地文件
    lib/asr.mjs:131
    if (r.status !== 0 || !existsSync(outSrt)) {
  • 读取本地文件
    lib/asr.mjs:134
    const parsed = parseTimedText(readFileSync(outSrt, 'utf8'))
  • 发起网络请求
    lib/asr.mjs:242
    res = await fetch(st.endpoint, {
  • 读取本地文件
    lib/install.mjs:37
    const txt = readFileSync(INSTALL_LOG, 'utf8')
  • 读取本地文件
    lib/install.mjs:44
    if (!existsSync(py)) return { ready: false }
  • 写入本地文件
    lib/install.mjs:52
    appendFileSync(INSTALL_LOG, s, 'utf8')
  • 执行外部命令来自配置/变量
    lib/install.mjs:68
    child = spawn(cmd, args, {
  • 读取本地文件
    lib/install.mjs:121
    if (!existsSync(venvPython())) {
  • 读取本地文件
    lib/install.mjs:123
    if (code !== 0 || !existsSync(venvPython())) {
  • 读取本地文件
    lib/install.mjs:217
    if (existsSync(dir)) modelBytes = dirSize(dir)
  • 读取本地文件
    lib/install.mjs:220
    try { logBytes = existsSync(INSTALL_LOG) ? statSync(INSTALL_LOG).size : 0 } catch { logBytes = 0 }
  • 写入本地文件
    lib/install.mjs:247
    export function ensureLogDir() { mkdirSync(LOG_DIR, { recursive: true }) }
  • 写入本地文件
    lib/install.mjs:253
    appendFileSync(join(LOG_DIR, 'runtime.log'), `${new Date().toISOString()} ${line}\n`, 'utf8')
  • 读取本地文件
    lib/guide.mjs:18
    if (!existsSync(GUIDE_FILE)) {
  • 读取本地文件
    lib/guide.mjs:27
    const mtime = statSync(GUIDE_FILE).mtimeMs
  • 读取本地文件
    lib/guide.mjs:29
    cache = { mtime, text: readFileSync(GUIDE_FILE, 'utf8') }
  • 读取本地文件
    tools/e2e.mjs:45
    if (!existsSync(target)) {
  • 读取本地文件
    tools/selftest.mjs:34
    ok(existsSync(join(ROOT, f)), f)
  • 读取本地文件
    tools/selftest.mjs:39
    const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'))
  • 读取本地文件
    tools/selftest.mjs:49
    ok(existsSync(join(ROOT, p)), `files 里列的 ${p} 真的存在`)
  • 读取本地文件
    tools/selftest.mjs:55
    for (const e of readdirSync(dir, { withFileTypes: true })) {
  • 读取本地文件
    tools/selftest.mjs:66
    const src = readFileSync(f, 'utf8')
  • 读取本地文件
    tools/selftest.mjs:80
    const guide = readFileSync(join(ROOT, 'prompts/distill.md'), 'utf8')
扫描于 2026/11/4(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库

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

社区实装还没有人打卡 · 装过的第一个就是你
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

DSH 插件「视频蒸馏」:读目录里的视频(口播转写 + 关键帧 + 镜头切点),拆成结构模板并生成同款文案。

README

由 DeepSeek 最新模型翻译生成
dsh-video-distill · 视频蒸馏(DSH 插件) 你说:「D:\videos\口播样例 里有一些视频,看看人家怎么做的,给我写几条同款文案。」 它会:读视频 → 转写口播 → 抽关键帧 → 数镜头切点 → 拆成结构模板 → 按模板写新文案。 想直接装? github:Mrliu12345/dsh-video-distill —— 粘到 DSH「设置 → 插件 → 添加」,重启。 详细步骤(含连不上 GitHub 时的替代装法、装完要补什么):INSTALL.md。 它到底做了什么 一条视频被「拆开」成四叠材料,交给 DSH 的模型去理解: | 材料 | 从哪来 | 模型拿它干什么 | | 口播全文 + [mm:ss] 时间轴 + 语速 | 同名字幕 / 内嵌字幕 / 本地 faster-whisper / 兼容 OpenAI 的转写 API | 拆钩子、拆结构、学语言指纹 | | 关键帧 6 张 + 一张拼图 | ffmpeg 均匀抽帧(避开片头片尾) | 看构图/字幕样式/出镜方式/BGM 情绪 | | 镜头切点时间表 | scene 检测 | 判断节奏是「快剪」还是「长镜头」 | | 元数据 | ffprobe | 时长、竖屏横屏、帧率、码率、有没有人声 |
展开完整文档(README 剩余 1 节)
dsh-video-distill · 视频蒸馏(DSH 插件)

你说:「D:\videos\口播样例 里有一些视频,看看人家怎么做的,给我写几条同款文案。」
它会:读视频 → 转写口播 → 抽关键帧 → 数镜头切点 → 拆成结构模板 → 按模板写新文案。

想直接装? github:Mrliu12345/dsh-video-distill —— 粘到 DSH「设置 → 插件 → 添加」,重启。
详细步骤(含连不上 GitHub 时的替代装法、装完要补什么):INSTALL.md。

它到底做了什么

一条视频被「拆开」成四叠材料,交给 DSH 的模型去理解:

| 材料 | 从哪来 | 模型拿它干什么 |
| 口播全文 + [mm:ss] 时间轴 + 语速 | 同名字幕 / 内嵌字幕 / 本地 faster-whisper / 兼容 OpenAI 的转写 API | 拆钩子、拆结构、学语言指纹 |
| 关键帧 6 张 + 一张拼图 | ffmpeg 均匀抽帧(避开片头片尾) | 看构图/字幕样式/出镜方式/BGM 情绪 |
| 镜头切点时间表 | scene 检测 | 判断节奏是「快剪」还是「长镜头」 |
| 元数据 | ffprobe | 时长、竖屏横屏、帧率、码率、有没有人声 |

然后按插件自带的手册(prompts/distill.md)做十项拆解 → 抽出一个可复用模板 → 生成 3 条故意不同的同款文案(同骨架 / 同钩子换骨架 / 同人群换切口)。

装

1. 装进当前 profile(link 方式:改代码立刻生效,不用重装) <本目录> = 你 clone 下来的这个仓库的绝对路径
dsh plugin --profile desktop add link:<本目录>

2. ⚠️ 必须重启一次 DSH(bundle 层要重启才组装,不重启插件不生效)

重启后在对话里直接说人话就行,不用记工具名:

「D:\videos\样例 里有两个视频,先看看有什么,然后挑一条拆给我看,再写三条同款文案。」

没有 dsh CLI 时,两步手动装:

1. 让 profile 认得到插件(junction 或整个目录复制过去) New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-video-distill" -Target "<本目录的绝对路径>"

2. profile 的 cordis.patch.yml 末尾追加一条
- id: video-distill
name: dsh-video-distill

装完想确认插件真的活了,跑这两条(不依赖 DSH):

node tools/selftest.mjs # 187 项:导出、工具 schema、依赖洁癖、假 ctx 真装载
node tools/e2e.mjs <一个放视频的目录> # 真调 7 个工具跑一遍

依赖

| 依赖 | 必须? | 说明 |
| ffmpeg + ffprobe | ✅ 必须 | 抽音轨/抽帧/数切点全靠它。winget install Gyan.FFmpeg,或在配置里写 ffmpegPath。插件会自动在 PATH 和几个常见安装位置找。 |
| 转写 | ✅ 必须(否则只能看画面) | 四条路任选,见下。 |
| Python ≥ 3.9 | 可选 | 只有「本地转写」需要;插件会自己建 venv,不动系统 Python。 |
| Node ≥ 22 | ✅ | DSH 本身的要求。 |

转写的四条路(backend=auto 按顺序试)

1. sidecar — 视频旁边有同名 .srt/.vtt/.ass/.json。最快最准(人写的),零依赖。
2. embedded — 字幕压在容器里(mkv/mp4 常见),ffmpeg 一条命令抽出来。
3. local — 本地 faster-whisper。一次装好,永久离线:在对话里说「装一下本地转写」,或 node tools/cli.mjs asr-install --wait。
默认 small 模型(中文口播够用,约 500MB,装完整套 ~1.5GB)。
4. api — 任何兼容 OpenAI /v1/audio/transcriptions 的服务(硅基流动、阿里、本地 whisper-server 都行):

// 在对话里说,或直接改 $DSH_HOME/video-distill/config.json { "apiBaseUrl": "https://api.siliconflow.cn/v1", "apiModel": "FunAudioLLM/SenseVoiceSmall" } Key 存 $DSH_HOME/video-distill/secret.json(工具只写不读,返回值里是打码的),也可以用环境变量 DSH_ASR_KEY。

🔴 四条都不通时它会明说「口播没拿到」并给出修法,绝不给空文本 —— 那会让模型对着空气编文案。

用(对话示例)

你:D:\videos\口播样例 里有些视频,先看看有什么 DSH:video_scan → 2 条,一条 20 秒竖屏有声,一条 16 秒竖屏带字幕

你:把带字幕那条拆给我看 DSH:video_distill + video_guide → 十项拆解 + 可复用模板(读完还要 read_image 看拼图)

你:照这个模子写三条文案,存下来 DSH:video_save → out/2025-xx-xx/<标题>.md

产出的文件:

$DSH_HOME/video-distill/
├── config.json 配置(改了就生效,不用重启) ├── secret.json 转写接口的 Key(不进日志) ├── work/<视频名>-<指纹>/ 每条视频的材料 │ ├── transcript.md ⭐ 带时间轴的口播(模型主要读这个) │ ├── transcript.json 逐段起止时间 │ ├── meta.json ffprobe 元数据 │ ├── frames/frame-01.jpg… 关键帧 │ ├── sheet.jpg 关键帧拼图 │ └── bundle.json 索引(下次命中缓存) ├── out/<日期>/<标题>.md ⭐ 拆解 / 文案成稿 └── logs/ 运行日志 + 安装日志

配置

改配置不用编辑文件,直接说就行(模型会调 video_config):

| 项 | 默认 | 说明 |
| dir | 空 | 默认视频目录 |
| backend | auto | auto/sidecar/embedded/local/api/none |
| language | zh | 转写语言 |
| modelSize | small | 本地模型:tiny/base/small/medium/large-v3 |
| frames | 6 | 每条视频抽几张关键帧(1–16) |
| sceneDetect | true | 数不数镜头切点 |
| sceneThreshold | 0.3 | 切点阈值(越大越保守) |
| outDir | 空 | 成稿目录(默认 $DSH_HOME/video-distill/out) |
| apiBaseUrl / apiModel | 空 / whisper-1 | 云端转写 |
| maxVideos | 20 | 一次扫目录的上限(防误扫整盘) |
| minSec / maxSec | 3 / 1800 | 太短太长的不看 |
| ffmpegPath / ffprobePath / pythonPath | 空 | 自动找不到时手动指 |

命令行(不开 DSH 也能用,也是自测入口)

node tools/cli.mjs status --verbose # 体检:ffmpeg / 转写 / 目录 / Python
node tools/cli.mjs scan D:\videos\样例 # 清单
node tools/cli.mjs distill D:\videos\a.mp4 # 拆一条(--force 重跑,--json 拿原始结果)
node tools/cli.mjs guide # 手册原文
node tools/cli.mjs asr-install --wait # 装本地转写并等它装完
node tools/selftest.mjs # 离线自检(187 项,不联网)
node tools/e2e.mjs D:\videos\样例 # 端到端演练:真调 7 个工具跑一遍

常见问题

「口播没拿到」 → node tools/cli.mjs status 看两件事:ffmpeg 在不在、local.ok / api.ok。缺什么补什么。

切点数是 0,但明明有换镜 → 把 sceneThreshold 调到 0.2;纯色/慢推镜的 scene 分天然偏低。

第一次转写卡住很久 → 在下载模型(约 500MB)。asr-install 默认会预下;如果当时没下成,第一次转写会自己再下一次。看 logs/asr-install.log。

视频有字幕但抽不出来 → 硬字幕(烧在画面里的字)不是字幕流,抽不出来;那是「看画面」的事,模型靠关键帧读。

磁盘占用 → 每条视频的材料 1–3MB(帧 + 拼图)。清理:删 $DSH_HOME/video-distill/work/ 里对应目录即可。

设计上的三条自我约束

1. 零第三方依赖:dependencies 是空的,连 @deepseek-ai/dsh-tools 都不用。桌面版配置档经常一个 @deepseek-ai/* 都没有,裸 import 会让整条插件静默不激活(工具全不出现)。
工具定义自己造(lib/toolkit.mjs:扁平参数规格 → JSON Schema)。
2. 插件包只读:一切运行数据在 $DSH_HOME/video-distill/,升级/重装不丢。
3. 不编内容:转写/抽帧失败都如实报告 + 给修法,且失败原因一条条摊开(不是一句套话); video_distill 返回值里带 briefing 告诉模型「这次哪些材料可用、哪些不可用」。

许可

MIT
预览分享卡片长什么样 ↓yiz-dev/dsh-video-distill
分享 · DeepSeek Harness Hub
yiz-dev/dsh-video-distill

拆解视频口播与镜头结构,套用模板生成同款文案

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部