Communitygithub.com

brewed-ops/spatial-film

Spatial Film——talking-explainer背后的电影引擎。制作一段45-90秒的无旁白空间3D产品动态图形(一个CSS-3D摄像机、推入式转场、无硬切、最后一帧循环到第一帧),包含真实产品UI、忠实于平台的三方画布(GHL、n8n、Zapier)、原生节奏音乐和音效,通过HyperFrames渲染输出1080p60。

spatial-film 是什么?

Spatial Film(空间影片)是一个面向AI编码代理(如Claude Code、Cursor、Codex)的专业技能,用于自动制作高质量的无旁白3D产品动态图形影片。该技能专为快速制作45-90秒、采用CSS-3D摄像机、推入式转场、无硬切、最后一帧无缝循环到第一帧的影片而设计。影片完全由AI代理完成:从项目搭建、构思分镜、UI表面预渲染,到组建并行场景工作流、音乐与音效生成,最终输出1080p60的影片——整个过程无需人工干预。该技能证明了7个场景、5个并行工作单元可在约4分19秒内完成渲染。它支持GHL、n8n和Zapier等第三方平台画布的忠实复现。核心工作流分为6个阶段,每个阶段都有明确的检查门:搭建、确认分镜、设计合约与表面、场景工作单元、音频、最终组装与验证。输出通过逐像素接缝检查和闪光帧检测确保完美无瑕。使用场景为:用户说“制作空间影片”或“3D动态图形”,提交MOTION-PROMPT.md,或要求制作6个以上场景、使用3D摄像机、无旁白的产品视频时。该技能主要面向talking-explainer项目环境,需要Node 20+、ffmpeg、Python 3.10+以及HyperFrames技能包。

兼容平台✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/brewed-ops/talking-explainer/tree/main/skills/spatial-film

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

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下
2frame.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.pyfilm.json -> index.html(小节网格上的场景槽、背景音乐、带有持续时间和轨道的音效)。
scripts/seam-check.py逐像素验证每个衔接(剪切前/剪切时/剪切后)和循环(第0帧与最后一帧)。
scripts/render-final.mjsCLI渲染,使用--browser-gpu,记录到文件,验证GPU模式和ffprobe,然后执行flash-scan.py。
scripts/flash-scan.py渲染的每一帧:如果一帧与相邻两帧都不同(某个元素只显示1/60秒),则视为错误。
scripts/encode-web.mjsWeb编码(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的循环行。

硬性规则(确保专业和流畅的规则)

  1. 真相来源于源。 产品UI来自产品自身的HTML/CSS;第三方画布从用户真实捕获重建,绝不凭记忆。仅使用虚构数据。仅包含产品真正拥有的功能。
  2. 单摄像机。 每个场景中透视距离为1866px(35mm);在摄像机层下有一个漂移层,确保没有任何东西是静止的;远处的叶子有视差+景深模糊。3D父级上无透明度/滤镜/溢出/剪切路径(它会展平)。
  3. 1080p+清晰。 使用CSS zoom(.kit-zoom)缩放UI,绝不使用transform: scale(>1);每个元素以其最接近的屏幕尺寸创作;捕获的2倍PNG以等于或小于原始尺寸显示。
  4. 60fps平滑法则。 仅使用变换/透明度;使用弹簧效果进行着陆,使用expo/power3进行到达,使用inOut进行摄像机移动;无线性、步进、捕捉或舍入;摄像机移动重叠0.15-0.3秒并持续0.6秒以上。
  5. 确定性。 每个场景一个暂停的时间线,注册在window.__timelines[id]中,最后注册的生效;fromTo具有明确的起始值,无随机/时钟/CSS过渡。
  6. 衔接是共享帧。 每个衔接是一幅精确的图像,相邻场景均保持约0.1秒,由动画装置外部的2D板锁定。无硬切、无空白帧、无淡出至无。最后一帧=第一帧。
  7. 符合网格。 场景为完整/半小节;点击、滴答、标记和交换落在节拍上;音乐以自然节奏在小节线上剪切。
  8. 修复合同,而非修复场景。 根相对路径assets/...,ID不以数字开头,ID和类以场景ID为前缀。每个场景都会违反的规则在frame.md/worker-brief中一次性修复。
  9. 禁止项。 无表情符号、霓虹发光、紫色到蓝色渐变、彩色左边框卡片、圆形旋转器(闪烁)、库存人物、“John Doe”、幻灯片式节奏或不传达任何信息的动作。
  10. 最终渲染使用CLI和硬件GPU。 绝不使用Studio的导出功能(软件路径,速度慢约15倍)。

当出现问题时

症状原因修复
Lint:资产路径错误子合成中的../assets/使用根相对路径assets/...(在合同中修复)
Lint:无效合成IDID以数字开头使用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 - 在新机器上或进行任何编辑后运行它。

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

相关技能