Communitygithub.com

mhuot/promo-video-skill

完全离线的约60秒宣传片制作工具:Kokoro TTS + HyperFrames + FFmpeg + OpenMontage 治理。包含模板以及该技能自身制作的演示视频。

promo-video-skill 是什么?

该技能旨在让AI编码智能体在完全离线的环境下,使用本地命令行工具链,自动生成约60秒的解说宣传片。它集成了Kokoro文本转语音(TTS)、基于HTML/CSS/GSAP的合成引擎HyperFrames、音视频编解码器FFmpeg以及项目管理与治理工具OpenMontage。该技能严格遵循“先锁定脚本,再衍生所有下游数据”的核心原则,确保所有视觉效果、时长、音乐都与最终的解说音频精确匹配。它支持Claude Code、GitHub Copilot CLI、Cursor等多种AI编码智能体,并提供可复用的项目模板和分阶段的工作流程指导。

兼容平台✓Claude Code✓Codex CLI✓Cursor✓Antigravity✓Windsurf
npx skills add mhuot/promo-video-skill

在你喜欢的 AI 中提问

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

文档

宣传片制作(本地工具链)

智能体无关:专为能够读取文件并运行shell命令的任何AI编码智能体编写——通过install.sh原生安装到Claude Code、GitHub Copilot CLI / Microsoft Scout和Google Antigravity;也可从Cursor、Codex、Windsurf等工具作为纯Markdown运行手册使用。 该工具链由Idan Shimon(微软,github.com/idanshimon)首次展示——他使用AI智能体驱动这套本地零成本堆栈制作的58秒概念解说视频,正是此技能的灵感来源。

不要假设安装位置:将PROMO_VIDEO_SKILL_DIR设置为包含此SKILL.md的目录。其scripts/目录包含TTS脚本、发音辅助脚本和固定的TTS依赖;assets/包含合成骨架、空间组件模板和决策日志模式;references/包含每台机器一次性的工具链引导程序(install.md)。

核心原则:脚本先锁定;每个下游数字都从测量的解说音频中派生,绝不能猜测。

宣传,而非解说:该技能推销的是“为什么”——受众、痛点、转变、证明、一个号召性用语(CTA)。“它是如何工作的”最多只能占一个节拍,且仅在用户要求时才涉及;教授工作原理的请求属于配套的explainer-video技能。

工具概况

工具位置已验证版本许可证角色调用方式
OpenMontage~/OpenMontage2026-07 签出版本AGPLv3(仅为工具;视频中不附带任何内容)流程治理:阶段顺序、决策日志、门控、渲染后自我审查纪律阅读其AGENT_GUIDE.md + pipeline_defs/animation.yaml;您(智能体)是编排者——无需运行守护进程
KokoroPyPIKokoro-82M (hexgrad), kokoro==0.9.4代码 Apache-2.0;模型权重 Apache-2.0解说TTS,每场景24 kHz单声道WAVPython 3.12虚拟环境;KPipeline(lang_code="a"),语音 af_heart,speed 参数
HyperFrames~/hyperframes0.7.57,从源码构建Apache 2.0HTML/CSS/GSAP合成 → 通过无头Chrome生成确定性MP4node ~/hyperframes/packages/cli/dist/cli.js <cmd>
FFmpeg~/FFmpeg 源码;构建目录中的二进制文件git master (N-125608 时代)GPL构建(启用了libx264)音乐合成(aevalsrc)、编码、ffprobe质量检查、响度、平台打包在hyperframes命令之前将构建目录放入PATH
bunnpm-global1.3.14—构建hyperframes monoreponpm i -g bun(brew cask曾静默失败过一次)
Node.jsnvm24.x (需要≥22)—运行hyperframes CLI—
Pythonuv管理3.12—Kokoro推理始终使用虚拟环境(uv venv --python 3.12 .venv);black + pylint 清洁
espeak-ngHomebrew 或 apt任意GPLKokoro G2P回退(必需)macOS:brew install espeak-ng;Debian/Ubuntu:sudo apt install espeak-ng
gsap供应商文件3.x gsap.min.jsGSAP标准许可合成内部的动画时间线复制到 video/assets/ — 绝不使用CDN <script src>(离线确定性)

环境检查(每台机器一次)

export FFMPEG_BUILD_DIR="${FFMPEG_BUILD_DIR:-$HOME/ffbuild}"
export HYPERFRAMES_DIR="${HYPERFRAMES_DIR:-$HOME/hyperframes}"
export PATH="$FFMPEG_BUILD_DIR:$PATH"
node "$HYPERFRAMES_DIR/packages/cli/dist/cli.js" doctor

在生产前,FFmpeg、FFprobe、Node和Chrome必须全部通过(其他doctor行是此技能不使用的可选组件)。如果这四个工具中的任何一个失败,请遵循$PROMO_VIDEO_SKILL_DIR/references/install.md——每台机器一次性的完整引导程序:系统包(macOS/Linux)、克隆源代码工具(包括OpenMontage参考签出)、从源码构建带有libx264的FFmpeg、构建HyperFrames CLI以及智能体权限提示。当doctor已通过时,不要加载该文件。

Chrome:CLI自动使用~/.cache/puppeteer/下缓存的chrome-headless-shell(如果不存在,则下载一次)。第一次TTS运行同样会下载Python包和Kokoro-82M权重。完成这些一次性下载后,合成、验证、渲染和打包即可离线进行。

项目布局(每次视频创建,拥有自己的Git仓库,原子提交)

<project>/
  .venv/                          # uv虚拟环境,python 3.12
  .gitignore                      # .venv/ __pycache__/ video/snapshots/ .DS_Store
  README.md                       # 制作方式+复现方式(最后编写)
  production/
    research/research-brief.md    # 带有来源的事实+诚实的竞品对比表
    proposal.md                   # 概念、场景列表、交付承诺、时长决策
    script/script.md              # 锁定的解说词
    scene_plan/scene-plan.md      # 每个场景的视觉+运动注释
    checkpoints/decision-log.json # 只能追加;(category, subject) 是键
    checkpoints/self-review.md    # 渲染后的证据
    assets/audio/                 # Kokoro WAV文件 + durations.json
    renders/                      # 主视频+平台衍生版
    youtube/                      # metadata.md + thumbnail.jpg
  tools/tts_generate.py           # 从此技能复制,编辑SCENE_NARRATIONS
  tools/tts_pronounce.py          # 来自scripts/,缩略词发音(不要编辑)
  tools/pronunciation.local.json  # 可选的项目级词汇覆盖
  video/
    index.html                    # 合成主文件
    assets/gsap.min.js            # 供应商文件
    assets/audio/                 # WAV副本(合成相对路径)

阶段1 — 调研(优先离线)

挖掘主题自身仓库/README/文档——寻找成果和证据(数字、采用率、前后对比结果),而非架构。生成 research-brief.md,包含:

  • 定位块(必需——脚本没有它就无法锁定)。 每项一句话:受众(这是为谁准备的),痛点(他们目前感受什么),转变(他们能获得什么改变),差异化因素(选择此方案的唯一理由)。这是信息;每个下游阶段都为其服务。
  • 产品事实,每个都对照来源验证——仅当它们支持某个好处时才保留(“那又怎样?”测试:观众能从中得到什么?)。
  • 竞品对比表(弱点 → 此方案如何解决)。诚实原则:承认竞争对手的真实优点(在他们那一列打一个✓能为你的论点赢得可信度)。
  • 一个 “已确认可用于脚本的主张” 列表。

阶段2 — 提案+决策

编写 proposal.md:重述定位块、5–7个场景概念、目标≈60秒、交付承诺(“运动主导,非幻灯片”)、工具链表。记录到 decision-log.json——从 $PROMO_VIDEO_SKILL_DIR/assets/decision-log.json 的空模式开始(字段:category、subject、selected、options_considered、rejected_because、confidence):pipeline_selection、render_runtime_selection(记录Remotion作为考虑过的替代方案——OpenMontage硬性规则)、composition_mode(主角作品使用atelier)、voice_selection、music_selection、output_profile、approval_gates、以及 cta_selection——恰好一个呼吁行动(给仓库加星/ 安装/ 观看演示/ 访问网站);宣传片如果以功能列表而非请求结尾就会失败。如果用户的简介已固定某个选择,则标记为 approved_by: "user brief"。后来更改了决策?APPEND一个具有相同(category, subject)的新条目——切勿编辑旧条目。

阶段3 — 脚本(锁定点)

约150–155个单词 ≈ 60秒(以kokoro af_heart 速度1.1)。宣传弧线(推销“为什么”):利害/痛点钩子 → 揭示(名称及其承诺的转变) → 对观众世界的好处 → 证据(诚实的比较、真实成果) → 愿景 → 来自决策日志的单一CTA。功能仅作为好处的证据出现:每个句子必须通过“那又怎样?”测试(观众得到什么?)或重写。最多一个“它是如何工作的”节拍,且仅在用户要求时——教授机制是explainer-video技能的工作。语气:第二人称,现在时,简短有力的句子(“你更快地发布”,而不是“它使得更快的发布成为可能”)。在脚本和屏幕文本中自然地书写缩略词(“OID”、“MCP”、“AI”)——在合成时,tools/tts_pronounce.py从其词典中派生口语形式(单词 vs 逐个字母),未知的缩略词会逐个字母拼写并报告。在可选的 tools/pronunciation.local.json 中对领域术语进行分类({"NASA": "word", "ECG": "letters", "NGINX": "engine X"})。避免全大写强调:全大写单词被视为缩略词。每场景的单词预算 ≈ 其占60秒的比例 × 2.55字/秒。

阶段4 — 解说+时长派生(关键步骤)

从附带的固定直接依赖创建项目的Python 3.12环境,复制通用模板,并仅编辑 SCENE_NARRATIONS:

uv venv --clear --python 3.12 .venv
uv pip install --python .venv/bin/python \
  -r "$PROMO_VIDEO_SKILL_DIR/scripts/tts-requirements.txt"
mkdir -p tools
cp "$PROMO_VIDEO_SKILL_DIR/scripts/tts_generate.py" tools/tts_generate.py
cp "$PROMO_VIDEO_SKILL_DIR/scripts/tts_pronounce.py" tools/tts_pronounce.py
PYTORCH_ENABLE_MPS_FALLBACK=1 .venv/bin/python tools/tts_generate.py
mkdir -p video/assets
curl -fL https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js \
  -o video/assets/gsap.min.js

第一次合成会下载Kokoro-82M权重(约330 MB);后续运行使用缓存并可离线。它写入每场景WAV + durations.json。如果运行打印出未知缩略词警告,在 tools/pronunciation.local.json 中对这些标记进行分类,并在派生时长之前重新运行。

将以速度1.1的150-155个单词作为起始估计,而非校准。总时长>62秒?提高语速或减少单词,然后再次测量。

派生时间线(场景间填充约0.3–0.5秒):

n_start[0] = 0.5                       # 开场
scene_start[i] = n_start[i] - 0.15..0.4   # 视觉效果在画外音之前出现
n_start[i+1] = n_start[i] + dur[i] + 0.3..0.35
TOTAL = 最后一个n_end + 0.6..0.9 结束(淡出至黑)

这些数字必须出现在三个地方且必须一致:场景的data-start/ data-duration、<audio data-start>值以及JS的const S1..Sn时间线常量。将WAV复制到video/assets/audio/。

阶段5 — 音乐背景(无下载)

使用从源码构建的FFmpeg生成确定性合成垫音。将 TOTAL_SECONDS 设置为派生的合成总时长;无需外部样本或私有参考:

TOTAL_SECONDS=60.5
FADE_SECONDS=3.5
FFMPEG_BIN="${FFMPEG_BUILD_DIR:-$HOME/ffbuild}/ffmpeg"
FADE_OUT_START="$(awk -v total="$TOTAL_SECONDS" -v fade="$FADE_SECONDS" \
  'BEGIN { printf "%.3f", total - fade }')"
mkdir -p production/assets/audio
"$FFMPEG_BIN" -hide_banner -y \
  -f lavfi \
  -i "aevalsrc=0.07*(sin(2*PI*110*t)+0.65*sin(2*PI*110.7*t)+0.45*sin(2*PI*164.81*t)+0.30*sin(2*PI*220.9*t)):s=48000:d=${TOTAL_SECONDS}" \
  -af "highpass=f=55,lowpass=f=1200,afade=t=in:st=0:d=2,afade=t=out:st=${FADE_OUT_START}:d=${FADE_SECONDS},pan=stereo|c0=c0|c1=c0" \
  -c:a pcm_s16le production/assets/audio/music-bed.wav

保持稀疏;混合在data-volume="0.12..0.16"时,它读作氛围音——对于宣传片的能量,靠近该范围的上限,但绝不能高于解说。将WAV复制到video/assets/audio/并使用单独的音频轨道。

阶段6 — 合成创作(HyperFrames契约)

从 $PROMO_VIDEO_SKILL_DIR/assets/composition-skeleton.html 开始(它体现了下面的每一条规则);在编写HTML之前,浏览 $HYPERFRAMES_DIR/skills/hyperframes-core/SKILL.md。结构:独立的 index.html,根 <div data-composition-id="X" data-start="0" data-duration="TOTAL" data-width="1920" data-height="1080">,场景作为 <section class="clip" data-start data-duration data-track-index="1">,一个 gsap.timeline({paused:true}) 在 window.__timelines["X"] 同步注册,补间位置 = S<i> + local_offset。

以下规则会耗费大量真实调试时间——请预先遵守:

  1. 每个 <video>/<audio> 都需要一个明确的、非空的 id。 在HyperFrames中,没有id的音频可能在浏览器预览中播放,但渲染时会是SILENT(lint会错误处理;不要忽略)。
  2. 永远不要在GSAP补间元素的CSS中设置初始 transform:——使用 tl.fromTo(el, {y:24, opacity:0}, {y:0, opacity:1, ...});GSAP拥有完整的变换。(CSS中单独的 opacity: 0 对于仅使用 .to() 的显示效果没问题。)
  3. 背景填充应放在一个全出血的子元素上(position:absolute; inset:0),绝不能放在根元素上(根元素背景可能渲染为黑色)。
  4. 媒体元素是根元素的直接子元素,data-track-index 用于不同的音频轨道(例如7=画外音,8=音乐)。
  5. 无无限重复(禁止 repeat: -1——使用有限值,例如光标闪烁 repeat: 9, yoyo: true),无 Date.now/Math.random,无网络请求,无CDN脚本。
  6. 所有地方使用唯一 id;仅对变换/不透明度进行动画(+ SVG线条绘制的 strokeDashoffset)。
  7. 在场景中间出现的元素:隐藏初始状态 + 在 S<i>+offset 处进行补间。fromTo 具有 immediateRender,因此预补间seek会显示 from 状态——这正是使其免于seek问题的原因。
  8. :root CSS变量中的设计令牌 = 主题;交换ground/ink/accent + 字体栈以更换皮肤。使用渲染器自带的字体(JetBrains Mono)——系统字体无论如何都会被替换(lint信息会告诉您映射关系)。
  9. 保持次要文本在ground颜色上满足WCAG AA标准——check 会在对比度上构建失败,并打印出合规的颜色供您使用。
  10. 有意重叠/滚动穿透:使用 data-layout-allow-overflow/-overlap/-occlusion 标记,而不是留下未解释的结果。

场景主题建议:展示OUTCOME状态——观众改善的世界(前后对比、痛点的消失、屏幕上的结果)。从主题的实际UI/领域(伪造的搜索面板、终端、树、表格)中提取支持性的一瞥,作为好处的证据,而不是机制介绍。叙事弧线有模板;主题绝不能有模板。 $PROMO_VIDEO_SKILL_DIR/assets/ 中的合成骨架HTML实现了所有这些约束。不要从头开始编写合成——复制骨架并编辑场景。

阶段7 — 渲染、质量检查和导出

在撰写合成的同时,根据需要频繁运行预览渲染进行视觉核对:

node $HYPERFRAMES_DIR/packages/cli/dist/cli.js serve --port 3300 &
npx puppeteer browsers install chrome-headless-shell  # 如果需要

当所有元素都看起来正确后,使用OpenMontage进行最终渲染:

# 渲染主视频
node $HYPERFRAMES_DIR/packages/cli/dist/cli.js render --composition X
# 运行OpenMontage自检
~/OpenMontage/scripts/self-review.sh production/

查阅 ~/OpenMontage/AGENT_GUIDE.md 了解完整的门控和审批协议。

阶段8 — 平台打包

  • YouTube:生成 youtube/metadata.md(标题、描述、标签、端卡)、1080p MP4、缩略图。
  • 社交媒体:根据平台规格调整分辨率(例如,TikTok 1080x1920)。
  • 本地存档:将所有源文件和最终渲染版本提交到项目仓库。

贡献

欢迎提交issue和pull request。请遵循现有的代码风格和提交消息惯例。

相关技能