分镜

Coopanion / Cortico 的 World:把视频变成可引用的分镜宫格图,附一份带时间码的清单。
本页所有截图与数字都是在本机真跑出来的(Windows 11 · Coopanion 0.2.17 · 同一支 B 站视频 0–60s)。

它能干什么

一支视频丢进来,出来一张分镜宫格图和一份带时间码的清单:每个格子是一个镜头,标着编号和时间范围,可以直接引用「cell 7 = 00:12.76–00:31.40」。

格子按镜头的稠密程度分配大小:一个 20 秒的长镜头给整幅宽,一串 0.3 秒的蒙太奇缩成 1/8 格。所以一张图能装下几十个镜头,又不会为了快剪白白浪费 token。

四个工具,各管一段:

工具干什么什么时候用
video_probe只读文件头:多长、多大、有没有字幕轨动手之前先看看这片子值不值得读
video_storyboard出宫格图 + 时间码清单,带本地转写与硬字幕 OCR想快速看懂一支片子、写摘要、做解说稿
video_locate只查台词,不取帧,纯文本返回「这片子哪一秒说到了 X」
video_zoom把某一格那几秒放大重读字幕、人名、小字看不清时

一个真数:一支 24 分钟影视解说,37 张图、14,208 token 就能把全片过一遍;同一支片子改用上游默认的切点检测,是 94 张图、36,096 token(1440 秒全片,分五段跑,两组参数相同)。

安装(从本机目录)

先完全退出 Coopanion,把仓库目录复制或 junction 到扩展目录,然后在 extensions/package.json 的 dependencies 里加一行:

# 1) 放进扩展目录
%LOCALAPPDATA%\Programs\Coopanion\data\extensions\node_modules\cortico-world-storyboard

# 2) extensions\package.json 里加依赖
"cortico-world-storyboard": "link:<path-to-this-repo>"

# 3) 启动 Coopanion → 设置 → World → 「分镜」→ 启用

外部程序不用手工装:服务页「一键安装(small)」分五项处理(whisper 运行时 / 模型 / Python venv / wheel 缓存 / 离线 pip),已存在的逐项跳过。OCR 需要 Python 3.11–3.13——这个 Python 只为装 rapidocr 用;可以自己装,也可以用别的方式装 rapidocr。

最常用的三条用法

// ① 幻灯片 / 讲课:关掉积分伪切点,一格一句话
{ "input": "D:/media/lecture.mp4", "integral": false }

// ② 底部有硬字幕的片子:用自带检测器 + 复用扫描结果
{ "input": "D:/media/bilibili.mp4", "detect": "own", "precomputed": "auto" }

// ③ 只要图、不要台词(最快)
{ "input": "D:/media/clip.mp4", "transcript": "off" }

input 可以贴完整链接、b23.tv 短链(QQ 卡片里的短链也行)或本地路径; 裸 BV 号不行(会被当成本地文件名交给 ffprobe)。

1. 积分伪切点:开 vs 关

同一支视频(0–60s)、同一套参数,只切 integral。true 时会在长镜头内部 按“画面变化量累积”补采样帧,这些时刻不是真切点,于是多出重复格。

integral:true 的分镜
integral: true(默认)· 69 帧 · 9 个伪切点 · 4 张图 · 4.8 s
integral:false 的分镜
integral: false · 58 帧 · 0 个伪切点 · 2 张图 · 4.3 s(真切点仍是 58 个,一个没少)

2. 切点检测:upstream vs own

同一支视频。上游检测对底部硬字幕敏感,字幕一换就被当成切点;detect:"own" 先裁掉底部字幕带, 再做直方图差 + 三点一致性 + 短镜头合并。

54upstream 切点 · 65 帧 · 3.5 s(含首次扫描落盘)
14own 切点 · 24 帧 · 1.8 s(含首次扫描落盘)
detect upstream 分镜
detect: "upstream" · 54 切点 / 65 帧 / 3 张图 · 3.5 s
detect own 分镜
detect: "own" · 14 切点 / 24 帧 / 2 张图 · 1.8 s

3. 分析资产复用(precomputed)

第一次扫描落盘(miss→built),第二次同一个窗口直接读盘(hit),不再解码视频。

调用状态耗时切点
第 1 次miss→built1.8 s14
第 2 次hit1.0 s14(完全一致)

窗口越大差距越明显:整片分析一次之后,换格数、换档位、换窗口都只是重新拼图。

4. OCR 把 ASR 错字对齐到画面上实际写的字

同一段 0–60s、detail: "high":ocr: false 13.3 s;ocr: true 31.4 s, 回执是 ocr: corrected 5 lines / 14 chars (engine=rapidocr, conf≥0.85)。 这一节留了一次**真实的 bug 复盘**:它最初把「神探」改成了「神判」,后来定位、修掉、并用回归断言锁住。

修前 vs 修后

修前修后
纠正行数 / 字数5 行 / 14 字5 行 / 14 字
完全正确(逐字一致)2 / 53 / 5
部分正确(替换对、漏首尾字)1 / 52 / 5
错(false correction)2 / 5(都是 探 → 判)0
字级一致61 / 6763 / 67

核心结论是“错的行从 2 降到 0”:5 行里 3 行完全正确、2 行部分正确(漏了画面上的首尾字,只能靠 A2 标记,不该硬补)。 加严规则(票差 ≥2 才改)让 45.0s 那行少改一个字,字级从 64 掉到 63——这是“宁可少改”的代价,不是退步。

那个 bug 是什么

原因:逐字投票之前用 lcs() 按拼音把 OCR 的字对齐到 ASR 的句子上。 ASR 写「审判敌人节」(pan)、画面写「神探狄仁杰」(tan)——拼音不同,LCS 配不上这一列, 于是这一列**没有票**,拼回时保留 ASR 的「判」,产出一句两个源都没有的混血句:前半来自 OCR、后半来自 ASR。

修法(原则是“宁可少改,不要错改”):

修前的逐行结果(留作对照,别当结论)

时间ASR 原文OCR 结果画面实际写的修前判定
0.4s看着棒学着棒学好记住看折棒学折棒学好记住看折棒,学折棒对(改对了人名)
17.4s可为什么第三个系列要选审判敌人节呢可为什么第三个系列要选神判狄仁杰呢…要选神探狄仁杰呢错(探→判)
36.4s但审判敌人节就是能做的成功但神判狄仁杰就是能做的成功但神探狄仁杰就是能做到成功错(探→判,且漏了「到」)
39.2s这部剧给大家展现了历史改变剧的上限这部剧给大家展现了历史改编剧的上限这部剧给大家展现了历史改编剧的上限对(逐字一致)
45.0s能知道敌人节这个名字能知道狄仁杰这个名字长能知道狄仁杰这个名字士部分对(替换对,首尾两字丢了)

修后的逐行结果

时间ASR 原文修后 OCR 结果画面实际写的判定
0.4s看着棒学着棒学好记住看折棒学折棒学好记住看折棒,学折棒对(尾字 ASR 保留)
17.4s可为什么第三个系列要选审判敌人节呢可为什么第三个系列要选神探狄仁杰呢…要选神探狄仁杰呢对(逐字一致)
36.4s但审判敌人节就是能做的成功但神探狄仁杰就是能做到成功但神探狄仁杰就是能做到成功对(逐字一致)
39.2s这部剧给大家展现了历史改变剧的上限这部剧给大家展现了历史改编剧的上限这部剧给大家展现了历史改编剧的上限对(逐字一致)
45.0s能知道敌人节这个名字能知道狄仁节这个名字长能知道狄仁杰这个名字士半:漏了「杰」(票数不够,宁可不改)

锁住它的两条回归断言

这两条已经进 scripts/check.mjs(纯函数、不需要 ffmpeg,npm run check 就会跑):

ok  OCR 对账:判→探 一类"拼音不同"的字也能纠正
ok  OCR 对账:两侧字数不等时不串位(多出的「这个」仍在原位)

另外四条真实样本断言(0.4s / 17.4s / 36.4s / 39.2s 的原句与拼音)也一起跑, 其中「看着棒→看折棒」必须仍然改对——防止修 bug 时把原来对的改动一起废掉。

样本很小:只有 1 段 60 秒、5 行被纠正,以上比例仅作参考,不能当总体成功率。 参数也没有美化:MIN_CONF = 0.75(逐字投票)、单字闸门 ≥0.85、 票差 ≥2、简繁归一(zhconv)+ 拼音对齐。OCR 结果默认只是建议,引用前请以画面为准。

要自己看原始像素,这五张是被裁出来对比的字幕条(15% padding、2× 放大):

D:\deepflash\out\ocr-evidence\sub-1-0.4s.png
D:\deepflash\out\ocr-evidence\sub-2-17.4s.png
D:\deepflash\out\ocr-evidence\sub-3-36.4s.png
D:\deepflash\out\ocr-evidence\sub-4-39.2s.png
D:\deepflash\out\ocr-evidence\sub-5-45.0s.png

三步判别法(当时用来确认“画面上到底是探还是判”):连续 3 帧(17.36 / 17.40 / 17.44s)整帧识别都是 神探狄仁杰、conf 1.000;同一条字幕条 LANCZOS 2× 放大后仍是 神探、conf 0.971。 三个尺度、四次识别全部是「探」——所以不是分辨率、不是裁切、不是模型认错。

5. 静态画面提示(不改行为,只提示)

画面几乎不动时,清单里会多出一行,建议重跑参数:

hint: 画面几乎不动(p50=0.00 · p90=0.00,63% 的格子来自积分伪切点)——这类幻灯片/讲课类视频用 integral:false 重跑会更整齐:一格一句话,不再有重复帧。
样本p50p90伪切点段占比是否提示
静态 PPT 类(120s,6 页)0.000.0062.5%出提示
动作类(testsrc2)2.934.2726.5%不出
真实影视 0–60s0.549.1128.4%不出

6. 体检表:Python / OCR 三态

服务页体检表现在会多两行,缺什么写什么(不教怎么装,只说用途与可选项)。

状态Python 行OCR 行
就绪可用 · 3.11.18 · …\storyboard\ocr\venv\Scripts\python.exe 可用 · rapidocr 3.10.0 · onnxruntime 1.31.1 · …\storyboard\ocr\venv
有 Python、没引擎可用 · 3.11.18 引擎未就绪 · 缺 rapidocr / onnxruntime
没有 Python未找到 · — · 未找到 3.11–3.13(OCR 需要 Python 3.11–3.13) 不可用 · 安装 OCR 需要 Python 3.11–3.13;可自行安装,或用别的方式装 rapidocr
版本过高(3.15)版本过高 · 3.15.0 · 高于 3.13,该版本暂无可用 wheel 不可用

判据是区间 3.11 ≤ v ≤ 3.13:onnxruntime 等编译包只有 cp311/cp312/cp313 的 win_amd64 wheel, 3.14/3.15 装了也跑不起来。

7. token 消耗:钱花在哪

本地跑 ffmpeg / ASR / OCR 不花 token——那些都是你机器上的 CPU。真正花钱的只有两样: 交给模型的图(每张 800×800 的宫格 = 384 tokens,是视觉输入硬上限) 和在场的文字索引(时间码 + 台词)。所以一次 run 的账大致是:

花费 ≈ 分镜图张数 × 384(视觉)  +  文字索引字符数 ÷ 2~3(中文粗略折算)

同一支视频、同一个 60s 窗口(standard 档、800×800 画布),把两个开关各拨一次:

规格张数视觉 tokens备注
integral:true(默认)41,53669 帧里 9 个来自伪切点
integral:false276858 帧全是真切点,图少一半
strategy:"full"155,760每格放大,最贵
strategy:"skim"(速览)1384全 1/8 档,切点按 0.35s 合并
strategy:"brief"(一图)1384整段压进一张
maxFrames: 2431,152封顶帧数
maxFrames: 121384封顶 + 并格

文字索引这一半基本是“常数”:同一支片子,detail 三档实测长度

窗口detail=lowdetail=meddetail=high
60s(本页那支)8,902 字符8,902 字符8,902 字符
300s16,731 字符16,731 字符16,731 字符
长台词字幕(合成演示)1,675 字符1,692 字符1,692 字符

为什么三档一样:detail 的预算是按每格给的(low 60 / med 120 / high 400 字符), 而这支片子的每格台词都短于 60 字,从来没触顶。只有当某一格里有整段朗读(或你塞了长台词 SRT)时 三档才会分开——上表第三行就是那种情况,差距立刻出现。

省钱的四个旋钮,各自都有代价:

做法省多少代价
strategy 往 skim / brief 走4 张 → 1 张(1,536 → 384)全部 1/8 档,细节丢;快切被合并成少数几格
tierMap 把相邻的快切格压到小档张数不变但每格更小,模型更难读字字变小 / 小字看不清,等于丢内容
maxFrames 封顶24 帧 → 3 张(1,152);12 帧 → 1 张(384)超出的短镜头直接被并掉,长片尾段可能只剩一格
detail:"low"每格台词压到 60 字不能引用原话,只能判断“这段在讲什么”

反过来说,最省的一步不是这些旋钮,是别请求你不需要的时间段:只取 30 秒比你整片跑完再压档便宜得多。

8. 工具说明精简:少占上下文

工具描述(tool descriptions)每轮请求都会常驻上下文。0.9.1 之前五个工具的说明合计 3902 字符、parameters JSON 合计 16287 字符,一大半在讲「为什么这么设计」「举例」 「边界情况」——模型每次开口都要先读一遍。

0.9.2 的做法:每个工具只留 参数清单 + 一句什么时候用(决策线索保留:先 video_probe 再切格、6 格还是 12 格、幻灯片用 integral:false、外语硬字幕用 subtitles:"ocr"); 长解释整段搬进 README 的「工具细节」;新增 storyboard_help({topic}) 按主题只取回那一小节。

指标0.9.10.9.2变化
五个工具的 description 合计3902 字符1671 字符(含新增 help)−57%
parameters JSON 合计16287 字符15889 字符−2.4%
工具数56+ storyboard_help
{ "topic": "storyboard" }   // 参数、engine、transcript
{ "topic": "choose" }       // 该用哪个:probe 先跑、6 格 vs 12 格
{ "topic": "subtitles" }    // ocr / ocr-dense 的区别与代价

可用 topic:storyboard / engine / transcript / subtitles / detect / precomputed / integral / tier / probe / locate / zoom / status / choose。取不到的主题会返回可用列表, 不会把整份 README 塞回上下文。