prod-by-claude
Use when an agent needs to make a HyperFrames video of a Strudel live-coding track, where the code itself is the picture and every token lights up on the note it plays. Trigger on: "strudel video", "live coding video", "show my code playing", "make a video of this track with the code", "prod by claude". Covers composing the track, recording it offline from pinned Strudel, and building the 1080x1080 video (typed opener, parts powering on, real Strudel theme switches, graffiti ASCII drops, typed sign-off).
1实现原理 · 为什么它能做到
画面与声音同源:先把 Strudel 曲目离线渲染成音频,再把每个音符事件映射回产出它的代码 token,视频由这些事件驱动——所以每次高亮都落在它自己的音符上,而不是后期对轨。
The music is rendered offline from the code, and the video is driven by Strudel's own note events, so every highlight lands on its note.
录音链路:record.mjs 在 127.0.0.1 上起一个只服务 recorder.html 与 node_modules 的静态服务,用静音无头 Chrome 逐 part 单独求值,导出每个音符的时间、所属 part、取值,以及产出它的代码字符区间,然后离线渲整轨 mix(含可选 ending)与 ._scope() part 的 solo stem。
// Serves recorder.html (the pinned @strudel/web build from node_modules) on 127.0.0.1, opens it in a … // muted headless Chrome, and for the track named in <project>/video.json: … // 1. evaluates every labelled part on its own and exports each note event with its time, part,
离线一致性的关键:recorder.html 只从本机 node_modules 载入钉版 @strudel/web,不从 strudel.cc 取任何东西;示例轨道是纯合成器无采样,所以首跑不需要额外的采样网络请求。
nothing is fetched from strudel.cc. Samples are only fetched if the track itself calls samples(...).
时间轴不是手写的:build-data.mjs 把 Strudel cycle 换算成视频秒,生成 window.SQ(token 高亮、段落 power-on、widget、能量曲线),把总时长写回 index.html 的 data-duration、把音频起点写成 pre。
const sec = (c) => +(PRE + c * SPC).toFixed(4); // strudel cycle -> video seconds … fs.writeFileSync(path.join(project, "data/events.js"), "window.SQ = " + JSON.stringify(out) + ";\n"); … // stamp the length into index.html; the audio stays the length of the recording
镜头也默认自动规划:不给 camera 就按段落 power-on 取景、build 段推近 all( 块、drop 后揭示新段落、结尾拉远到 sign-off;要覆盖才在 video.json 里写 camera 行。
Leave `camera` out to get the automatic plan: frame each part as it powers on, push in on the `all(` block during builds, reveal the newest part after a drop, pull wide after the last drop, and zoom to the sign-off.
录音质量是门禁而非事后补救:录制按每 4 小节打印电平并告警,要求反复录到 drop 峰值 ≤ -2 dBFS 且无削波;改曲就退回步骤 5-7 重录,改 video.json 只回步骤 6-7。
Fix the track and record again until the drops peak at or below -2 dBFS with no clipped samples.
2核心能力
3外部依赖
| Kind | Dependency / Endpoint |
|---|---|
| cli | HyperFrames CLI(钉版 0.8.70,经 npx --yes 调用;check/snapshot/render/cloud render) |
| package | @strudel/web 1.3.0(AGPL-3.0,从 npm 装,不进仓库) |
| package | puppeteer-core 25.12.0 + @puppeteer/browsers 3.2.3 与 OFL 字体(@fontsource/jetbrains-mono、rubik-mono-one) |
| package | Chrome for Testing 149.0.7827.22(录音用;可用 CHROME=<path> 覆盖) |
| network | 成片构图在载入时从 jsDelivr 取 GSAP 3.14.2(故渲染并非完全离线) |
| api | HyperFrames 云渲染(可选,消耗用户账户额度,须用户显式同意才可走) |
| network | 轨道自身若调用 samples(...),录音时会去轨道写明的采样 URL 取音频(示例轨道无采样) |
4管控建议与判定依据
- scripts/: 执行本地脚本或命令: node scripts/new-project.mjs <dir>(纯本地文件拷贝:模板 + 轨道 + 4 个 tools 文件)
- 网络端点: 声明外部网络访问: npm install(npm registry;依赖版本由 tools/package.json 钉死)
- 本地工作目录: 文件读写与工件输出: 仅在 <project-dir> 内:data/events.json、data/events.js、assets/bgm.wav、assets/st
5第二遍独立确认
- [ok] 『No credentials.』声明是否成立 — SKILL.md Rights/Side effects 段明写 'No credentials.';目录内 process.env 命中只有 record.mjs 的 CHROME 与 CFG 类入参,未见 key/token/cookie/keychain 读取,也无 .env 解析。
- [discrepancy] 『录音离线』是否等于全流程离线 — 录音段确实离线(127.0.0.1 静态服务 + 本地无头 Chrome + node_modules 里的 @strudel/web;recorder.html 注释明写 nothing is fetched from strudel.cc),但建仓(npm install、Chrome for Testing 下载)与渲染期构图载入 GSAP(cdn.jsdelivr.net)都要联网。SKILL.md 自己把这些写进 Side effects,故非隐瞒;只是『离线』的适用范围仅限录音。据此把档位定为蓝(官方源固定版本安装 + 产物期 CDN,与同 repo 先例 hyperframes-animation / hyperframes-core 同口径)。
- [ok] 高亮是否真由音符事件驱动(而非后期对齐) — 链路可读通:record.mjs 导出每音符的 locs(代码字符区间)→ build-data.mjs 用 byLoc 汇总成 token 时间表与段落/widget 时间 → 写成 window.SQ → 模板 app.js 消费。音画同源成立,不是后期对轨。
- [discrepancy] 是否误把 ffmpeg 列为依赖 — 本 skill 目录内不调用 ffmpeg:录音与 mix 走 Strudel 的离线渲染器,编码由 HyperFrames CLI 负责。故 external_deps 不列 ffmpeg(同批姊妹 skill 有列,本 skill 没有)。
- [ok] 大写 S 标签的拒绝逻辑是否真在代码里 — record.mjs 实有 'const soloing = labels.filter((l) => l.length > 1 && l.startsWith("S"));' 并在命中时 fail 提示改名——不是文档空话。
- [unlocatable] 录制耗时与云渲染额度的具体数值 — 『约 5-7 分钟/32 小节』与云渲染『消耗多少额度』只有文档声明,源码内无可复现依据;本次为只读侦查,未运行录音或渲染,故不作为已核事实使用。
6结论
90eabb744789abe6…