Cuepoint(垂直切片版)
把视频转为「视频的可导航索引」:transcript → 大纲(带证据引用)→ 定向选帧 → 索引文档(video_index.json + frames/,一等交付物,公开契约) → 渲染器族(frontend-slides HTML / markdown 笔记,均为文档的下游视图)。
本版覆盖:带字幕或无字幕(ASR)的在线视频(YouTube / B 站单 P)与本地视频文件。分析全程零打断,唯一交互点在文档导出后、渲染前。
运行前奏(每个宿主开工前先解析一次,跨平台可移植的关键):
- 解释器
PYBIN="$(command -v python3 || command -v python)"——Debian/Ubuntu 与 OpenClaw 网关最小 PATH 下没有裸 python、只有 python3,不解析会第一条命令就崩。
- skill 目录
SKILL_DIR= 本 SKILL.md 所在目录的绝对路径——各宿主 cwd 常不等于 skill 根(OpenClaw exec cwd=workspace、Codex cwd=项目目录),相对 scripts/ 会失配。OpenClaw 用 {baseDir} 令牌;其余平台按 skill 已知位置填绝对路径。
- 下文命令均简写作
python scripts/xxx.py,一律按 $PYBIN "$SKILL_DIR/scripts/xxx.py" 执行。
本 repo 内开发/验收:脚本跑在 uv venv 里,把上式换成 uv run python scripts/xxx.py。
路径约定:<OUT>/<W> 全程绝对路径。脚本把 --work 原样拼进 proxy_path/final_path/candidates.json 等制品;storyboard.py validate 用 Path(proxy_path).exists() 校验存在性——中途换相对路径或换 cwd 会误报失败。
各平台装载速查(四家共读 agentskills.io 的 SKILL.md+YAML frontmatter,按 description 触发;frontmatter 只保 name/description 公共子集,平台专属门控放各自 metadata.<平台>):
- Claude Code/Desktop:skill 目录直放。 Codex:拖入
~/.agents/skills/<名>/(或 repo .agents/skills),按 description 隐式触发。
- OpenClaw:放
~/.agents/skills/<名>/,SKILL.md 必须实体拷贝(软链逃逸守卫会拒),scripts//schemas//assets/ 可软链。 Hermes:~/.hermes/skills/<类>/<名>/,slash 命令 + description 触发。
预算(硬约束)
- 图片 Read 预算:短视频(≤30min)总数 ≤ 15 次;分章场景(>30min)按章预算 = 探针 ≤2 + 每章 sheet ≤2 + 细看 ≤1/章(spec §12「长视频分章场景按章预算」——19 章满额约 40 次是预期成本,非超支)
- 引用校验重生成 ≤ 2 轮,仍失败的节点降级纯文字 slide
流程
0. 预检
python scripts/setup.py(不加 --check——该 flag 会静默掉所有提示文本,只留 exit code,缺二进制时看不到装什么;不加 flag 才会打印缺失清单与按平台适配的安装提示)。
支持平台:macOS(Apple Silicon / Intel)与 Linux/Unix(arm64 / x86_64,含 Ubuntu/Debian、CentOS/RHEL/Fedora);不支持原生 Windows(WSL2 上报为 Linux,正常运行)。配置文件路径尊重 $XDG_CONFIG_HOME,缺省回落 ~/.config/video2slides/.env。
exit 0 → 预检通过,继续;exit 1 → 原生 Windows,不支持(改用 WSL2 / Linux / macOS,不要继续);exit 2 → 按 stdout 提示装好 ffmpeg/ffprobe/yt-dlp(macOS 印 brew、Linux 按发行版印 apt/dnf——ffmpeg 走发行版包管理器、yt-dlp 走 pipx)后重试;exit 3 → 所配 ASR 后端不可用:按 stdout 提示配置(<XDG 或 ~/.config>/video2slides/.env 写 ASR_BACKEND 与对应 key,或 FUNASR_VENV;若配了 arm64-only 后端而当前非 Apple Silicon,stdout 会提示改用 funasr);仅处理带字幕视频时可 ASR_BACKEND=none 继续。exit 4 → 仅缺 tesseract,提示信息可读但不阻塞,直接继续(文字密度打分自动降级为边缘密度代理)。
node/deno:setup 若印「未检出 node 或 deno」只是提示、不阻塞——仅 YouTube 取流需其一解 JS 挑战(EJS),本地文件/B 站不需要;缺了再装不迟。
0b. 宿主视觉能力自测(决定后续走视觉路还是时间轴路,守零打断放在分析前):Read <SKILL_DIR>/assets/vision_probe.png,把你在图上看到的确切文字交给 checker 比对:
python scripts/vision_probe.py --claim "<你看到的确切文字>"。
- exit 0 → 视觉模式:后续步骤 3/5 照常 Read sheet 目验。
- exit 1(答不出 / 拒读 / 看不见)→ 无视觉模式:后续按各步骤的「无视觉」分支走时间轴路——核心索引与笔记照常产出,仅幻灯片降级(见步骤 6/8)。纯文本宿主别编造图上文字:checker 只认归一化后哈希,编造必判否,如实走无视觉路才是对的。
- 续跑友好:
<W> 存在后(步骤 1 之后)带 --work <W> 重跑一次,判决落 .work/host_caps.json;续跑/新会话开工时先读它恢复模式(存在即不必重读探针图,省 1 次图 Read)。
- 用户可显式声明宿主视觉能力,覆盖本判决。
1. 取流
python scripts/fetch.py --url <URL> --work <OUT>/.work(B 站加 --cookies-from-browser chrome;headless / 云沙箱无本机 Chrome 时改用 --cookies <cookies.txt> 传导出的 cookies 文件)。
exit 3 = 无字幕轨且 ASR 不可用(none/配置无效)——告知用户配置 ASR(设 ASR_BACKEND / 部署 funasr venv),停止(转写文件直供 --transcript 为未来版本能力,本切片未实现,不要提)。无字幕轨但 ASR 可用时,fetch 会自动抽音频轨(.work/audio.mp3)并提示进 transcribe。本地文件:直接把文件路径当 --url 传入(sidecar 同名 .json 元数据自动读取;--cookies-from-browser 忽略)。
输出目录 <OUT> 默认 ~/Desktop/video2slides/<title>_<YYYYMMDD>/。
2. 转写与信号
python scripts/transcribe.py --work <W> → python scripts/signals.py --work <W>。
- transcribe.py:有字幕轨时,exit 1 = 字幕文件解析出 0 段(格式异常/内容为空),停止并告知用户;exit 0 = 成功。无字幕轨时 transcribe 走 ASR(后端由 .env 决定,默认 funasr):exit 3 = ASR 配置无效 /
ASR_BACKEND=none / .work/audio.mp3 缺失 / 后端调用失败(按 stdout 定位具体原因);exit 1 = ASR 产出 0 段(全部块失败或音频异常),不落盘;exit 0 = 成功。funasr 本地转写约 0.1–0.3× 实时(自动选 GPU:Apple Silicon 走 MPS / N 卡走 CUDA,否则 CPU;FUNASR_DEVICE 可强制),长视频耐心等待;mimo/qwen 为 45s 切块逐块调用,transcript 的时间戳为块级粒度(45s 级)、source 标 asr:<backend>。ASR/机翻文本有噪声:evidence quote 仍保原文一字不差,专有名词错译可在 slide 正文用小注澄清(参考 meeting5min 成品)。失败块会在 stdout 汇总(transcript 留缺口)。
- signals.py:exit 1 = scene-score 遍历失败(如单帧/静态视频)——spec §11 描述的降级路径(uniform 抽帧兜底)本切片未实现,遇到即直接停止并告知用户。
- 块级时间戳窗口微调(
transcript.json 的 source 为 asr:mimo/asr:qwen 时适用):此时时间戳是 45s 块级粒度,叶子节点的选帧时间窗本质是宿主估计,而非精确边界;frames.py 的窗口预扩张可能让选中帧落在字面窗口外,与步骤 9 QA「角标秒数须在节点时间窗内」的规则系统性打架。出现此类落空时,微调相邻叶子节点的边界使角标落回窗内(验收 #21 实例:相邻叶子 3.2/3.3 边界微调至 144.0),而不要因窗外丢弃该帧。
记下 stdout 的 curve_stats(含 plateau_ratio、spikes_per_min,下一步要用)。
2.5 划章(仅 meta.duration > 1800s)
python scripts/signals.py --chapter-hints --work <W>,读 stdout 候选表(每条含信号依据 + 前后转写摘录):
- 原生 chapters 存在时候选即原生章(信号列 native);无则为多信号共振合成;信号全无时按 10min 均分兜底(fallback=uniform,如实告知用户)
- 语义确认(只并不拆):按摘录判断候选是否真是内容转折;过碎相邻章合并(如 19 个 4min 伪章收到 8–10 章);拆分不允许——需要拆分说明候选不足,对该边界拉局部 transcript 上下文再定
- 密度合章:开场铺垫/闲聊占时长大但信息密度低的,合成一章而非按时长均分
- 块级时间戳对齐(
source 为 asr:mimo/asr:qwen 时):章界对齐到最近 45s 块边界,避免章界切开一个块
- 定稿写
.work/chapter_plan.json:{"source": "native|hints|manual|uniform", "chapters": [{"idx", "title", "t_start", "t_end", "hint_refs": [采纳的候选 idx]}]}——章界必须首尾相接、无缝无重叠、覆盖 [0, duration](validate 会校验)
≤30min 跳过本步(不写 chapter_plan,后续全部走原路径)。
3. 轴 A/B 分类
python scripts/frames.py --probe --work <W>,Read 探针 sheet(≤2 张):
无视觉模式:跳过探针 sheet 的 Read/目验(无法目验定性)——保守处理:visual_form 标 unknown(不猜 slide-driven)、不启用页边界 snap;轴 B 体裁仍照常从 transcript+标题+分区先验(纯文本)判。核心索引/笔记不依赖轴 A,幻灯片版面权重在无视觉降级下 moot(步骤 8)。以下目验相关条目仅视觉模式适用。
- 长平台 + 尖峰(plateau_ratio>0.8 且 spikes_per_min 约 0.5–6)且画面为版式文字 → slide-driven
- 其他形态:本版仅支持 slide-driven 的完整能力,如判非 slide-driven,
visual_form 标签仍如实记录(talking-head/screen-recording/cinematic,不得改标 slide-driven,否则污染 storyboard 下游语义),告知用户后仅在行为上保守复用 slide-driven 流程,但不启用页边界 snap(spec §11)
- 数值判据不可独立成立:曲线数值落在 slide-driven 区间不代表就是 slide-driven(实证:秀场直播 plateau_ratio 0.942 / spikes_per_min 0.57 全部落带,目验实为 talking-head+弹幕 UI),最终以探针 sheet 目验画面为准
- 平台分区去锚(轴 A):读
.work/ytdlp_info.json 的 categories/tags(若存在,fail-open;实测 yt-dlp 2026.07:YouTube categories 稳定给单一干净值(3/3:Education / Science & Technology / News & Politics),可直接当体裁软提示;B 站 categories 恒空、官方分区不透出(4/4),只有 tags——UP 主/活动/推广标签混着话题词,有噪声、≠ 官方分区、甚至整串被广告标签污染,可能误导。故务必只当去锚软提示、绝不建硬表,样本与实例见 spec §5.1)。分区落在演出/虚构/二创类(番剧/国创/动画/电影/电视剧/影视/鬼畜/音乐/舞蹈;YouTube Film & Animation/Music)是强「非 slide-driven」信号——用它把判断推离 slide-driven,提醒别硬套。但分区只能去锚、不能定性:永远不得单凭分区把视频判成 slide-driven,定性仍以探针 sheet 目验为准(与上条「数值判据不可独立成立」同规格——只保留纠错方向,不开锚定口子)。判为演出/虚构非目标时工具会降级(帧退化为氛围/装饰),如实告知用户但不中途打断分析,该提醒并入步骤 6 交互点前言 / 交付说明。
轴 B:读 transcript 前 60 段 + .work/meta.json(标题)+ .work/ytdlp_info.json(简介/description 字段 + categories/tags 平台分区,若存在——fetch.py 落盘的 meta.json 只保留 title/duration/language/uploader,不含简介,原始简介与分区仍在未裁剪的 ytdlp_info.json 里)+ .work/priors.json(chapters),从 {课程/教程, 演讲/分享, 访谈/播客, 评测/对比, 资讯/解读, 会议记录, vlog/生活, 直播实录/秀场, 纪录片} 选一。平台分区当软提示,非硬映射:分区是「主题域」(讲什么)、体裁是「形态」(哪种片),二者正交——一个分区(如 科技数码 / 知识)可对多种形态(课程/演讲/评测/资讯),分区只是与 transcript/标题同权的一个额外输入,最终由语义定。
4. 大纲生成(核心语义步骤)
读 .work/transcript.json + .work/priors.json + .work/page_boundaries.json,生成层级大纲并直接写 .work/storyboard.json(schema 见 spec §6)。规则:
- chapters 作 level-1 骨架(无则自行分层);heatmap 高值时段值得展开为独立要点
- 每个节点的 evidence 必须含
{segment_id, quote},quote 是该字幕段内一字不差的原文短语——禁止改写
- slide-driven:节点边界优先落在页边界上(±3s 内 snap 到最近页边界,超出保留字幕时间)
- 大纲语言跟随视频语言;
video 块填 meta 内容(video.priors.page_boundaries 取 .work/page_boundaries.json 的真实内容——.work/priors.json 里同名字段是 fetch.py 阶段写入的占位空数组,永远不会被后续步骤回填,不要从那里取),visual_form 填单段,media 先留空数组
- 叶子节点数参考:每 10 分钟 6–10 个
分章生成(存在 .work/chapter_plan.json 时):
- 逐章生成:每章只读该章 [t_start, t_end] 内的 transcript 段(75–130min 片单章约 150–300 段),外加两样轻量上下文——①已生成各章的 level-1/2 标题树(防跨章重复要点、保术语/编号一致)②前一章最后 3 段转写(防承接语误读)
- 章 = level-1 节点(title/t_start/t_end 与 chapter_plan 对应,容差 ±30s),章内叶子照常规则;每完成一章立即追加写入 storyboard.json 再开下一章
- 章级续跑:中断后对比 storyboard 已有 level-1 时间窗与 chapter_plan,缺哪章补哪章,不重生成已有章
- 全部章完成后:宿主只读各章 level-1/2 标题做一次归一(统一层级深度、消除章间标题近重复;不重读正文),然后照常跑 validate(会额外校验章覆盖与 level-1 对应)与 dedup(本就全局,天然覆盖跨章)
写完跑 python scripts/storyboard.py validate --work <W>;exit 5 → 按 stdout 失败节点重写其 evidence(≤2 轮,仍失败改纯文字节点:删 media、保留大纲文字)。
5. 选帧
python scripts/frames.py --candidates --work <W>,然后 Read 各章 sheet(每张有同名 .map.json 映射 cell→候选;字段:chapter/truncated/dropped_node_ids/cells[{cell,node_id,t,file}]):
无视觉模式:跳过 Read sheet。为每个叶子直接读 .work/candidates.json、按 node_id 过滤,按 peak_score 降序取第 1 个(换页最强的峰后落定帧;不按 composite score——含密度会挑到工具栏/水印虚高帧),无 peak 的 midpoint 候选退窗口位置。照下方写回格式把 proxy_path/t/reason/score 写进 media,并加一句约定标记 "note": "auto-timeline-unverified"(「自动按时间轴选取、未经视觉精修」)。dedup 改跑 --no-vision(见下),跳过「去重仲裁」条(无目验可仲裁)。以下目验相关条目仅视觉模式适用。
- 为每个叶子选 1–2 帧(选版式完整、文字清晰、无转场残影的),需要细看时才 Read 单帧原图
- 候选
reason 取值:scene-peak/window-midpoint(窄窗)、slot-peak/slot-midpoint(>90s 宽窗槽采样,长视频块级时间戳下常见)——slot 类候选覆盖全窗,后半段内容不再需要跨节点借帧
- 对最高分候选保持怀疑(2026-07-11 三次实测):tesseract 缺失时边缘密度代理会给"高 UI 杂色低信息量"画面(PPT 编辑器工具栏、水印页、插播网页截图)系统性虚高评分——终选以你目验的内容相关性为准,分数只是排序参考
- 把选中项写回 storyboard 各节点
media:{"type":"frame","proxy_path":<map 的 file>,"final_path":null,"finalized":false,"t":<map 的 t>,"reason":<候选 reason>,"score":<候选 score>}——reason/score 不在 map.json 里,需按 file 路径去同一份 .work/candidates.json(本步开头已生成)里查对应候选取值
- 若某张 sheet 的
dropped_node_ids 非空(该章候选量超过 18 帧/2 张 sheet 的预算上限,轮转配额未覆盖到的节点整个被挤出 sheet),这些节点不会出现在任何 sheet 里,需直接读 .work/candidates.json 按 node_id 过滤出它们的候选(未剪枝的原始候选,含 score/reason/file),挑分数最高的 1 张,不必额外 Read 图——score 已经是 ffmpeg 边缘/文字密度 + 峰值合成的排序依据
- 跑
python scripts/storyboard.py dedup --work <W>(跨要点去重标注:重复组写 dedup_group、组内最高分标 dedup_primary: true,单帧组为 null;不删除、不替换任何 media——唯一性是 slide 视图层策略,见步骤 7)。无视觉模式加 --no-vision:抬合并门→宁欠勿并,只并近乎同帧,宁可多张可见重复页也绝不静默吞掉不同讲义页(spec §视觉能力处理 6a)
- 去重仲裁(宿主复核):dedup 的 stdout 会列出各标注组及成员。16×16 签名对「同版式、不同内容」的讲义页(如渐进 build 的 BPE 演示页)分辨力不足,可能误组——对每个标注组,回看你在 sheet 上已目验过的成员帧:内容确实不同的,拆组(把该 media 的
dedup_group 置 null、dedup_primary 置 true);首选帧不当的,组内换 primary(恰保一个 true)。语义判断归宿主,机器标注只防「同一截图上多页」。改完重跑 validate 确认通过(validate 会校验组内恰一个 primary)
5.5 打包导出(无条件,先于交互点)
python scripts/storyboard.py export --work <W> —— 从 storyboard + transcript + meta 组装一等交付物 <OUT>/video_index.json + <OUT>/frames/(单 JSON 内嵌全量转写与 timestamp_granularity、媒体全相对路径、dedup 标注如实透传;公开契约 schema 见 schemas/video_index.schema.json)。索引文档是本体,渲染是视图——不因用户只要 slide 而跳过本步。
- exit 0 = 已产出(或已最新自动跳过,
--force 重跑);exit 5 = 契约校验不通过:不产出文档,按 stdout 定位修复 .work 制品(通常回步骤 4 重写对应节点 evidence,或回步骤 5 修 media/标注)后重跑,修不过不得进入步骤 6
- 导出物只读:后续渲染的记账(
on_page/final_path)只写内部 storyboard.json,不回写 video_index.json
- 交付说明须告知用户:
<OUT> 整目录自包含可迁移,删除 .work/ 不损失交付物(仅失去换粒度免重跑的缓存)
6. 形态与粒度询问(唯一交互点,合并 frontend-slides Phase 1)
用 AskUserQuestion 问一次(此时 video_index.json 已落盘,本问只决定是否继续渲染)。宿主无 AskUserQuestion 等结构化提问工具时(Codex/OpenClaw/Hermes),退化为纯文本问同样几项、给同样默认值:
- 「产出形态」slides / markdown 笔记 / 两者 / 仅索引文档(默认 slides)——选「仅索引文档」则按步骤 5.5 的交付说明告知文档落点,到此收束
- 无视觉模式:默认翻成「笔记」;slides / 两者 列为「需视觉宿主、默认不可用」并附因由。用户仍坚持要 slides 时(5b),带风险提示后按步骤 8 的无视觉分支一站式照渲(不赶用户去别处)
- 「Length」短 5–10 / 中 10–20 / 长 20+(默认中)——对任何渲染器都是展开深度:slides 页数档位与笔记
--depth 同套语义(短→1 / 中→2 / 长→3)
- 分章场景(有 chapter_plan):档位语义不变(短→L1/中→L2/长→L3),但文案报按深度预估的真实页数(如「本片 12 章:短≈12 / 中≈30 / 长≈65 页」)——长视频页数随章数上浮是预期行为,让用户看真数字选档
- 顺带允许覆盖输出语言(默认跟随视频)
Purpose 由轴 B 推断、Content 恒为 ready、Density 默认 high-density/reading-first,不问。
6.5 笔记渲染(形态含「笔记」时)
python scripts/notes.py --index <OUT>/video_index.json --depth <N>(短→1/中→2/长→3)→ 产出 <OUT>/notes.md。确定性脚本,只读导出文档与 frames/,禁碰 .work/;本地视频的时间戳自动退化为 mm:ss 纯文本。exit 5 = 契约缺字段/缺资产——不应发生,说明步骤 5.5 的导出校验有漏:回查修复后重跑 export 再渲染。形态只选「笔记」时,到此交付收束(交付说明同步骤 5.5)。
7. 渲染前置(形态含 slides 时)
- 深度:短→level 1;中→level 2;长→level 3(不足则全展开)
- 浅于叶子的展开:
python scripts/storyboard.py aggregate --work <W> --depth <N> 取各页 media(输出到 stdout 的 JSON,不会自动写回 storyboard;下一步的 on_page 仍需手动编辑)
- 给本次上页的每条 media 标
on_page: true(直接编辑 storyboard.json)
- 视图级唯一性(slide 专属):标
on_page 时,同一 dedup_group 只允许 primary 上页;非 primary 的节点该页改用其候选次名(按步骤 5 的 candidates.json 流程另选未用且目验不同的帧写入该节点 media)或转纯文字——「一帧不上两页」是 slide 的版面策略,不改动 dedup 标注本身
python scripts/frames.py --finalize --work <W> [--cookies-from-browser chrome | --cookies <cookies.txt>](B 站高清定稿同样需 cookie;headless 无 Chrome 用 --cookies <file>)
- 语义输入与记账分离:对外语义真相是导出的 video_index.json(只读);本步及步骤 8 对 media 的视图级调整(唯一性回退、on_page、final_path)只写内部 storyboard.json,不回写导出文档。若发生步骤 4/5 层面的语义修正(改 evidence/大纲/标注),须重跑
storyboard.py export --force 保持文档与真相一致
8. 渲染(调 frontend-slides skill)
读 frontend-slides 的 SKILL.md 并遵循其全部不变量(1920×1080 fixed stage、零依赖)。跳过其 Phase 1/2 提问:
- 无视觉模式的幻灯片(5b,仅用户在步骤 6 显式确认后渲染):①deck 每页加可见标记「自动选帧,未经视觉校对」(页脚或角标)——deck 会被转发,让下游看的人也知情,把「静默的错」变「看得见的错」;②交付前言带风险提示(帧未精修 / 可能一页多帧误并 / 挑得非最优);③帧已按
peak_score 时间轴选取、去重已 --no-vision 宁欠勿并,无需再目验。前置依赖:slides 需 frontend-slides skill 在场(缺则只能出笔记/索引);其定位同各平台 skill 装载路径。
- 风格 = 轴 B 自动映射(均为 STYLE_PRESETS.md 内已验证存在的预设名):课程/教程→Swiss Modern;演讲/分享→Bold Signal;访谈/播客→Paper & Ink;评测/对比→Electric Studio;资讯/解读→Notebook Tabs;会议记录→Paper & Ink;vlog/生活→Split Pastel;直播实录/秀场→Split Pastel;纪录片→Vintage Editorial
- 版式需求:每页含标题 + storyboard 该节点 media(用
final_path)+ 时间戳角标(mm:ss,<a href> 用 video_index.json 的 video.badge_url_template 填 t 的整数秒——跳转模板以导出文档为语义真相)+ summary 要点文字;quality_limited 的帧右下角标「代理画质」
- 帧的版面权重按 visual_form 定(2026-07-11 用户 review 反馈落地):
- screen-recording / 文档录屏 / slide-driven / whiteboard:帧是信息主体——截图里的文字必须肉眼可读,帧卡片占页面主导面积(≥60%,全宽大图+紧凑文字条,或图 70% 文 30%),UI 截图用 contain/原比例完整呈现,禁止 cover 裁切
- cinematic / 游记 / 实拍:帧承载氛围,图文对半或图稍大均可,cover 裁切可接受
- talking-head / 会议网格:帧纯装饰(画面恒同),小图/拍立得式点缀即可,文字为主体
- 竖屏帧(高>宽):禁止 cover 成横图——原比例居中,两侧留白或毛玻璃衬底;信息主体类(录屏/演示)高度贴满 1080 减留白;可双帧并排利用横向空间
final_path 的两种取值,HTML 里引用方式不同:
- 正常定稿:
frames.py --finalize 写到 <OUT>/assets/final_<node_id>_<t:.1f>.jpg(如 final_2.1_431.2.jpg),HTML 用相对 index.html 的 assets/<文件名> 引用即可
- 降级(
quality_limited: true,高清直链两次都取不到):final_path 原样等于代理帧 proxy_path,即 <OUT>/.work/frames_proxy/f_xxxxx.jpg——不在 assets/ 下。渲染前把这些文件也拷贝进 assets/(保持自包含,便于之后整目录分享/部署),再按拷贝后的相对路径引用,不要直接从 .work/ 里引
- 本地文件(platform=local):无跳转 URL(badge_url_template 为 null)——时间戳角标改为打开内嵌播放器弹层:
<video src="file://<原始绝对路径>"> + JS 设 currentTime 跳播,弹层内显示源文件路径,SKILL 交付说明须注明「移动/删除原视频文件会使跳转失效」。deck 不拷贝视频文件(可达数百 MB)。
- 产出
<OUT>/index.html
交付时告知所用风格;用户可要求换风格/换粒度重渲染——只重复 6–8 步,零重跑分析。
9. QA(交付前自检)
- 索引文档(所有形态必查):
<OUT>/video_index.json 存在且步骤 5.5 校验通过;抽 1 条 media 确认 frames/ 相对路径可打开
- slides:每页有标题、无 placeholder 文案;所有
assets/ 引用文件存在;同一 dedup_group 不出现在两页(视图级唯一性生效)
- slides:抽 3 个时间戳角标,核对 URL 格式(YouTube
&t=<n>s / B 站 ?p=<n>&t=<n>)且秒数在该节点时间窗内
- slides:抽 2 页核对帧与要点相关性,不符则回 sheet 换帧重渲染该页
- 笔记:notes.md 无死链(引用的
frames/ 文件存在——渲染器已强校验,此处抽 1 图确认可显示);抽 2 个时间戳——在线核对跳转链接格式,本地确认为 mm:ss 纯文本
1---2name: cuepoint3description: Turn a YouTube/Bilibili/local video into a self-contained video index document (video_index.json + frames/, the outline-transcript-frames mapping), then render it as a frontend-slides HTML deck and/or markdown notes with timestamp links back to the source. Requires ffmpeg + yt-dlp; subtitled videos need no API key. Frame curation and slides want an image-reading (multimodal) host; a text-only host still produces the text index + notes.4---56# Cuepoint(垂直切片版)78把视频转为「视频的可导航索引」:transcript → 大纲(带证据引用)→ 定向选帧 → **索引文档(`video_index.json` + `frames/`,一等交付物,公开契约)** → 渲染器族(frontend-slides HTML / markdown 笔记,均为文档的下游视图)。9本版覆盖:带字幕或无字幕(ASR)的在线视频(YouTube / B 站单 P)与本地视频文件。分析全程零打断,唯一交互点在文档导出后、渲染前。1011> **运行前奏(每个宿主开工前先解析一次,跨平台可移植的关键)**:12> 1. **解释器** `PYBIN="$(command -v python3 || command -v python)"`——Debian/Ubuntu 与 OpenClaw 网关最小 PATH 下**没有裸 `python`、只有 `python3`**,不解析会第一条命令就崩。13> 2. **skill 目录** `SKILL_DIR=` 本 `SKILL.md` 所在目录的**绝对路径**——各宿主 cwd 常不等于 skill 根(OpenClaw exec cwd=workspace、Codex cwd=项目目录),相对 `scripts/` 会失配。**OpenClaw** 用 `{baseDir}` 令牌;其余平台按 skill 已知位置填绝对路径。14> 3. 下文命令均简写作 `python scripts/xxx.py`,**一律按 `$PYBIN "$SKILL_DIR/scripts/xxx.py"` 执行**。15> **本 repo 内开发/验收**:脚本跑在 `uv` venv 里,把上式换成 `uv run python scripts/xxx.py`。16> **路径约定**:`<OUT>`/`<W>` 全程绝对路径。脚本把 `--work` 原样拼进 `proxy_path`/`final_path`/`candidates.json` 等制品;`storyboard.py validate` 用 `Path(proxy_path).exists()` 校验存在性——中途换相对路径或换 cwd 会误报失败。17>18> **各平台装载速查**(四家共读 agentskills.io 的 `SKILL.md`+YAML frontmatter,按 `description` 触发;frontmatter 只保 `name`/`description` 公共子集,平台专属门控放各自 `metadata.<平台>`):19> - **Claude Code/Desktop**:skill 目录直放。 **Codex**:拖入 `~/.agents/skills/<名>/`(或 repo `.agents/skills`),按 description 隐式触发。20> - **OpenClaw**:放 `~/.agents/skills/<名>/`,**`SKILL.md` 必须实体拷贝**(软链逃逸守卫会拒),`scripts/`/`schemas/`/`assets/` 可软链。 **Hermes**:`~/.hermes/skills/<类>/<名>/`,slash 命令 + description 触发。2122## 预算(硬约束)2324- 图片 Read 预算:短视频(≤30min)总数 ≤ 15 次;分章场景(>30min)按章预算 = 探针 ≤2 + 每章 sheet ≤2 + 细看 ≤1/章(spec §12「长视频分章场景按章预算」——19 章满额约 40 次是预期成本,非超支)25- 引用校验重生成 ≤ 2 轮,仍失败的节点降级纯文字 slide2627## 流程2829### 0. 预检30`python scripts/setup.py`(不加 `--check`——该 flag 会静默掉所有提示文本,只留 exit code,缺二进制时看不到装什么;不加 flag 才会打印缺失清单与按平台适配的安装提示)。31**支持平台**:macOS(Apple Silicon / Intel)与 Linux/Unix(arm64 / x86_64,含 Ubuntu/Debian、CentOS/RHEL/Fedora);**不支持原生 Windows**(WSL2 上报为 Linux,正常运行)。配置文件路径尊重 `$XDG_CONFIG_HOME`,缺省回落 `~/.config/video2slides/.env`。32exit 0 → 预检通过,继续;**exit 1 → 原生 Windows,不支持**(改用 WSL2 / Linux / macOS,不要继续);exit 2 → 按 stdout 提示装好 `ffmpeg`/`ffprobe`/`yt-dlp`(macOS 印 brew、Linux 按发行版印 apt/dnf——ffmpeg 走发行版包管理器、yt-dlp 走 pipx)后重试;exit 3 → 所配 ASR 后端不可用:按 stdout 提示配置(`<XDG 或 ~/.config>/video2slides/.env` 写 `ASR_BACKEND` 与对应 key,或 `FUNASR_VENV`;若配了 arm64-only 后端而当前非 Apple Silicon,stdout 会提示改用 funasr);仅处理带字幕视频时可 `ASR_BACKEND=none` 继续。exit 4 → 仅缺 `tesseract`,提示信息可读但不阻塞,直接继续(文字密度打分自动降级为边缘密度代理)。33**node/deno**:setup 若印「未检出 node 或 deno」只是提示、不阻塞——仅 YouTube 取流需其一解 JS 挑战(EJS),本地文件/B 站不需要;缺了再装不迟。3435**0b. 宿主视觉能力自测**(决定后续走视觉路还是时间轴路,守零打断放在分析前):`Read` `<SKILL_DIR>/assets/vision_probe.png`,把你在图上看到的**确切文字**交给 checker 比对:36`python scripts/vision_probe.py --claim "<你看到的确切文字>"`。37- exit 0 → **视觉模式**:后续步骤 3/5 照常 Read sheet 目验。38- exit 1(答不出 / 拒读 / 看不见)→ **无视觉模式**:后续按各步骤的「无视觉」分支走时间轴路——核心索引与笔记照常产出,仅幻灯片降级(见步骤 6/8)。**纯文本宿主别编造图上文字**:checker 只认归一化后哈希,编造必判否,如实走无视觉路才是对的。39- **续跑友好**:`<W>` 存在后(步骤 1 之后)带 `--work <W>` 重跑一次,判决落 `.work/host_caps.json`;续跑/新会话开工时先读它恢复模式(存在即不必重读探针图,省 1 次图 Read)。40- 用户可显式声明宿主视觉能力,覆盖本判决。4142### 1. 取流43`python scripts/fetch.py --url <URL> --work <OUT>/.work`(B 站加 `--cookies-from-browser chrome`;**headless / 云沙箱无本机 Chrome** 时改用 `--cookies <cookies.txt>` 传导出的 cookies 文件)。44exit 3 = 无字幕轨且 ASR 不可用(none/配置无效)——告知用户配置 ASR(设 `ASR_BACKEND` / 部署 funasr venv),停止(转写文件直供 `--transcript` 为未来版本能力,本切片未实现,不要提)。无字幕轨但 ASR 可用时,fetch 会自动抽音频轨(`.work/audio.mp3`)并提示进 transcribe。本地文件:直接把文件路径当 `--url` 传入(sidecar 同名 `.json` 元数据自动读取;`--cookies-from-browser` 忽略)。45输出目录 `<OUT>` 默认 `~/Desktop/video2slides/<title>_<YYYYMMDD>/`。4647### 2. 转写与信号48`python scripts/transcribe.py --work <W>` → `python scripts/signals.py --work <W>`。49- transcribe.py:有字幕轨时,exit 1 = 字幕文件解析出 0 段(格式异常/内容为空),停止并告知用户;exit 0 = 成功。无字幕轨时 transcribe 走 ASR(后端由 .env 决定,默认 funasr):exit 3 = ASR 配置无效 / `ASR_BACKEND=none` / `.work/audio.mp3` 缺失 / 后端调用失败(按 stdout 定位具体原因);exit 1 = ASR 产出 0 段(全部块失败或音频异常),不落盘;exit 0 = 成功。funasr 本地转写约 0.1–0.3× 实时(自动选 GPU:Apple Silicon 走 MPS / N 卡走 CUDA,否则 CPU;`FUNASR_DEVICE` 可强制),长视频耐心等待;mimo/qwen 为 45s 切块逐块调用,transcript 的时间戳为块级粒度(45s 级)、`source` 标 `asr:<backend>`。**ASR/机翻文本有噪声**:evidence quote 仍保原文一字不差,专有名词错译可在 slide 正文用小注澄清(参考 meeting5min 成品)。失败块会在 stdout 汇总(transcript 留缺口)。50- signals.py:exit 1 = scene-score 遍历失败(如单帧/静态视频)——spec §11 描述的降级路径(uniform 抽帧兜底)本切片未实现,遇到即直接停止并告知用户。51- **块级时间戳窗口微调**(`transcript.json` 的 `source` 为 `asr:mimo`/`asr:qwen` 时适用):此时时间戳是 45s 块级粒度,叶子节点的选帧时间窗本质是宿主估计,而非精确边界;frames.py 的窗口预扩张可能让选中帧落在字面窗口外,与步骤 9 QA「角标秒数须在节点时间窗内」的规则系统性打架。出现此类落空时,微调相邻叶子节点的边界使角标落回窗内(验收 #21 实例:相邻叶子 3.2/3.3 边界微调至 144.0),而不要因窗外丢弃该帧。52记下 stdout 的 `curve_stats`(含 `plateau_ratio`、`spikes_per_min`,下一步要用)。5354### 2.5 划章(仅 meta.duration > 1800s)55`python scripts/signals.py --chapter-hints --work <W>`,读 stdout 候选表(每条含信号依据 + 前后转写摘录):56- 原生 chapters 存在时候选即原生章(信号列 native);无则为多信号共振合成;信号全无时按 10min 均分兜底(fallback=uniform,如实告知用户)57- **语义确认(只并不拆)**:按摘录判断候选是否真是内容转折;过碎相邻章合并(如 19 个 4min 伪章收到 8–10 章);拆分不允许——需要拆分说明候选不足,对该边界拉局部 transcript 上下文再定58- 密度合章:开场铺垫/闲聊占时长大但信息密度低的,合成一章而非按时长均分59- **块级时间戳对齐**(`source` 为 `asr:mimo`/`asr:qwen` 时):章界对齐到最近 45s 块边界,避免章界切开一个块60- 定稿写 `.work/chapter_plan.json`:`{"source": "native|hints|manual|uniform", "chapters": [{"idx", "title", "t_start", "t_end", "hint_refs": [采纳的候选 idx]}]}`——章界必须首尾相接、无缝无重叠、覆盖 [0, duration](validate 会校验)61≤30min 跳过本步(不写 chapter_plan,后续全部走原路径)。6263### 3. 轴 A/B 分类64`python scripts/frames.py --probe --work <W>`,Read 探针 sheet(≤2 张):65> **无视觉模式**:跳过探针 sheet 的 Read/目验(无法目验定性)——**保守处理**:`visual_form` 标 `unknown`(不猜 slide-driven)、**不启用页边界 snap**;轴 B 体裁仍照常从 transcript+标题+分区先验(纯文本)判。核心索引/笔记不依赖轴 A,幻灯片版面权重在无视觉降级下 moot(步骤 8)。以下**目验相关条目仅视觉模式适用**。66- 长平台 + 尖峰(plateau_ratio>0.8 且 spikes_per_min 约 0.5–6)且画面为版式文字 → slide-driven67- 其他形态:本版仅支持 slide-driven 的完整能力,如判非 slide-driven,`visual_form` **标签仍如实记录**(talking-head/screen-recording/cinematic,不得改标 slide-driven,否则污染 storyboard 下游语义),告知用户后仅在行为上保守复用 slide-driven 流程,但**不启用页边界 snap**(spec §11)68- 数值判据不可独立成立:曲线数值落在 slide-driven 区间不代表就是 slide-driven(实证:秀场直播 plateau_ratio 0.942 / spikes_per_min 0.57 全部落带,目验实为 talking-head+弹幕 UI),**最终以探针 sheet 目验画面为准**69- **平台分区去锚(轴 A)**:读 `.work/ytdlp_info.json` 的 `categories`/`tags`(若存在,fail-open;**实测 yt-dlp 2026.07:YouTube `categories` 稳定给单一干净值(3/3:Education / Science & Technology / News & Politics),可直接当体裁软提示;B 站 `categories` 恒空、官方分区不透出(4/4),只有 `tags`——UP 主/活动/推广标签混着话题词,有噪声、≠ 官方分区、甚至整串被广告标签污染,可能误导**。故务必只当去锚软提示、绝不建硬表,样本与实例见 spec §5.1)。分区落在**演出/虚构/二创类**(番剧/国创/动画/电影/电视剧/影视/鬼畜/音乐/舞蹈;YouTube `Film & Animation`/`Music`)是强「非 slide-driven」信号——用它把判断**推离** slide-driven,提醒别硬套。**但分区只能去锚、不能定性**:永远不得单凭分区把视频判成 slide-driven,定性仍以探针 sheet 目验为准(与上条「数值判据不可独立成立」同规格——只保留纠错方向,不开锚定口子)。判为演出/虚构非目标时工具会降级(帧退化为氛围/装饰),**如实告知用户但不中途打断分析**,该提醒并入步骤 6 交互点前言 / 交付说明。70轴 B:读 transcript 前 60 段 + `.work/meta.json`(标题)+ `.work/ytdlp_info.json`(简介/description 字段 + `categories`/`tags` 平台分区,若存在——fetch.py 落盘的 `meta.json` 只保留 title/duration/language/uploader,不含简介,原始简介与分区仍在未裁剪的 `ytdlp_info.json` 里)+ `.work/priors.json`(chapters),从 {课程/教程, 演讲/分享, 访谈/播客, 评测/对比, 资讯/解读, 会议记录, vlog/生活, 直播实录/秀场, 纪录片} 选一。**平台分区当软提示,非硬映射**:分区是「主题域」(讲什么)、体裁是「形态」(哪种片),二者正交——一个分区(如 科技数码 / 知识)可对多种形态(课程/演讲/评测/资讯),分区只是与 transcript/标题同权的一个额外输入,最终由语义定。7172### 4. 大纲生成(核心语义步骤)73读 `.work/transcript.json` + `.work/priors.json` + `.work/page_boundaries.json`,生成层级大纲并**直接写 `.work/storyboard.json`**(schema 见 spec §6)。规则:74- chapters 作 level-1 骨架(无则自行分层);heatmap 高值时段值得展开为独立要点75- **每个节点的 evidence 必须含 `{segment_id, quote}`,quote 是该字幕段内一字不差的原文短语**——禁止改写76- slide-driven:节点边界优先落在页边界上(±3s 内 snap 到最近页边界,超出保留字幕时间)77- 大纲语言跟随视频语言;`video` 块填 meta 内容(`video.priors.page_boundaries` 取 `.work/page_boundaries.json` 的真实内容——`.work/priors.json` 里同名字段是 fetch.py 阶段写入的占位空数组,永远不会被后续步骤回填,不要从那里取),`visual_form` 填单段,`media` 先留空数组78- 叶子节点数参考:每 10 分钟 6–10 个7980**分章生成(存在 `.work/chapter_plan.json` 时)**:81- 逐章生成:每章只读该章 [t_start, t_end] 内的 transcript 段(75–130min 片单章约 150–300 段),外加两样轻量上下文——①已生成各章的 level-1/2 标题树(防跨章重复要点、保术语/编号一致)②前一章最后 3 段转写(防承接语误读)82- 章 = level-1 节点(title/t_start/t_end 与 chapter_plan 对应,容差 ±30s),章内叶子照常规则;每完成一章**立即追加写入** storyboard.json 再开下一章83- **章级续跑**:中断后对比 storyboard 已有 level-1 时间窗与 chapter_plan,缺哪章补哪章,不重生成已有章84- 全部章完成后:宿主只读各章 level-1/2 标题做一次归一(统一层级深度、消除章间标题近重复;不重读正文),然后照常跑 validate(会额外校验章覆盖与 level-1 对应)与 dedup(本就全局,天然覆盖跨章)8586写完跑 `python scripts/storyboard.py validate --work <W>`;exit 5 → 按 stdout 失败节点重写其 evidence(≤2 轮,仍失败改纯文字节点:删 media、保留大纲文字)。8788### 5. 选帧89`python scripts/frames.py --candidates --work <W>`,然后 Read 各章 sheet(每张有同名 `.map.json` 映射 cell→候选;字段:`chapter`/`truncated`/`dropped_node_ids`/`cells[{cell,node_id,t,file}]`):90> **无视觉模式**:**跳过 Read sheet**。为每个叶子直接读 `.work/candidates.json`、按 `node_id` 过滤,**按 `peak_score` 降序取第 1 个**(换页最强的峰后落定帧;**不**按 composite `score`——含密度会挑到工具栏/水印虚高帧),无 peak 的 midpoint 候选退窗口位置。照下方写回格式把 `proxy_path`/`t`/`reason`/`score` 写进 media,并加一句约定标记 `"note": "auto-timeline-unverified"`(「自动按时间轴选取、未经视觉精修」)。dedup 改跑 `--no-vision`(见下),**跳过「去重仲裁」条**(无目验可仲裁)。以下目验相关条目仅视觉模式适用。91- 为每个叶子选 1–2 帧(选版式完整、文字清晰、无转场残影的),需要细看时才 Read 单帧原图92- 候选 `reason` 取值:`scene-peak`/`window-midpoint`(窄窗)、`slot-peak`/`slot-midpoint`(>90s 宽窗槽采样,长视频块级时间戳下常见)——slot 类候选覆盖全窗,后半段内容不再需要跨节点借帧93- **对最高分候选保持怀疑**(2026-07-11 三次实测):tesseract 缺失时边缘密度代理会给"高 UI 杂色低信息量"画面(PPT 编辑器工具栏、水印页、插播网页截图)系统性虚高评分——终选以你目验的内容相关性为准,分数只是排序参考94- 把选中项写回 storyboard 各节点 `media`:`{"type":"frame","proxy_path":<map 的 file>,"final_path":null,"finalized":false,"t":<map 的 t>,"reason":<候选 reason>,"score":<候选 score>}`——`reason`/`score` 不在 map.json 里,需按 `file` 路径去同一份 `.work/candidates.json`(本步开头已生成)里查对应候选取值95- 若某张 sheet 的 `dropped_node_ids` 非空(该章候选量超过 18 帧/2 张 sheet 的预算上限,轮转配额未覆盖到的节点整个被挤出 sheet),这些节点不会出现在任何 sheet 里,需直接读 `.work/candidates.json` 按 `node_id` 过滤出它们的候选(未剪枝的原始候选,含 `score`/`reason`/`file`),挑分数最高的 1 张,不必额外 Read 图——`score` 已经是 ffmpeg 边缘/文字密度 + 峰值合成的排序依据96- 跑 `python scripts/storyboard.py dedup --work <W>`(跨要点去重**标注**:重复组写 `dedup_group`、组内最高分标 `dedup_primary: true`,单帧组为 null;**不删除、不替换任何 media**——唯一性是 slide 视图层策略,见步骤 7)。**无视觉模式加 `--no-vision`**:抬合并门→宁欠勿并,只并近乎同帧,宁可多张可见重复页也绝不静默吞掉不同讲义页(spec §视觉能力处理 6a)97- **去重仲裁(宿主复核)**:dedup 的 stdout 会列出各标注组及成员。16×16 签名对「同版式、不同内容」的讲义页(如渐进 build 的 BPE 演示页)分辨力不足,可能误组——对每个标注组,回看你在 sheet 上已目验过的成员帧:内容确实不同的,**拆组**(把该 media 的 `dedup_group` 置 null、`dedup_primary` 置 true);首选帧不当的,组内**换 primary**(恰保一个 true)。语义判断归宿主,机器标注只防「同一截图上多页」。改完重跑 validate 确认通过(validate 会校验组内恰一个 primary)9899### 5.5 打包导出(无条件,先于交互点)100`python scripts/storyboard.py export --work <W>` —— 从 storyboard + transcript + meta 组装一等交付物 `<OUT>/video_index.json` + `<OUT>/frames/`(单 JSON 内嵌全量转写与 `timestamp_granularity`、媒体全相对路径、dedup 标注如实透传;公开契约 schema 见 `schemas/video_index.schema.json`)。**索引文档是本体,渲染是视图——不因用户只要 slide 而跳过本步**。101- exit 0 = 已产出(或已最新自动跳过,`--force` 重跑);exit 5 = 契约校验不通过:**不产出文档**,按 stdout 定位修复 `.work` 制品(通常回步骤 4 重写对应节点 evidence,或回步骤 5 修 media/标注)后重跑,修不过不得进入步骤 6102- 导出物只读:后续渲染的记账(`on_page`/`final_path`)只写内部 storyboard.json,不回写 video_index.json103- 交付说明须告知用户:`<OUT>` 整目录自包含可迁移,删除 `.work/` 不损失交付物(仅失去换粒度免重跑的缓存)104105### 6. 形态与粒度询问(唯一交互点,合并 frontend-slides Phase 1)106用 AskUserQuestion 问一次(此时 video_index.json 已落盘,本问只决定是否继续渲染)。**宿主无 AskUserQuestion 等结构化提问工具时**(Codex/OpenClaw/Hermes),退化为纯文本问同样几项、给同样默认值:107- 「产出形态」slides / markdown 笔记 / 两者 / 仅索引文档(默认 slides)——选「仅索引文档」则按步骤 5.5 的交付说明告知文档落点,到此收束108 - **无视觉模式**:**默认翻成「笔记」**;slides / 两者 列为「需视觉宿主、默认不可用」并附因由。用户仍**坚持要 slides** 时(5b),带风险提示后按步骤 8 的无视觉分支一站式照渲(不赶用户去别处)109- 「Length」短 5–10 / 中 10–20 / 长 20+(默认中)——对任何渲染器都是展开深度:slides 页数档位与笔记 `--depth` 同套语义(短→1 / 中→2 / 长→3)110- 分章场景(有 chapter_plan):档位语义不变(短→L1/中→L2/长→L3),但文案报按深度预估的真实页数(如「本片 12 章:短≈12 / 中≈30 / 长≈65 页」)——长视频页数随章数上浮是预期行为,让用户看真数字选档111- 顺带允许覆盖输出语言(默认跟随视频)112Purpose 由轴 B 推断、Content 恒为 ready、Density 默认 high-density/reading-first,不问。113114### 6.5 笔记渲染(形态含「笔记」时)115`python scripts/notes.py --index <OUT>/video_index.json --depth <N>`(短→1/中→2/长→3)→ 产出 `<OUT>/notes.md`。确定性脚本,只读导出文档与 frames/,禁碰 `.work/`;本地视频的时间戳自动退化为 mm:ss 纯文本。exit 5 = 契约缺字段/缺资产——不应发生,说明步骤 5.5 的导出校验有漏:回查修复后重跑 export 再渲染。形态只选「笔记」时,到此交付收束(交付说明同步骤 5.5)。116117### 7. 渲染前置(形态含 slides 时)118- 深度:短→level 1;中→level 2;长→level 3(不足则全展开)119- 浅于叶子的展开:`python scripts/storyboard.py aggregate --work <W> --depth <N>` 取各页 media(输出到 stdout 的 JSON,不会自动写回 storyboard;下一步的 `on_page` 仍需手动编辑)120- 给本次上页的每条 media 标 `on_page: true`(直接编辑 storyboard.json)121- **视图级唯一性(slide 专属)**:标 `on_page` 时,同一 `dedup_group` 只允许 primary 上页;非 primary 的节点该页改用其候选次名(按步骤 5 的 candidates.json 流程另选未用且目验不同的帧写入该节点 media)或转纯文字——「一帧不上两页」是 slide 的版面策略,不改动 dedup 标注本身122- `python scripts/frames.py --finalize --work <W> [--cookies-from-browser chrome | --cookies <cookies.txt>]`(B 站高清定稿同样需 cookie;headless 无 Chrome 用 `--cookies <file>`)123- **语义输入与记账分离**:对外语义真相是导出的 video_index.json(只读);本步及步骤 8 对 media 的视图级调整(唯一性回退、on_page、final_path)只写内部 storyboard.json,不回写导出文档。若发生步骤 4/5 层面的语义修正(改 evidence/大纲/标注),须重跑 `storyboard.py export --force` 保持文档与真相一致124125### 8. 渲染(调 frontend-slides skill)126读 frontend-slides 的 SKILL.md 并遵循其全部不变量(1920×1080 fixed stage、零依赖)。跳过其 Phase 1/2 提问:127- **无视觉模式的幻灯片(5b,仅用户在步骤 6 显式确认后渲染)**:①deck 每页加**可见标记**「自动选帧,未经视觉校对」(页脚或角标)——deck 会被转发,让下游看的人也知情,把「静默的错」变「看得见的错」;②交付前言带风险提示(帧未精修 / 可能一页多帧误并 / 挑得非最优);③帧已按 `peak_score` 时间轴选取、去重已 `--no-vision` 宁欠勿并,无需再目验。**前置依赖**:slides 需 `frontend-slides` skill 在场(缺则只能出笔记/索引);其定位同各平台 skill 装载路径。128- 风格 = 轴 B 自动映射(均为 STYLE_PRESETS.md 内已验证存在的预设名):课程/教程→Swiss Modern;演讲/分享→Bold Signal;访谈/播客→Paper & Ink;评测/对比→Electric Studio;资讯/解读→Notebook Tabs;会议记录→Paper & Ink;vlog/生活→Split Pastel;直播实录/秀场→Split Pastel;纪录片→Vintage Editorial129- 版式需求:每页含标题 + storyboard 该节点 media(用 `final_path`)+ **时间戳角标**(mm:ss,`<a href>` 用 video_index.json 的 `video.badge_url_template` 填 t 的整数秒——跳转模板以导出文档为语义真相)+ summary 要点文字;`quality_limited` 的帧右下角标「代理画质」130- **帧的版面权重按 visual_form 定**(2026-07-11 用户 review 反馈落地):131 - screen-recording / 文档录屏 / slide-driven / whiteboard:**帧是信息主体**——截图里的文字必须肉眼可读,帧卡片占页面主导面积(≥60%,全宽大图+紧凑文字条,或图 70% 文 30%),UI 截图用 contain/原比例完整呈现,禁止 cover 裁切132 - cinematic / 游记 / 实拍:帧承载氛围,图文对半或图稍大均可,cover 裁切可接受133 - talking-head / 会议网格:帧纯装饰(画面恒同),小图/拍立得式点缀即可,文字为主体134 - **竖屏帧(高>宽)**:禁止 cover 成横图——原比例居中,两侧留白或毛玻璃衬底;信息主体类(录屏/演示)高度贴满 1080 减留白;可双帧并排利用横向空间135- `final_path` 的两种取值,HTML 里引用方式不同:136 - 正常定稿:`frames.py --finalize` 写到 `<OUT>/assets/final_<node_id>_<t:.1f>.jpg`(如 `final_2.1_431.2.jpg`),HTML 用相对 `index.html` 的 `assets/<文件名>` 引用即可137 - 降级(`quality_limited: true`,高清直链两次都取不到):`final_path` 原样等于代理帧 `proxy_path`,即 `<OUT>/.work/frames_proxy/f_xxxxx.jpg`——**不在** `assets/` 下。渲染前把这些文件也拷贝进 `assets/`(保持自包含,便于之后整目录分享/部署),再按拷贝后的相对路径引用,不要直接从 `.work/` 里引138- **本地文件(platform=local)**:无跳转 URL(badge_url_template 为 null)——时间戳角标改为打开内嵌播放器弹层:`<video src="file://<原始绝对路径>">` + JS 设 `currentTime` 跳播,弹层内显示源文件路径,SKILL 交付说明须注明「移动/删除原视频文件会使跳转失效」。deck 不拷贝视频文件(可达数百 MB)。139- 产出 `<OUT>/index.html`140交付时告知所用风格;用户可要求换风格/换粒度重渲染——只重复 6–8 步,零重跑分析。141142### 9. QA(交付前自检)143- 索引文档(所有形态必查):`<OUT>/video_index.json` 存在且步骤 5.5 校验通过;抽 1 条 media 确认 `frames/` 相对路径可打开144- slides:每页有标题、无 placeholder 文案;所有 `assets/` 引用文件存在;同一 `dedup_group` 不出现在两页(视图级唯一性生效)145- slides:抽 3 个时间戳角标,核对 URL 格式(YouTube `&t=<n>s` / B 站 `?p=<n>&t=<n>`)且秒数在该节点时间窗内146- slides:抽 2 页核对帧与要点相关性,不符则回 sheet 换帧重渲染该页147- 笔记:notes.md 无死链(引用的 `frames/` 文件存在——渲染器已强校验,此处抽 1 图确认可显示);抽 2 个时间戳——在线核对跳转链接格式,本地确认为 mm:ss 纯文本