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

视频分镜宫格 SAOS-3624/dsh-video-storyboard

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

速览版在 README.md三分钟读完。这份是长版:设计取舍、实测基线、和其他插件的对比。

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

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

自动兼容检查有告警:未发布到 npm registry,仅可从源码安装。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add SAOS-3624/dsh-video-storyboard
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · vision
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 0 天前
档位由哪些信号合成

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查⚠ 装前注意

未发布到 npm registry,仅可从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包dsh-video-storyboard(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=20 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/27 23:40:12

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

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

可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
涉及外部主机:api.bilibili.com

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

依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-tools
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

DeepSeek Harness 的场景感知分镜:9-16 个带编号的格子,含时间戳、转录文本和音频包络,压进一张图(384 token)。两个工具:video_storyboard 和 video_zoom。

README

由 DeepSeek 最新模型翻译生成
dsh-video-storyboard · 中文长版说明 速览版在 README.md(三分钟读完)。这份是长版:设计取舍、实测基线、和其他插件的对比。 给纯文本模型装一双"能翻页的眼睛"。 输入一个视频,输出一张可寻址的宫格图:每格烧着编号、对应一段时间、下面挂着那段时间里说过的台词;底部还有一条与画面同时间轴的音频包络。模型因此可以「看」、可以「引用第 5 格」、可以「再放大看这 3 秒」,而不是被动接受一段别人写好的摘要。 社区项目,与 DeepSeek 官方无关。 效果(实测数字):5 分钟片子 → 一张图,9–16 帧只花 384 token;逐帧方案同样覆盖要 3,072–6,144。 90 秒 PV 扫描 9 秒,出 12 格、对齐 41 个切点。同一文件第二次调用 3.6 秒(命中缓存)。 不需要模型权重、不需要图像库、不需要字体配置。 详细的安装与使用教程:docs/USAGE.zh.md (每个参数、六种工作流、故障排查、成本估算一页看完) 装完用不了?先跑这一条 node bin/doctor.mjs 它会把 ffmpeg / yt-dlp / ASR / 缓存目录挨个查一遍,每条后面直接跟"你要做什么"。 用户反馈里一半以上不是 bug,是没配 ASR、或者装完 ffmpeg 没重启 DSH。
展开完整文档(README 剩余 1 节)
dsh-video-storyboard · 中文长版说明

速览版在 README.md(三分钟读完)。这份是长版:设计取舍、实测基线、和其他插件的对比。

给纯文本模型装一双"能翻页的眼睛"。

输入一个视频,输出一张可寻址的宫格图:每格烧着编号、对应一段时间、下面挂着那段时间里说过的台词;底部还有一条与画面同时间轴的音频包络。模型因此可以「看」、可以「引用第 5 格」、可以「再放大看这 3 秒」,而不是被动接受一段别人写好的摘要。

社区项目,与 DeepSeek 官方无关。

效果(实测数字):5 分钟片子 → 一张图,9–16 帧只花 384 token;逐帧方案同样覆盖要 3,072–6,144。
90 秒 PV 扫描 9 秒,出 12 格、对齐 41 个切点。同一文件第二次调用 3.6 秒(命中缓存)。
不需要模型权重、不需要图像库、不需要字体配置。

详细的安装与使用教程:docs/USAGE.zh.md (每个参数、六种工作流、故障排查、成本估算一页看完)

装完用不了?先跑这一条

node bin/doctor.mjs

它会把 ffmpeg / yt-dlp / ASR / 缓存目录挨个查一遍,每条后面直接跟"你要做什么"。
用户反馈里一半以上不是 bug,是没配 ASR、或者装完 ffmpeg 没重启 DSH。

- 手把手第一次:docs/入门-五分钟.zh.md(每一步都写了"你应该看到什么")
- 报错原话对照:docs/FAQ.zh.md(17 条,全部来自真实反馈)

现在可以直接贴链接

input 不再只认本地路径,B 站链接(以及任何 yt-dlp 支持的站点)直接传:

video_storyboard({ input: "https://example.com/some-video" }) // 任意 yt-dlp 支持的站点

取流不是"下完再分析",而是两级:扫描用一份 360P 全片(5 分钟约 7 MB),video_zoom 则回到原始流按帧取 1080P,不落盘。90 秒片子实测:下载 4.5 s + 扫描出 9 格 9.1 s,一次 6 格 zoom 5.3 s。

需要两样外部东西:yt-dlp(不打包,用户自己装)和一份 cookies.txt。
后者是必需的——同一条视频匿名最高 480P、登录才到 1080P,而且匿名时 1080P 根本不在格式列表里,下游没有任何信号能察觉自己被降级了。

配置见 docs/SETUP.zh.md(装 yt-dlp、导出 cookies、五步验证、故障对照)。

想在桌宠里用?

有人问能不能装到 Coopanion(基于 Cortico 的桌宠)。已经有人在 npm 上做了:

cortico-world-video-storyboard —— 作者 huahuahuaze,思路来自这个项目。独立实现,功能比我们能提供的还全:Windows 自动下载 ffmpeg 和 yt-dlp、带配置页、长片多图分段、可选本地语音识别。

我们没有再发一个同类扩展。 两个包做同一件事只会让装的人困惑,而且我们那版只做了最小功能。

如果哪天需要"严格跟上游同步"的版本(而不是一份复制出去、不再跟随本仓库的实现),成本其实不高:依赖本包 + 接一层 Cortico World 就够。契约和已经踩过的点记在 docs/cortico-world-notes.md,照着写回去大约半小时。

设计原点(三句话,定了大半架构)

| 你的直觉 | 落到代码里 |
| 「先看台词,再决定怎么分」 | 音频与台词在格数决策之前算完;字/秒作为第三条密度信号参与格位分配——话密的地方格短。注意精确说法是"先拿到台词再决定每格覆盖多久",切换点本身仍是画面信号 |
| 「取帧要挑最清晰的,别被模糊转场坑」 | 段内取 3 个候选,比 Laplacian 方差取最锐。ffmpeg thumbnail 按直方图挑帧,而模糊不改变直方图——它会心安理得地选中一张糊掉的人员表 |
| 「快闪的地方要多给格,但别让半秒镜头吃掉一整格」 | 快切区格短帧多;minGap 不只固定 1.2 秒,还会被素材自身粒度压住(不超过中位段长的 0.9 倍),否则六个 1 秒镜头会被并成三个、凑不够格数 |

算法做了什么(8 条,每条都是被真实素材打脸后改的)

| # | 做法 | 为什么不用那个更简单的做法 | 代码 |
| 1 | HSV 三通道差做场景切换检测(64×36,\|ΔH\|+\|ΔS\|+\|ΔV\| 取均值) | 只看亮度会漏掉"同亮度换色"的切点(测试片 9 个切点里 2 个是这种;ffmpeg scdet 找到 4/9,我们 9/9) | src/scan.js |
| 2 | 阈值按"速率"反推:目标约 1.5 秒一个切点,分数下限 12 | 3×p95 在 B-roll 上给 38(正常),在快剪动画上给 130 → 一个切点都没有,正好死在最需要多格的内容上 | src/scan.js |
| 3 | 格位 = 局部剪辑率积分的等分点,并吸附到真实剪辑点(位移 ≤ 理想格长 30%) | 均匀切会给"6 秒 20 刀"和"20 秒空镜"一样的空间;不设位移上限的吸附会把权重整个打乱 | src/density.js |
| 4 | 格数由画面运动 p90 决定,不由时长决定 | 静态单镜头给 16 格 = 16 张一样的图;PV 给 4 格 = 什么都没看清 | src/plan.js |
| 5 | 最小间隔不超过素材自身粒度(min(minGap, 中位段长×0.9)) | 固定 1.2 秒会把六个 1 秒镜头并成三个,然后就再也凑不够格数 | src/segments.js |
| 6 | 段内取最锐的一帧:3 个候选比 Laplacian 方差 | thumbnail=n= 按直方图挑帧,而模糊不改变直方图——它会心安理得地选中一张糊掉的人员表 | src/sheet.js |
| 7 | 音频包络画在同一条时间轴上,塞进同一张图 | 单独出一张音频图要多花 384 token,还丢了对齐——而对齐才是全部意义 | src/audio.js、src/sheet.js |
| 8 | ASR 按 ≤45 秒切片、切点落在静音中点,非语音段过滤并报出 | 299 秒单请求会把时间码整体压早 13 秒;纯音乐 PV 会吐出一条 29 秒的假台词、被贴到 5 个格子上 | src/asr.js、src/pipeline.js |

前人的工作、我们不打算超越的精度上限、以及刻意没做的事:docs/REFERENCES.md。

本工具产出的分镜

上图是它自己的输出:一段 27 秒的合成素材(9 个镜头、响—静—响的音频)→ 一张 9 格图 + 底部音频条带。
素材由 ffmpeg 现场生成,仓库里没有任何第三方视频画面。

作者自己 19 分钟视频的宫格

同一工具跑作者自己的 19 分钟视频:64 格分 4 张,这里展示第 17–32 格。底部黄线是片子里检出的切点。
这张图是作者本人的素材,仅作演示用途——见 NOTICE。

先澄清一个常见误解

市面上不少介绍会把这类工具说成"画面越乱分辨率越低"。本插件不是这样做的,而且刻意没这么做:

- 格子尺寸全都一样。快速剪辑的段落得到的是更短的时段(所以有更多帧),不是更小的格子。
- 原因是 token 账:一张图 384 token 是固定的,把格子缩小一半,模型看到的就是糊的——省不到钱,只丢信息。
- 时间轴全覆盖,但不保证每个镜头都被看到(5 分钟 300 个镜头塞进 16 格,物理上不可能)。要细节用 video_zoom。
- 连续运镜(快速摇镜、变焦)不是切点。任何帧差打分器(本工具、scdet、TransNetV2)都会给它高分,所以要做峰值抑制与碎段合并。

参数怎么选(0.6):只有三个旋钮

一张图的钱按像素算(42px 一格:rows×(cols+1)+2,地板 544²、封顶 994 @1300×1300),所以格子越多、每格越小——这也意味着:留白、黑边、把格子放得比需要的更大,都是直接烧钱。
0.6 不再让你指定"几格" —— 帧数由镜头决定,大小由切点密度决定:

| 参数 | 管什么 | 怎么选 |
| maxFrames | 最多看多少帧 | 默认 0 = 不限。一段 = 一个镜头,几千刀就几千帧;填正数才合并最短的段(长镜头不受影响) |
| strategy | 每帧多大 | auto(默认):张数按时长定(≈4 分钟一张),再挑"最小格还认得出、张数最少"的那档,并把三档代价写进清单 / 显式值 full 升一档 · standard 1 档压成 1/2 · compact 降一档 · skim 全 1/8 · brief 整段装进一张 |
| maxSheets | 最多几张图 | 默认 0 = 不限。排布器说几张就几张;填正数才合并段重排 |
| dryRun | 只预演 | 默认 false。只扫描+排布:回报切点数、帧数、每档要几张图,不出图不取帧不转写 |
| budget | 一张图多少像素 | 默认 360000 = 800×450(≈233 token)。上限 1.69M = 1300×1300(≈994 token):按像素线性计价,抬上去是加钱换清晰度;低于 ~296k 也不会更便宜(计费按 544² 放大算,192 token)—— 给更小的 budget 会被抬到地板,清单里写明原值 |
| segment | 分段多长(秒) | 只传 from,自动推下一段,结果回显 next: from=… |

auto 是 0.6.1 起的新默认:张数只按时长算(默认 ≈4 分钟一张图,DSH_STORYBOARD_SECONDS_PER_IMAGE 可调),再在 compact / standard / full 里挑"最小格还认得出、张数最少"的那档 —— 同一时长的片子花同样的钱,切点多少不影响预算。不想要就 DSH_STORYBOARD_AUTO=0,或 ~/.dsh/video.json 里
{"storyboard": {"auto": false}},退回 compact。清单里会写清 auto chose … 和"别的档要几张"。
不知道一部片有多少刀时,先 dryRun: true 问一次(扫描有缓存),再决定出不出图。

800 宽画布上每档的实际像素(16:9 源):

| 档位 | 格子 | 能看清 |
| 1(1/1) | 800×450 | 字幕、UI 文字 |
| 2(1/2) | 400×225 | 人脸、动作 |
| 4(1/4) | 200×113 | 构图、谁在画面里 |
| 8(1/8) | 100×56 | 色块 —— 只够看出"换镜头了" |

档位是算出来的:快剪区密度高 → 8 档小格;长镜头密度低 → 1 档大格。
由积分假切点切出来的帧额外 +2 档(连续运镜/渐变的段落,一帧代表不了多久)。
所以一张 30 帧的图里,前 5 格可能是大画面、后 24 格是豆腐块 —— 那正说明"这里开始快剪了"。

清单会直接写 cell>=100x56 (composition) 并在最小格看不清时报警:那时该换 strategy:"full"(每帧升一档),或者用 zoom 看那几帧。

叙事内容(剧情片、番剧、纪录片)默认分段看,不要先来一张全片总览:

video_storyboard({ input, from: 0, segment: 300 })
// → 结尾回显:segment 1/22 · 5% watched · 82 min left · next: from=300

总览只用于"先确认这是什么片",而且开了就等于接受剧透。

长片:文本清单也是有预算的

一张图 384 token,但文本清单是没有上限的——一部 2 小时 35 分的片子出 64 个 cell,清单 75 KB,超过宿主单次工具结果的上限,超出部分被折进磁盘上的溢出文件,模型没去读,于是中段 44 分钟的台词等于没给。
现在三件事堵住这个口子:

- 每格台词封顶,三档:detail: low 60 / med 120(默认)/ high 400 字符,且截断落在句末 (120 字的预算会停在窗口内最后一个「。」之后,而不是切在半句)。实测 9 格:1,521 / 1,915 / 3,031 字符;
- 在 cell 边界截断,并写明怎么续读: [text stopped at cell 13 of 64 — call again with cellRange: [14, 64] and textOnly: true to read the rest]
- cellRange + textOnly:翻页取文本,不重出图、不重解帧。

video_storyboard({ input, maxFrames: 96 }) // 多张图 + 第一页文本
video_storyboard({ input, maxFrames: 96, cellRange: [14, 42], textOnly: true })
video_storyboard({ input, maxFrames: 96, cellRange: [43, 64], textOnly: true })

textOnly 不返回图,所以翻页不花 token;扫描与 ASR 结果都缓存后,每页约 0.8 秒。

整片批处理(命令行)——一部两小时的片子要 24 个窗口,手敲 24 次 --from/--to 不叫工作流:

node bin/storyboard.mjs film.mp4 --batch 300 --out film.png
→ film-w01.png … film-w24.png,外加 film-timeline.md (窗口 | 格数 | 切点 | 字数 | 图片路径,一张表串成整条时间线)

尾巴短于 30 秒的窗口会并进前一个,而不是花 384 token 换一张只有 1 格的 2 秒图。
窗口边界会吸附到真实剪辑点(先全片扫一遍,--no-snap 可关),所以不会正好切在一句话中间。

一、它到底解决了什么问题

纯文本模型看不到视频。现有三条路各有硬伤:

| 做法 | 硬伤 |
| 抽 N 帧、N 张图发给模型 | 每张图 384 token,16 帧就是 6144。想看全片就破产 |
| 把帧发给外部视觉模型,回一段文字 | 回来的是别人替你看过的结论,不能追问、不能引用 |
| 只读字幕/ASR | 画面全丢。「这个镜头为什么好」这类问题永远答不了 |

我们走第四条:把 N 帧打进一张图。于是 384 token 就能看 9–16 帧,而且看的是当前这个模型自己。

二、六个真正不一样的地方

1. 打包,而不是逐帧 —— 同覆盖率下省 8–16 倍

| 方案 | 帧数 | 图数 | token |
| 本插件(9 格) | 9 | 1 | ≈390–990 |
| 本插件(16 格) | 16 | 1 | ≈390–990 |
| 逐帧插件 count=8 | 8 | 8 | 3,072 |
| 逐帧插件 count=64 | 64 | 64 | 24,576 |

代价是每帧分辨率:本插件一格约 68k 像素,逐帧方案每张可到 640k。这不是缺陷,是分工——先把"看多少帧"和"每帧多清楚"解耦,粗看用前者,细节用后者。

token 口径(照抄宿主 dsh-llm-deepseek 的实现):14px patch 对齐 → 42px 一格 → rows×(cols+1)+2,地板 544²(任何小图都按 192 收)、封顶 ≈994(1300²)。800×450 ≈ 233、1066×600 ≈ 407、1280×720 ≈ 578。
旧文档的 800²/384 和后来的"像素÷1650"都是近似,已被 scripts/verify-image-tokens.mjs 对拍纠正。
下文若还出现 "384 token",那是 800² 时代的旧数,按上面的新口径换算。

2. 可寻址 —— 编号烧进像素,不是排在文本里

三重冗余,任何一层失效都还能定位:烧进像素的编号(格高 15%)/文本坐标 cell 5 (r2c2, image 1)/精确时间码 frame@00:01:17.20。

为什么非烧进去:小模型数格子会数错(4×4 就开始飘),但读一个数字很准。做过下采样验证——整图降到每格 66×37,11% 格高的数字仍可辨认。

3. 选段:格位按内容密度分配,不是均匀切

均匀采样的隐蔽错误:20 秒的空镜和"6 秒里切了 20 刀"的高潮,拿到的格数一样,后者 19 个画面直接消失。

我们用三条信号叠出一条密度曲线,再在积分上等分:

| 信号 | 来源 | 作用 |
| 剪辑密度 | 局部切点率 / 全片平均率 | 快切区格短、帧多 |
| 台词密度 | 字/秒 | 话密的地方格短 |
| 声音事件 | 响度突变(+8 dB / −15 dB) | 画面没动但有事发生——突然安静、爆笑、音乐进副歌 |

第三条是帧差永远看不见的:口播片 299 秒里会触发 92 次(每句话的停顿),所以只在"话不密"的片子里启用;音乐 PV 只触发 7 次,正好是拍点。

4. 两段式:粗看 → 回看(video_zoom)

一张图 384 token 是固定的,所以清晰度和覆盖率零和。我们不硬凑,而是拆成两次:

第一次:9–16 格看结构(384 token)第二次:对感兴趣的 3 秒调 video_zoom(384 token,格子大 4 倍)

回看时还能进一步调:

- minGap: 0.4 —— 把最小间隔从默认 1.2 秒压到 0.4,半秒的镜头也各得一格
- maxFrames: 6 —— 少帧大图,字幕、人脸、画面里的字都能读

5. 选帧:比 Laplacian 方差,而不是取中点

段内取 3 个候选,选锐度最高的那帧。为什么不能省:

| 策略 | 问题 |
| 取段中点 | 溶解/淡入的中点拿到叠影 |
| ffmpeg thumbnail | 按直方图挑帧,而模糊不改变直方图——它会心安理得地选中一张糊掉的人员表 |
| Laplacian 方差 | 无 |

真实案例:某 PV 片尾人员表,thumb 在 84.88s 拿到高斯模糊帧(文字全糊),sharp 在 85.20s 拿到清晰帧。同一个秒级窗口。

反直觉的是它更快:三次 seek 各解 1 帧 ≈ 9.2s,而 thumbnail 要解码整段统计直方图 ≈ 11.3s。

6. 快闪蒙太奇:告诉模型"切点在哪、切得有多快"

三件事同时给:

| 给什么 | 长什么样 | 回答什么问题 |
| 位置 | 底部条带上的黄色刻度 | 在哪切的 |
| 数量 | 每格 cuts 7、表头 cuts="141 (avg 2.1s)" | 这段切得多快 |
| 可调粒度 | video_zoom({minGap: 0.4}) | 觉得切太粗,能重切 |

没有第二项时,刻度线一密,模型只能"感觉"很密、数不准;有了数字,"这是快闪蒙太奇"才是个可陈述的事实。

三、读法有两种,其中一种会剧透

一部两小时片子的总览图,会把片尾的画面和片头摆在同一屏上。这对"这是部什么片"很有用,对"接下来发生什么"是致命的。

| | 它做什么 | 什么时候用 |
| 结构读法 —— 全片 maxFrames: 96 | 几张大图,一次看完整条弧线;2.5 小时约 310 秒 CPU | 分析、索引、"值不值得看" |
| 顺序读法 —— from/to 窗口,一次 2–5 分钟 | 永远只看到你问到的那一段,后面的不会提前出现 | 追剧情、看悬念、逐镜头研究 |

video_probe 会把两种读法都列出来并标注哪种会剧透,不替你选。

工具里没有任何一处强制"先看全片":from/to 是主接口;每段的扫描结果都会缓存,所以第一遍读过之后,回头再看某一段是免费的。

四、长片的成本模型

读一部 2 小时 35 分的电影,不是"24 张图一次性铺开"(= 9216 token)。正确读法是分两级:

| 阶段 | 花什么 | 拿到什么 |
| ① video_probe | 0 | 时长、有无音轨/字幕、建议怎么读 |
| ② 一次总览(16 格盖全片) | 384 | 结构:哪几段值得细看(7.5 分钟/格) |
| ③ 只打开可疑的 3 个段落 | 3 × 384 | 每段 16 格细节 |
| 合计 | 1536 | 和"一次性 4 张图"同价,但前 384 就能开始判断 |

差别不在总价,在决策顺序:旧做法先付 1536 再看,新做法付 384 就知道该不该继续。给模型省钱的方式不是压缩单张图,而是让它早一点停止。

五、实现原理

整条链是一次解码、多路复用:扫描时只解码到 64×36,把切点、密度、音频、台词全部拿到,最后只为选中的格子各解 1–3 帧。

1. 探测

ffprobe 拿时长/帧率/旋转/音轨/字幕轨。两个细节:手机竖拍会带旋转元数据,要按显示方向算宽高比;片尾常有一帧黑场,取帧时避开最后一帧。

2. 切分:HSV 三通道差 + 速率法定阈值

把每帧缩到 64×36(2304 像素),转 HSV,与上一帧逐像素求 (|ΔH|+|ΔS|+|ΔV|)/3。选 HSV 而不是亮度差,是因为同亮度换色(红→蓝)在亮度域几乎为零:测试片里 9 个切点有 2 个是这种,ffmpeg -vf scdet 只找到 4/9,我们 9/9。

阈值不是固定值,也不是分数的倍数,而是按速率反推:

目标切点数 = min(帧数 × 25%, 时长 / gap) // gap ≈ 1.5s,短片收紧到 0.5s 阈值 = 分数排序第 (帧数 - 目标) 位,向下取整,且 ≥ 12

这条是被真实素材打脸后改的:第一版用 3 × p95,在 B-roll 上很准(38 分 → 88 切点),但在快剪动画上 p95 已经是 43,×3 = 130 → 0 个切点。越是需要多格的内容,倍数法越失效。

3. 分格:密度积分等分 + 吸附剪辑点

先按 0.1s 粒度建密度曲线(局部率要箱型平滑一遍,否则节奏均匀的片子会在每个切点附近鼓包、认出一堆假爆发),累积后按 总积分/格数 等分。切点会尝试吸附到真实剪辑点,但位移超过理想格长的 30% 就放弃——否则吸附会把权重分配整个打乱(踩过:全片被拉到剪辑点上,格长变得不均)。

4. 取帧:段内 3 候选,比 Laplacian 方差

| 策略 | 做法 | 问题 |
| mid | 取段中点 | 溶解/淡入的中点拿到叠影 |
| thumb | thumbnail=n= 取直方图最接近均值的帧 | 对模糊→清晰不敏感(模糊不改颜色分布) |
| sharp(默认) | 段内 3 个候选,比 Laplacian 方差取最大 | 无 |

真实案例:某 PV 的片尾人员表,thumb 在 84.88s 取到的是高斯模糊帧(文字全糊),sharp 在 85.20s 取到清晰帧——同一段、同一秒级窗口。

反直觉的性能结果:90s 片 / 9 格,mid 8.0s、sharp 9.2s、thumb 11.3s。锐度法比 thumbnail 还快,因为三次 seek 各解 1 帧,而 thumbnail 要解码整段才能统计直方图。

5. 拼图:零图像库、零字体依赖

取帧直接解成 rgb24,按 cell 尺寸做 cover 裁剪;宫格在普通 Buffer 上逐字节拼;编号用内置 5×7 点阵字模画;最后 -f rawvideo -pix_fmt rgb24 喂回 ffmpeg 出 PNG(或 -f image2pipe 直接给宿主)。

这一套把 Pillow / sharp / canvas / fontconfig 全部移出依赖图。最后那个不是洁癖:drawtext 需要字体系统,而 Windows 上的 fontconfig 经常是坏的——测试机上跑 ffmpeg -vf drawtext 直接报 Cannot load default config file。零字体依赖等于零这类事故。

6. 音频:条带与画面同轴

ebur128 抽瞬时响度(100ms/点)+ silencedetect 找停顿,然后画在宫格底部:青色柱是响度、深灰块是静音、黄色刻度是切点。

价值不在"能测响度",而在同一条时间轴:模型能把"第 5 格"和"这里最响"直接对上,不用自己做时间映射。成本是同一张图内 0 额外 token,只占约 9% 像素预算。

它回答"哪里在响",不回答"为什么响"。实测一支成片的响度全程只在 3 dB 内波动、与剪辑数相关 r=0.35——母带处理把动态压平了,靠响度找不出副歌。

7. 台词:三级降级 + 切片 + 幻觉过滤

字幕轨(免费、最准)→ OpenAI 兼容 ASR(本地/云)→ 都没有就跳过(分镜照出)

三个坑,都踩过:

| 坑 | 现象 | 处理 |
| 长请求漂移 | 单次 299s 请求,时间码整体压早 13s(≈4.5%,停顿被吃掉) | 客户端切 ≤45s,漂移降到 0.02s |
| 切片劈词 | 固定秒数切会把词切两半,段数凭空变多 | 切点吸附到静音中点 |
| 音乐段幻觉 | 纯音乐段吐出 29s / 9 字的假句子,按时间重叠被贴到 5 个格子上 | 时长 > 8s 且 < 0.8 字/秒 判为幻觉丢弃,并在输出里报出丢弃条数(不静默丢) |

台词最后按时间重叠贴到格子上——一句跨两格时两边都保留,宁可重复也不让某格"没话说"。字/秒还作为第三条密度信号喂回去:话多的地方格更短。

8. 缓存

键 = (路径, 大小, mtime, 窗口, 阈值策略)。不 hash 文件内容(2GB 读一遍比重新扫描还慢),不缓存响度(2 小时有 7 万个点)。实测 5 分钟片子:首次 13.4s → 第二次 3.6s。这样 video_storyboard 之后再 video_zoom,同一段不会扫两遍。

六、为什么是这些取舍

384 token/图 是硬约束,所以「格数」和「每格清晰度」是零和的。整个设计围绕一件事:把一次性的"看"拆成两段——先粗看结构,再按需放大。

| 需求 | 做法 |
| 这片子讲了什么 | 9–16 格概览(384) |
| 第 1:12 发生了什么 | video_zoom(from, to)(384) |
| 一句台词在哪一格 | 清单直接给 cell k = 时间码 |
| 音乐什么时候起来 | 底部条带(0 额外) |
| 一小时的电影怎么看 | --plan 300 分段,逐段 --from/--to |

七、实测基线

6 条 1080p 素材(B-roll / 动画正片 / 两支 PV / 特别 ED / 单镜头长take):

| 素材 | 时长 | p90 | 自动阈值 | 切点 | 平均间隔 | 自动格数 |
| B-roll | 299s | 7.9 | 16 | 141 | 2.1s | 16×1 |
| 动画正片 | 1090s | 15.0 | 43 | 408 | 2.7s | 16×2 |
| 特别 ED | 80s | 11.6 | 21 | 42 | 1.9s | 4×1 |
| PV(正式) | 92s | 17.0 | 54 | 44 | 2.1s | 9×1 |
| PV(概念) | 90s | 26.1 | 58 | 41 | 2.2s | 9×1 |
| 单镜头 | 251s | 2.2 | 12 | 1 | — | 4×1 |

扫描速度:22–47× 实时(1080p,瓶颈是 H.264 解码,不是打分)。

台词密度还能反过来当类型判据(响度与字/秒反向):

| 类型 | 字/秒 | 响度 |
| 口播教程 | 5.60 | −23 LUFS |
| 叙事动画 | 2.07 | −21.4 LUFS |
| 音乐 PV | 0.32 | −14.2 LUFS |

字/秒 < 0.8 且响度 > −18 基本可以判定"音乐驱动,别指望台词"。

八、边界:明确不做的事

- 不保证每个镜头都被看到,只保证时间轴不漏。
- 不做音乐理解。响度是测量,不是理解;情绪、曲风、副歌位置都不在范围内。
- 手机端未实测(设计上零原生依赖,Termux 理论可行)。
- 超长视频解码是瓶颈:2 小时片子首次约 5 分钟,降采样实测无收益(瓶颈在解码不在打分)。

九、后续改进方向

P0(下一步就做)

1. 四条素材基线表——把上面的表扩成同一张对照表(字/秒、响度、静音数、切点数四列),作为这个工具的第一份 benchmark。
2. video_probe 工具——只读容器头,几毫秒:时长/分辨率/有无音轨/切点密度。让模型先问再决定"9 格还是 16 格、要不要分段",省掉一次盲试的 384 token。

P1(ASR 侧,已有方案未落地)

3. 按内容类型自动分流——字/秒 < 0.8 && 响度 > −18 → 判定音乐片,多给格、少指望台词;口播片反之。这条已经有数据支撑,只差写进 suggest()。
4. 术语校对层——ASR 对专业名词必错(实测 彗差→绘插、八羽怪→巴与怪、收光圈→烧光圈)。计划:清单里保留原文 + 标注"ASR 输出,引用前对画面核对",并支持一份用户词典做替换。
5. 说话人分离——多人对话视频里,"谁在说"和"说了什么"一样重要。SenseVoice 不直接给 speaker,需要接 cam++ 之类的声纹模型。
6. 音乐段降级策略——现在只是过滤幻觉;更进一步可以:检测到"长段低字/秒 + 高响度"时,自动把该段的格子权重调低(画面变化才是信息),并把音频条带加高。

P2(工程化)

7. 抽样扫描——超长视频(>1 小时)每 N 分钟只扫 30s,把 5 分钟降到 30s;代价是失去全局密度感知,需标 degraded。
8. Termux 实测——目前只有设计推演。
9. npm 发布——名字 dsh-video-storyboard 已确认未被占用。
10. 多语言——目前中文口播效果最好;英文/日文的时间码与标点规则需要单独验证。

十、和其他视频插件怎么选

只想"看一眼这几帧" → 逐帧类插件(更简单、无需全片扫描)想让别的模型替我总结成一段话 → 桥接类插件(外部 VLM)想知道"整片怎么组织、第几分钟发生了什么、谁在说什么" → 本插件(video_storyboard + video_zoom)只想分析一首歌 → 音乐分析类插件

许可

MIT。不复制任何第三方源码;ffmpeg、模型权重、视频素材都不随仓库分发。ASR 默认对接用户自建的 OpenAI 兼容端点(如 SenseVoiceSmall,FunAudioLLM / 阿里,遵循 FunASR Model License v1.1,保留署名)。
预览分享卡片长什么样 ↓SAOS-3624/dsh-video-storyboard
分享 · DeepSeek Harness Hub
SAOS-3624/dsh-video-storyboard

速览版在 README.md三分钟读完。这份是长版:设计取舍、实测基线、和其他插件的对比。

回到顶部