Spatial Film
已在60秒产品广告片(7个场景、5个工作线程一轮、每个衔接帧精确、检查干净、1080p60格式、耗时4分19秒)上经过完整端到端验证。模板中的film.json包含该广告片真实的场景时间线和提示单,作为工作示例。
<skill> = 本技能所在的文件夹(即此SKILL.md所在的目录)。所有脚本均从广告片项目根目录运行。
下面的python = 由explainer-kit的setup-check保存在~/.talking-explainer/config.json中的Python命令(Windows上为py -3.12,macOS上为python3)。
你需要什么
- Node 20+,
ffmpeg+ffprobe位于PATH中,Python 3.10+且安装numpy和Pillow。 - HyperFrames技能:
npx hyperframes skills update general-video hyperframes-animation media-use(数据包构建器、蓝图+动画映射、音乐助手)。CLI本身按项目固定,通过npx运行。 - Playwright用于预渲染表面:在广告片文件夹中执行
npm i -D playwright(或设置PLAYWRIGHT_PATH)。 - 可选,用于来源音乐:一个HeyGen账户 - 在您自己的终端中运行
npx hyperframes auth login一次。如果没有,请自带音轨并从阶段4的music-analyze.py开始。 - 用于最终渲染的GPU机器(软件路径仍然可以渲染,但速度慢约15倍)。
如何在不浪费上下文的情况下使用此技能
- 每次只加载一个阶段参考,在其阶段开始时加载。切勿预加载所有内容。
- 脚本是经过验证的代码。运行它们;切勿内联重新生成它们的逻辑。
- 主会话从不读取整个场景文件(20-60KB)。通过联系单判断场景;使用
grep -n加精确补丁修复。 - 工作者接收文件路径,而不是粘贴的内容。
storyboard.html(60KB+)按单元格ID进行grep,从不整体读取。 - 先阅读联系单;仅当需要检查细节时才打开单个快照PNG。
阶段(每个阶段以检查点结束 - 未通过前不得继续)
| # | 阶段 | 加载 | 检查点 |
|---|---|---|---|
| 0 | 脚手架:node <skill>/scripts/new-film.mjs <dir> --name <slug> | - | 文件夹包含film.json、frame.md、kit、模板文档 |
| 1 | 输入、BRIEF、故事板、草图 | references/storyboard.md | 用户的明确“go”记录在## Locked下 |
| 2 | frame.md、kit、预渲染表面 | references/surfaces.md(+ platform-canvases.md仅当出现GHL/n8n/Zapier时) | frame.md中没有{{;每个捕获的PNG都已查看 |
| 3 | 框架冒烟测试、数据包、并行工作者 | references/workers.md | 每个场景:lint错误为0,联系单与其区块匹配 |
| 4 | 音乐+音效(工作者构建时运行) | references/audio.md | 背景音乐在自然BPM下,第0小节在强拍上;film.json中有提示 |
| 5 | 组装、检查、衔接、渲染、发布 | references/verify-ship.md | 检查通过,衔接检查全部PASS,硬件GPU渲染已验证 |
通过/hyperframes -> /general-video进行意图和草图审查步骤(本技能提供项目结构、合同、脚本和检查点;general-video提供数据包构建器和工作者角色)。
文件
| 路径 | 内容 |
|---|---|
template/ | 项目骨架:film.json(场景时间线、BPM、背景音乐、音效、提示 - 唯一的计时来源)、frame.md(设计真相合同,含填充项)、BRIEF.md、STORYBOARD.md、assets/kit.css(基础广告片工具包)、assets/fonts/(Poppins 500/700 + OFL)、references/worker-brief.md、references/scene-starter.html(lint干净的场景骨架)、references/snippets.html、package.json(固定hyperframes 0.8.117)。 |
surfaces/ | GHL、n8n和Zapier画布的现成HTML重建 + 一个长营销页面(虚构的Northwind Dental数据)、platform.css、sprite.svg、jobs.json。 |
scripts/new-film.mjs | 从模板搭建项目。 |
scripts/capture-surfaces.cjs | 将src/captures/*.html渲染为assets/captures/*.png,2倍大小(jobs.json)。 |
scripts/measure-surface.cjs | 表面上元素的1倍边界框,用于精确注释/裁剪目标。 |
scripts/make-harness.py、scripts/test-scene.mjs | 每个场景的独立测试项目;lint + 快照一个场景。 |
scripts/music-search.mjs、sfx-search.mjs | 通过media-use的REST助手访问HeyGen目录(Windows安全)。 |
scripts/music-analyze.py、music-bars.py | 仅使用numpy的节奏和每小节能量(无需librosa)。 |
scripts/bgm-cut.py | 在小节线上裁剪背景音乐,拼接音轨自己的结尾,淡入淡出;--b重复循环背景音乐以匹配更长的广告片。 |
scripts/assemble.py | film.json -> index.html(小节网格上的场景槽、背景音乐、带有持续时间和轨道的音效)。 |
scripts/seam-check.py | 逐像素验证每个衔接(剪切前/剪切时/剪切后)和循环(第0帧与最后一帧)。 |
scripts/render-final.mjs | CLI渲染,使用--browser-gpu,记录到文件,验证GPU模式和ffprobe,然后执行flash-scan.py。 |
scripts/flash-scan.py | 渲染的每一帧:如果一帧与相邻两帧都不同(某个元素只显示1/60秒),则视为错误。 |
scripts/encode-web.mjs | Web编码(H.264 CRF 21、faststart、AAC或--silent)+ 海报帧。 |
scripts/blink-fit.py、scripts/build-presenter.py | 有旁白的广告片:为姿势PNG叠加眨眼效果(--band = 眼部区域);从音轨构建主持人叠加,当吉祥物有mouth-fit.json时实现唇形同步。 |
scripts/el-tts.py、el-words.py、el-music.py | 通过ElevenLabs进行有旁白的广告片(密钥在环境变量EL中,绝不在磁盘上):从SCRIPT.md获取候选语音,Scribe单词时间戳,生成的背景音乐。 |
scripts/seam-diff.py | 衔接差异,并屏蔽主持人框(有旁白的广告片)。 |
scripts/fromto-scan.py | 预渲染:标记fromTo中仅FROM的属性,这些属性会导致渲染工作者(场景中跳转)永远不会应用。每次渲染前确保0次命中。 |
scripts/patch_exact.py | 精确、CRLF安全的查找/替换,用于脚本化的场景修复(断言每次只有一个匹配)。 |
scripts/selftest.mjs | 端到端验证本技能在此机器上正常工作。必须输出PASS。 |
有旁白的广告片(画外音 + 屏幕主持人)
已在有旁白的“API vs Webhook”讲解视频(83秒、竖屏和横屏、克隆声音、吉祥物主持人、10个场景由5个工作者完成)上验证。上述音乐广告片规则仍然适用,但以下情况除外:
- 先确定候选声音。
EL=<key> python <skill>/scripts/el-tts.py . eric=<voice_id> michelle=<voice_id>读取SCRIPT.md中的缩进行,并将每个声音的MP3写入src/audio/voice-candidates/(密钥仅来自环境变量,绝不在磁盘上)。让用户通过听觉选择,然后仅转录所选的声音。 - 旁白就是时钟。 单词时间:
EL=<key> python <skill>/scripts/el-words.py <voice.mp3> src/timing/voice-words.json(ElevenLabs Scribe - 无需本地模型,需要密钥),或使用word_timestamps参数的faster-whisper(设置语言,启用VAD;explainer-kit的transcribe.py,免费且本地运行)。将单词JSON保存在src/timing/中。在film.json中设置"bpm": 60, "beats_per_bar": 1,这样一个小节就是1秒,场景长度就是句子停顿处的普通秒数。故事板区块给出场景本地的单词时间;到达时间提前约0.1秒开始,并落在单词上。在下一个剪切之前,标题至少应在屏幕上显示约1秒(对照其显示的行的最后一个单词检查剪切)。 film.json额外项(assemble.py和make-harness.py会读取):"audio": [{id, src, dur, volume}]用于语音,"overlays": [{id, track, z}]用于全帧层(主持人),该层位于每个场景之上,因此它永远不会在衔接处弹出,并且场景保持无主持人状态。- 主持人来自姿势PNG: 每个姿势一个透明PNG(explainer-kit的
prep-mascot.py裁剪、缩放并写入清单),然后scripts/blink-fit.py <mascot_dir>(可选,需要torch;通过归一化互相关将BLINK眼部区域拟合到每个姿势上;得分低于0.70的姿势不进行眨眼)和scripts/build-presenter.py(从src/presenter-track.json写入叠加层:提示词上的姿势、头部锚定以确保切换不跳跃、语音包络波动、呼吸、眨眼、特效)。切勿手动编辑其输出。 - 音频: 将语音进行响度归一化至-16 LUFS / -1.5 dBTP(增益仅限制在+4.6 dBTP),离线侧链压缩背景音乐(语音下方约-17 dB),音效安静(0.1-0.45)。填充语音键,否则侧链压缩会将背景音乐截断为语音长度(检查警告“bgm 110.25s vs slot 113s”):
[1:a]apad=whole_dur=<TOTAL>[k];[0:a][k]sidechaincompress=threshold=0.03:ratio=6:attack=25:release=450,atrim=end=<TOTAL>。无目录背景音乐(HeyGen搜索500次或未登录):EL=<key> python <skill>/scripts/el-music.py .media/candidates/bed.mp3 <ms> "<prompt: instrumental, BPM, sits under a narrator, no drops, ends on a resolved chord>"- 一个在广告片长度内自然淡出的音轨不需要拼接。 - 衔接检查噪声: 主持人在每个衔接处移动,因此
seam-check.py仅因此失败。使用scripts/seam-diff.py a.png b.png out.png x0,y0,x1,y1(屏蔽主持人框)判断每个标记的对 - 外部差异为0.00%即为通过。 - 竖屏(1080x1920)尺寸规则: 每个场景的顶部区域必须包含真实标题(60-84px),正文文本>=40px,对象使用全宽以及主持人上方的高度。第一批次在帧的上45%区域中使用了约33px的文本 - 在分派前将这些数字放入frame.md,而不是之后。
- 非循环: 有旁白的广告片以保持的锁屏结束;忽略seam-check的循环行。
硬性规则(确保专业和流畅的规则)
- 真相来源于源。 产品UI来自产品自身的HTML/CSS;第三方画布从用户真实捕获重建,绝不凭记忆。仅使用虚构数据。仅包含产品真正拥有的功能。
- 单摄像机。 每个场景中透视距离为1866px(35mm);在摄像机层下有一个漂移层,确保没有任何东西是静止的;远处的叶子有视差+景深模糊。3D父级上无透明度/滤镜/溢出/剪切路径(它会展平)。
- 1080p+清晰。 使用CSS
zoom(.kit-zoom)缩放UI,绝不使用transform: scale(>1);每个元素以其最接近的屏幕尺寸创作;捕获的2倍PNG以等于或小于原始尺寸显示。 - 60fps平滑法则。 仅使用变换/透明度;使用弹簧效果进行着陆,使用expo/power3进行到达,使用inOut进行摄像机移动;无线性、步进、捕捉或舍入;摄像机移动重叠0.15-0.3秒并持续0.6秒以上。
- 确定性。 每个场景一个暂停的时间线,注册在
window.__timelines[id]中,最后注册的生效;fromTo具有明确的起始值,无随机/时钟/CSS过渡。 - 衔接是共享帧。 每个衔接是一幅精确的图像,相邻场景均保持约0.1秒,由动画装置外部的2D板锁定。无硬切、无空白帧、无淡出至无。最后一帧=第一帧。
- 符合网格。 场景为完整/半小节;点击、滴答、标记和交换落在节拍上;音乐以自然节奏在小节线上剪切。
- 修复合同,而非修复场景。 根相对路径
assets/...,ID不以数字开头,ID和类以场景ID为前缀。每个场景都会违反的规则在frame.md/worker-brief中一次性修复。 - 禁止项。 无表情符号、霓虹发光、紫色到蓝色渐变、彩色左边框卡片、圆形旋转器(闪烁)、库存人物、“John Doe”、幻灯片式节奏或不传达任何信息的动作。
- 最终渲染使用CLI和硬件GPU。 绝不使用Studio的导出功能(软件路径,速度慢约15倍)。
当出现问题时
| 症状 | 原因 | 修复 |
|---|---|---|
| Lint:资产路径错误 | 子合成中的../assets/ | 使用根相对路径assets/...(在合同中修复) |
| Lint:无效合成ID | ID以数字开头 | 使用s01-click,绝不使用01-click(film.py拒绝) |
| 数据包构建器抛出/数据包>48KB | 两个蓝图ID,或太多rules: | 一个id (Adapt);每块2-3个规则 |
| UI看起来模糊 | 使用了transform缩放或超出创作尺寸向摄像机方向推动 | 使用.kit-zoom,以最近尺寸创作 |
| 3D突然变平 | 3D祖先上的透明度/滤镜/溢出 | 将其移至叶子节点 |
| 衔接跳动 | 一侧未保持共享帧 | 使用seam-check.py,修复漂移侧的板/姿势 |
| 衔接失败几个百分点但帧看起来相同 | 板下的动画装置副本使其阴影加倍 | 保持期间仅显示板(frame.md中的衔接) |
| 产品UI与实际应用程序不符 | 凭记忆或部分CSS重建 | 以2倍大小捕获真实应用程序(surfaces.md第一部分) |
800+ duplicate_audio_track警告 | 音效缺少data-duration | 重新运行assemble.py |
music-search提示无凭据 | 未登录 | 用户在终端中运行npx hyperframes auth login |
| 渲染慢至约1帧/秒 | 软件GPU(Studio导出) | 使用render-final.mjs(CLI,--browser-gpu --workers 6) |
| 停止后渲染仍在运行 | 停止shell不会停止node | 终止进程树(verify-ship.md) |
| 无渲染进度显示 | 输出通过` | tail`管道化 |
自测
node <skill>/scripts/selftest.mjs
在临时文件夹中搭建一个2场景的广告片,然后运行框架、组装、check、衔接检查、硬件GPU渲染(1920x1080,60fps)和Web编码。它必须输出PASS - 在新机器上或进行任何编辑后运行它。