1实现原理 · 为什么它能做到
实现路径是「把 AE 的做法重建成逐帧确定性绘制器」:一个解析时钟同时画 plate、matte 与每个字形,整个合成不使用 CSS 动画(因为渲染器要能任意 seek)。
One clock paints plate, matte and every glyph. Nothing is a CSS animation.
相机不是真 3D,而是一个解析投影:每个镜头有 cz(沿轴推进)与 px/py(屏幕平移),所有图层活在「参考相机下的参考屏幕坐标」,投影式 screen = C + (ref − C)·D0/(D0 − cz) + (px,py);文字按 15 fps 步进并把相机也一起采样,叠 7 重影运动模糊。
as a deterministic per-frame painter for HyperFrames. One analytic clock: CAM3D.render(t) writes every style.
「AE 感」不靠硬件能力,靠三条硬编码的时间纪律:文字 POSTERIZE 到 15 fps(连它看到的相机一起采样)、7 重影运动模糊、plate 与 matte 每帧都动。
Text is posterized (camera AND own animation sampled at 15 fps) with 7-ghost motion blur; the plate/matte move every frame with a directional Gaussian from camera speed.
时间轴本身**不**锁死在模板里:分镜由模型按台词现场选 4–6 拍,每个分句拆一组;锁死的是绘制契约——15 fps 网格、字幕提前 0.2 s、进场的 ENTRY 位移与深度影子/DOF 公式。
Pick 4–6 beats. Split each spoken clause into its own group.
素材管线是一次性 prep:切出 portion(音频源)、反射补边的 plate-tall(防甩镜/推拉露边)、同帧 alpha matte(person.webm),然后**逐项比对三者**的尺寸/帧率/帧数,不一致直接 exit 1——这是「时间轴锁死」的真实落点。
[ "$tn" -eq "$pn" ] && [ "$mn" -eq "$pn" ] || fail "frame counts differ: portion $pn, plate $tn, matte $mn"
字体度量外置成可生成的数据表:font-metrics.py 用 fontTools 读出每个字形的 advance(em)与 line-height 1 的基线,变量字体先在给定的轴实例化;引擎据此自己排每个字形(CSS 关掉 kerning),所以变量轴必须同时在 CSS 里钉死,否则浏览器按字号走 opsz,宽度漂移。
Writes `window.CAM3D_METRICS = {adv: {key: {char: em}}, base: {key: em}}`.
镜头运动曲线不是拟合出来的,是从教程片里逐帧量出来的表:tables.js 存 camA(19 帧甩入+爬行)、camB(拉出/回稳/推进)、wipe 前缘 x 与进场剩余比例,按 30 fps 帧索引、按 H/1080 缩放。
/* tables.js — measured motion tables (skill camera-3d-captions). Frame-indexed at 30 fps; px values are for a 1080-tall frame (scale by H/1080);
手绘风格是跨 skill 复用:借已安装的 p5-paint-animation 引擎把每个词渲成 write-on 精灵图(build-sprites.py 用 subprocess 调该引擎的 render-anim.mjs,读完帧目录后再删掉),因此该风格的前置是另一个 skill 的 setup。
**Hand-drawn style (optional):** needs the `p5-paint-animation` skill installed and set up (its setup downloads pinned puppeteer, Chrome for Testing, p5 and p5.brush). `build-sprites.py` runs it headlessly; frames are written to that skill's `out/` and removed afterwards.
2核心能力
3外部依赖
| 类型 | 依赖 |
|---|---|
| cli | HyperFrames CLI(钉死 0.8.62,经 npx 从 npm 取) |
| cli | ffmpeg / ffprobe(切分、反射补边、探帧率与帧数、最后编码) |
| package | Python3 + fonttools/brotli/numpy/pillow(字体度量与手绘精灵图) |
| network | 抠像模型首次运行下载(人物分割权重 ~170 MB,本地推理) |
| network | GSAP 3.14.2(合成在预览/渲染时由页面加载;参考片 HTML 也引用同一 CDN) |
| package | 跨 skill 依赖:p5-paint-animation 的渲染引擎(仅手绘风格需要;用 render-anim.mjs 生成精灵图) |
| cli | 素材来源:用户自备 take 视频、自备字体(需自行确认嵌入许可)、以及用户自己本地跑的词级转录器 |
4风险提醒 风险提醒:黄色 · 留意使用
- 执行期联网不可免(每命令 npx 取 CLI) — 每条命令都经 npx [email protected] 从 npm registry 拉取;离线环境无法执行。版本已钉死,复现性可控,但需要网络与 npm 可达。
- 首次抠像下载 ~170 MB 权重,属第三方供应链面 — remove-background 由 CLI 从自身分发拉取人物分割模型到 ~/.cache/hyperframes/ 后本地推理;权重来源不在本 repo 审计范围内(与 embedded-captions 同型)。
- 渲染非完全离线 — 合成页在预览/渲染时从 cdn.jsdelivr.net 加载 [email protected];断网或 CDN 被拦时渲染会失败。
- 参考片不能照文档直接跑起来 — worked-film.html 除『媒体不含』外还引用未随包的 assets/o2o-data.js 与 Inter 字体文件(见 second_pass 的 discrepancy);读者须自行桥接 window.O2O 并自备字体。SKILL.md 未提示这一点。
- 跨 skill 依赖会把执行面扩到另一个 skill 的目录 — 手绘风格需要 p5-paint-animation 已 setup(该 setup 会拉 puppeteer/PyPI 之外的 Chrome for Testing 等),且 build-sprites.py 会在那个 skill 的 out/ 写并删除临时帧。按判级口径不改档,但用户要知道执行范围超出本目录。
- 素材合法性自负 — take 与字体都由用户提供;SKILL.md 只提醒『Confirm the licence allows embedding』(字体),没有对 take 的授权做任何校验。
5第二遍独立确认
- [ok] skill 自身是否读凭据(黄/橙分界的关键) — 全目录 process.env / os.environ 只命中 build-sprites.py 的 P5_ENGINE(p5 引擎目录路径);无 API key、token、cookie、keychain、.env 读取。因此按范式的橙档第一条不成立,落黄。
- [ok] 「外部资源」逐条回查调用点 — [email protected] 在 SKILL.md 与 prep-take.sh(HF="[email protected]")两处一致;ffmpeg/ffprobe 在 prep-take.sh 的 ffmpeg/probe() 内可验;fontTools 在 font-metrics.py import 段;GSAP CDN 在 worked-film.html 与 SKILL.md 的 Render time 条;p5 引擎调用在 build-sprites.py 的 subprocess.run(cwd=engine)。
- [ok] prep 阶段的三者对齐自检是否真会失败退出(不是装饰性文字) — prep-take.sh 定义 fail(){ echo ... >&2; exit 1; },并对 plate 尺寸、matte 尺寸、三者的帧率与帧数各写了一条比较分支,任一不符即 exit 1;成功路径打印 'prep-take: plate and matte aligned with the portion ($pn frames @ $pr)'。
- [ok] 手绘风格是否真的复用 p5-paint-animation 引擎,且临时帧会被清理 — build-sprites.py 用 subprocess.run(['node','render-anim.mjs', ...], cwd=engine) 渲染临时 sketch(写在 tempfile 目录,落在自己的 temp 而非引擎内),读引擎 out/_frames_<name>.anim/ 的 PNG 后调用 shutil.rmtree(fdir, ignore_errors=True);与 SKILL.md『frames are written to that skill's out/ and removed afterwards』一致。
- [discrepancy] worked-film.html 能否按 SKILL 的 Build 步骤原样打开(参考片的可运行性声明) — SKILL.md 只说 'its media is not included',但参考片的 <head>/<body> 还引用了 assets/o2o-data.js(注释写 'window.O2O here = assets/kit/tables.js')与 assets/Inter-Variable.woff2、assets/InterTight-900.woff2 等字体文件;o2o-data.js 与这两个 woff2 均不在包内(全量清单已确认)。按 SKILL 的『Copy assets/kit/{cam3d.js,tables.js,finish.js} and metrics.js into the project's assets/』操作仍缺 o2o-data.js,参考片不能原样运行——读者须自行把 tables.js 改名/桥接成 window.O2O 并自带字体。该出入对 pin commit 有效。
- [ok] 15 fps 网格与 0.2 s 提前量是否真在实现里(而不是只写在文档) — cam3d.js 顶部 var W = 1440, H = 1080, CX = 720, CY = 540, FPS = 30, POST = 15, K = 7;,并有 post(t) = Math.floor(t * POST + 1e-4) / POST;SKILL.md 给出 fr(t) = 2·round((t − 0.2)·15)(×2 即 30 fps 渲染下的 15 fps 映射),两处自洽。
- [unlocatable] 『带 grade 与多重影渲染约 2× 成本』的量化声明 — SKILL.md 的 Limits 段写 'Rendering with the grade and many ghost layers costs about 2× a plain render.',包内没有基准脚本或计时数据可复现该倍数;本次为只读侦查且不得运行渲染,故仅作为文档声明记录,不作为已核事实使用。
6结论
70cf48e51fa89f4e…ba7a0bb6d3