# 从 v0.1 到现在：改了什么，为什么改

> ⚠️ **本文写于 0.5.x。** 0.6.0 重写了切点之后的整条链路：`cells` / `sheets` / `need` /
> `maxPerSheet` / `adaptive` / `tileFirst` 已删除，换成 `maxFrames` / `strategy` / `maxSheets` / `tierMap`；
> 一格现在等于一个镜头，格子大小由切点密度决定（1/1 … 1/8）。
> 参数与输出格式以 [README](../README.md) 和 [CHANGELOG](../CHANGELOG.md) 为准。

> 这份不是 CHANGELOG 的翻译，是**按主题重讲一遍**。CHANGELOG 按版本记录，这份按"问题"记录 —— 每个问题当初怎么暴露的、试过什么、最后为什么这么定。

---

## 一、起点（0.1.0，10 月 27 日）

第一版就定下了三件事，之后没再动摇：

1. **一张宫格图 + 一份文本清单**，不逐帧抽图。
2. **格数固定、格子尺寸相同** —— 变的是每格覆盖多少秒，不是分辨率。
3. **清单只给原材料，不下结论** —— 不说"这是高潮""这是角色 A"。

第 2 条后来被人误读成"动态分辨率注意力机制"，README 里专门写了一段澄清：token 按图计费，格子缩小只会变糊，省不到钱。

---

## 二、被真实素材打脸的十二次

这是这个项目真正的历史。**每一条都是先有一个"看起来合理"的做法，然后被一支具体的片子打穿。**

| 原来怎么做 | 怎么被打穿 | 现在怎么做 |
|---|---|---|
| 阈值 = 3 × p95 | 快剪动画 PV（p95=43）→ 阈值 130 → **0 个切点** | 按"每秒几刀"反推阈值 |
| 色相直接相减 | 0° 和 359° 算成差 254 → 跨红/品红边界凭空多一刀 | 环形最短距离 |
| 色相不看饱和度 | 近灰像素的色相被噪声甩到色环另一端，肉眼没变却判成切镜 | 色相项按饱和度降权 |
| 阈值用 `>=` | 阈值落在分位上时，同分整簇被整批拖进来（实测多三成） | `>` + ε，并按目标数截断 |
| 镜头被切成 0.3 秒 | 短镜头被并进邻居 → 快闪段只剩一格 | 按密度分配格位 |
| 格位按密度分 | 密度上限 2.5 卡死，最密处只能拿 2.5 倍；实测变异系数 0.62 | 快闪格**摊成子格**（见第四节） |
| 粗扫间隔 0.35 秒 | 3 秒 18 镜的素材只数出 4 刀 | 逐窗用一帧间隔重判 |
| 每帧一剪 | 当成运镜丢掉（23 刀变 9 刀） | 间隔按内容分流 + 用分数变异系数区分 |
| 竖屏视频 | yt-dlp 报 360×640，`height<=480` 一个都匹配不到，**完全下载不了** | 选择器加无筛选兜底 + 按短边排序 |
| 非方像素素材 | `aspect = 宽/高`，1440×1080 + SAR 4:3 会把每格人脸压扁 | `宽 × SAR / 高`，旋转取倒数 |
| 空 ASR 结果 | 没配后端仍自称 `transcript="asr"`，模型以为有台词 | 区分"没跑"和"跑了没结果" |
| 摊子格按镜头数 | 15 秒 5 个镜头的正常叙事也被摊成 9 张 | 按**密度**（≥0.7 刀/秒） |

一共 12 条，每条都留了实测数字在 CHANGELOG 里。

---

## 三、成本：两次减肥

**图片**：一直没变 —— 384 token/张。对比逐帧抽图（10 分钟 600 张）是数量级差别。

**文本**：动过两次手。

| 改动 | 效果 |
|---|---|
| 每格台词 180 → 120 字，按句末截断；删掉 `sharp 479.7`、单图时删 `image 1` | 2,892 → **2,171 字符（−25%）** |
| 加 `detail: low/med/high`（60/120/400 字） | `low` 1,521 / `med` 1,915 / `high` 3,031 |
| 后来又减：切点列表、爆点行、窗口重判行 | 9 格约 1,495 字符 |

有意思的是 **`med` 比旧的硬切 120 字还便宜 12%**：按句末停通常比填满配额更省。

---

## 四、今天（10 月 28 日）加的三样东西

### 1. 快闪重判：让"数不清的刀"数得清

粗扫为了抗噪，规定两刀至少隔 0.35 秒 —— 而快闪全在 0.35 秒以下。

做法：**逐 2 秒窗口**，用同一份逐帧分数、换成**一帧**的间隔重判一次。零额外解码。

实测：3 秒 18 镜的素材 **4 刀 → 16 刀**；概念 PV 40 → 48（多出来的抽查确认是真的硬切）。

三道闸门（都来自实测，不是拍脑袋）：

- 细判必须数出更多刀
- 不能被判成"运镜"
- 刀的分均 ≥ 25 —— **这条是关键**：真剪辑均值 59，缓慢运镜只有 18

### 2. 子格摊开：快闪那一格摊成 k×k

一格快闪用一张代表帧说不清。摊成 3×3 或 4×4，**同样一张图、同样 384 token**，交代 9 或 16 个瞬间。

判据（都来自实测）：

- 密度 ≥ 0.7 刀/秒（平均镜头 ≤ 1.4 秒）——**按密度不按镜头数**，否则正常叙事也会被摊
- 刀本身要硬（均分 ≥ 30）——否则 2D 线稿动画会摊出 9 张一样的
- 子帧 ≥ 100×56 —— 再小只剩色块
- 每张图最多多取 32 帧（4×4 需要 15 次 ffmpeg），预算不够就降档

### 3. 记忆：`seen before`

缓存答的是"这一段算过没有"，答不了"这部片子我看过没有、看到哪了"。后者才是 agent 反复重讲同一部片子的原因。

`seen.jsonl` 只记**机械事实**（路径/窗口/格数/刀数/时间），**不存模型结论** —— 存了就等于把上次的猜测当证据。

---

## 五、输入侧：从"只吃本地文件"到"贴个链接"

| 能力 | 说明 |
|---|---|
| URL | B 站 / YouTube，走 yt-dlp，下载后缓存 |
| 竖屏 | 自动转正、画布按竖版排 |
| 分P | 只取 P1，多分P 提示 |
| 字幕文件 | `transcriptFile` 直接喂 SRT/JSON，跳过 ASR |
| ffmpeg 发现 | 宿主目录 → PATH → 常见安装位置，找不到给安装指引 |

实测覆盖：PPT 录屏、监控合集、二维动画、三维动画（Blender 短片）、演播室口播、手持 vlog、游戏录屏、竖屏短视频、动画混剪、117 分钟长片。

---

## 六、几条一直没变的判断

1. **不做说话人分离。** cam++ 在 1.9 秒短句上把单人素材切成 6 个人；demucs 先分人声也零改善。短片本来就不在适用区间。
2. **不烧时间码进画面。** 占像素、压缩后先糊；文本里是 0.01 秒精度。
3. **不打包 ffmpeg / yt-dlp。** 版本和安全归用户。
4. **不为了结构好看而重构。** 试过拆 analyze/plan/render：清单 +49%、热路径慢 2.3 倍。
5. **数字不可信时就说不信。** 连续运镜被逐帧切碎时，清单写 `cut count unreliable here`。

---

## 七、还没做的

- 画面小于 1% 的改动（PPT 多一行字）在 64×36 下不可见 —— 要分块扫描才能救，目前判断不值得。
- 缓存不是严格 LRU（按字节数 + 最旧淘汰）。
- 安装脚本只认桌面版 profile。
- 分块扫描：能做，但会带来鼠标/噪点的假切点，等有真实需求再说。
