Communitygithub.com

ClaudinNarsis/claude-video-skills

为任何产品制作一个 12–20 秒高能量动态图形宣传片,通过代码渲染为 MP4 并合成音乐。具备快节奏节拍锁定场景切换、动态文字排版、贯穿所有转场的形状元素以及 HUD 框架。当用户想要产品、应用或品牌的火爆预告、预告片、发布宣传或作品集风格宣传片时使用。

claude-video-skills 是什么?

这是一个专门为 AI 代理设计的技能,用于根据产品简介生成高能量动态图形宣传视频。它通过一个 HTML 文件实现,该文件将时间作为纯函数,渲染出具有运动模糊和合成音轨的 MP4 视频。该技能的核心在于其独特的视觉语法:一个**形状元素**贯穿所有转场(例如,火花变成线条,线条充满屏幕成为下一个背景),确保视觉流畅且专业。视频结构遵循 120-140 BPM 的节拍网格,包含 6-8 个场景,如品牌冲击、动态文字三重奏和产品微交互,每个场景都严格对齐节拍。该技能支持 Claude Code、Cursor 等工具,并提供一个详细的 SKILL.md 文档作为工作流程,包括获取简介、规划场景、构建页面以及使用 Playwright 和 ffmpeg 进行渲染。它还强调了事实准确性、品牌一致性和避免通用模板的设计规则。

兼容平台✓Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/ClaudinNarsis/claude-video-skills/tree/HEAD/skills/explainer-high-energy

在你喜欢的 AI 中提问

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

文档

高能量宣传片

一个 12–20 秒的爆款作品,像作品集一样冲击感十足。每半秒都有东西落在节拍上,但绝不会让人感觉是随意剪辑,因为一个形状贯穿所有转场,引导观众的视线:火花变成线条,线条充满屏幕成为下一个背景,句号脱离成为下一个场景围绕构建的球体。

你将其编写为一个 HTML 页面,该页面根据时间值渲染任何帧,然后将其渲染为带有运动模糊和合成音轨的 MP4。

步骤 1:获取简介(一条消息,然后开始)

在一条消息中询问所有内容,然后自行工作。只需要产品信息;对于任何留空的内容,自行决定并说明你的选择。

  1. 产品:名称,以及用几句话描述其功能。网站 URL 更佳。
  2. 观众必须记住的一件事(例如,“输入一个想法,得到一个实时应用”)。
  3. 品牌:颜色、字体、Logo 文件,或“使用网站”。如果没有,则使用下面的默认调色板。
  4. 格式:16:9(1920×1080)或 9:16(1080×1920)。默认为 16:9。
  5. 时长:此风格的默认时长如下所示。
  6. 音乐:他们自己的曲目(文件路径),或者让你合成一个。默认:合成。
  7. 方向(可选):他们想要的场景、标语、行动号召,任何需要避免的内容。

如果他们提供了 URL,则在编写任何内容之前先阅读它(网络抓取),以获取产品的实际措辞、功能、颜色和字体名称。对 UI 标签和标语使用他们的实际词汇。

切勿捏造事实。 不得出现编造的客户数量、收入、评分、推荐或引语。仅当用户或网站明确说明时才使用数字;否则使用相对条、进度和占位符骨架线条。UI 内部的内容示例(提示、文件名、项目名称、收据上的价格)是可以的,并且应该是可信的;关于产品本身的声明(统计数据、用户数量、评分、像“免费”这样的优惠)则不行。行动号召仅使用用户或网站提供的 URL 或报价;如果没有,则以名称和标语结束。

Logo。 仅当用户提供文件,或者这是他们自己的产品并且他们要求使用时才使用 Logo。否则,使用品牌字体将名称写为文本。第三方 Logo(集成)仅在提供时使用;否则使用带有名称的居中圆角方块。

此风格的默认时长:15 秒(128 BPM,8 小节)。范围为 12–20。

步骤 2:规划场景,然后构建

结构(6–8 个场景,每个 1.5-3 秒,基于 120-140 BPM 的网格)

从这些场景类型中选择,并将产品映射到它们上。首先编写节拍表(场景、节拍网格上的开始时间、引入什么、带出什么、声音)。

  1. 开场火花(黑暗)。近黑色画布,带有微弱的网格。出现一个小高亮标记,脉动,拉伸成一条细水平线横跨画面,带有一个小的等宽标签 (产品 / 发布 — 2026),抖动一次,然后线条垂直扩展成整个屏幕的渐变,充满高亮颜色。该渐变就是下一个背景。
  2. 品牌冲击。 产品名称使用粗无衬线字体,全大写,巨大,通过在连续的 1/16 拍上添加遮罩逐字母显示,上方有一个小的等宽踢脚线 (INTRODUCING — 2026),下方有一条斜体衬线行(它是什么,2–4 个词)。字重可以从细变为粗。句号或字母的点脱离并变大,成为下一个场景开始的形状。
  3. 动态文字三重奏。 价值主张以三个简短的单词或短语形式出现,每个两拍(手写体单词则为三拍,以便阅读),每个都有其自己的处理方式和自己的背景,在节拍上硬切换(在此场景内部,切换就是节奏,因此不算作剪辑):暗色背景上使用奶油色;黑色背景上使用高亮色,单词以轮廓图案重复填满画面;电动蓝背景上使用白色,在取景器角括号内带有一个小 1920 × 1080 标签;最后一个词使用手写体脚本自行绘制。每个词以快速模糊/滑动进入,并带有轻微过冲。
  4. 标志性视觉。 一个“他们是怎么做出来的”时刻,象征产品:一个 3D 立方体场从中心泛起涟漪(在画布上使用您自己的透视投影绘制,根据调色板按高度着色),一个生成粒子流凝聚成一个环并向外爆发,或一个液态斑点在不同形状之间变形。选择适合产品概念的那个(积木块 → 立方体,数据/AI → 粒子,灵活性 → 液态)。在播放期间,摄像机缓慢倾斜或环绕。
  5. 产品微瞬间。 产品中的一次真实交互,显示为浮动 UI 卡片,略微 3D 倾斜,后面堆叠有两张幽灵卡片:光标拨动开关,按下主按钮,进度条以高亮色填充并显示百分比,按钮变为 完成 ✓,右上角滑入一个提示条。使用产品的实际标签。
  6. 证明(仅当存在真实数据时)。 一个大的计数动画(+312% 风格,数字在旋转时模糊),一个环形图,错落有致的条形图上升,其中一个为高亮色,一条线在它们上方绘制并带有一个工具提示。没有真实数字 → 跳过此场景,或将产品的实际功能显示为一个猛冲入内的列表。
  7. 液态/变形桥接。 上一个场景中的形状收缩成一个点,该点变成黑暗背景上的脉冲环,脉冲环变成 Logo 中的点。
  8. 结束卡片(保持 2 秒)。Logo 或名称,带有高亮色点,斜体衬线标语,一个间距等宽行逐字母输入(如果提供,为实际 URL 或行动号召,例如 PRODUCT.COM;否则为简介中的短行),一条细线。

并非每个产品都需要每个场景;6 个强场景胜过 8 个弱场景。保持每个场景一个想法。

让视频看起来高级的规则

  • 转场是对象,不是剪辑。 每个场景通过一个形状(渐变、由增长条执行的擦除、变大成为下一个背景的点、穿过摄像机的词)传递给下一个场景。场景之间的硬剪辑仅在重拍上允许,最多两次。
  • 一切都在网格上。 大的变化在节拍上进行,文字排版在 1/8 或 1/16 音符上。对冲击效果略微过冲(z ≈ 0.6-0.7),然后稳定。
  • 每个场景上的 HUD 框架: 细角括号,右上角为 场景 02 / 07,左下角为微小的等宽类别标签(动态文字、产品、数据),右下角为运行时间码,带有一个红点。小尺寸(1080p 下为 12-14 像素),约 50% 不透明度;应感觉像摄像机取景器。
  • 纹理: 每个纯色背景上添加柔和的径向渐晕,非常细微的胶片颗粒(在加载时预生成几个种子颗粒块并循环使用),以及真实的运动模糊。
  • 字体: 粗无衬线字体用于冲击(例如 Inter Tight 900 或 Archivo Black),斜体衬线字体用于人情化行(Instrument Serif 斜体),等宽字体用于 HUD 和字幕(JetBrains Mono 或 Geist Mono)。如果品牌有字体,则对名称使用它们的字体,并保持等宽字体用于 HUD。
  • 默认调色板: 墨水 #0B0B10,高亮 #FF4F1A,奶油 #EDEBE4,电动蓝 #2F47FF,白色。品牌的主色替换高亮色,其次要色替换蓝色;单一颜色品牌使用其颜色的深色调作为次要色。
  • 禁止项: 模板式外观、到处都是发光效果、表情符号、一个场景中使用超过 3 种颜色(不包括黑白)、小于一目了然可读的文本、任何没有动效的场景。

声音

128 BPM(或用户的曲目)。强有力:每拍一个底鼓,第 2 和第 4 拍拍手,一个跟随场景根音的低音,一个滤波上升音进入品牌冲击和结束卡片,每次渐变时一个冲击音(低音轰鸣 + 噪声爆发),每个输入单词时一个短调音调,每次进行转场时一个嗖嗖声(带通滤波噪声扫描),结束卡片上一个柔和的声音延音。将音乐混合在音效之下。

页面构建方式

一个自包含的 HTML 文件(内联 CSS 和 JS;仅当用户提供时才包含图片)。使用 DOM + CSS 变换、SVG 或一个全屏画布,选择合适的方式。

页面契约

  • window.VIDEO = {w, h, duration} 和 window.seek(t),一个时间的纯函数:它设置时间 t(秒)的每个元素。没有 CSS 过渡或动画,没有 setTimeout,没有从上一帧继承的状态。任何帧都必须独立、以任何顺序正确渲染。在 seek 内部测量布局(例如 offsetLeft、measureText)是可以的;它是确定性的。
  • 随机性(颗粒、粒子、音频中的噪声)来自种子生成器,从不使用 Math.random。
  • 任何需要历史记录的内容(跟随移动物体而滞后的摄像机、模拟)在加载时一次烘焙完成:以 1/240 秒的步长从 0 到 duration 逐步执行,存储值,并让 seek 使用插值读回它们。对于循环,从循环开始的状态开始模拟(参见风格的无缝规则)。
  • 循环: seek(t) 使用 t % duration;跨越接缝的效应(例如,着陆时的挤压、声音尾音)在两端绘制或混合。
  • URL 中包含 ?render 时:无控件,无预览缩放;舞台恰好为 w × h,位于左上角。没有 ?render 时:舞台使用 CSS 缩放以适应窗口,并显示预览控件。
  • 字体:嵌入它们。 下载 woff2 文件(Google Fonts 或品牌的)并将它们内联为 base64 @font-face,这样渲染永远不会静默回退。对于字重变形,使用具有范围的变量字体([email protected])。
  • 图层: 给场景图层透明的背景,让它们后面的一个舞台背景提供基色,这样后面的图层就不会意外隐藏前面的图层。绝对定位的文本获得 white-space: nowrap。
  • 速度: 大型的 CSS filter: blur() 和大型模糊渐变在截屏时非常慢。在加载时将渐变和发光效果一次绘制到画布(或作为图像)上并移动它。

辅助函数:粘贴它们并在此基础上构建所有内容

// 阻尼弹簧阶跃响应 0 -> 1。w = 速度(8 慢,14 正常,22 快),z = 阻尼(1 = 无过冲)
function spring(t, w = 14, z = 0.82) {
  if (t <= 0) return 0;
  if (z >= 1) return 1 - Math.exp(-w * t) * (1 + w * t);
  const wd = w * Math.sqrt(1 - z * z);
  return 1 - Math.exp(-z * w * t) * (Math.cos(wd * t) + (z * w / wd) * Math.sin(wd * t));
}
// 一个值多次改变目标,每次弹簧过渡:track(start, [[time, value, w?, z?], ...]) -> f(t)
const track = (v0, keys) => t => { let v = v0, p = v0; for (const [tk, val, w, z] of keys) { v += (val - p) * spring(t - tk, w, z); p = val; } return v; };
// 精确缓动关键帧(最适合摄像机移动):kf([[t0, v0], [t1, v1, easeFn?], ...]) -> f(t)
const kf = keys => t => { if (t <= keys[0][0]) return keys[0][1]; for (let i = 1; i < keys.length; i++) { const [a, va] = keys[i - 1], [b, vb, e = ease] = keys[i]; if (t <= b) return lerp(va, vb, e((t - a) / (b - a))); } return keys[keys.length - 1][1]; };
const clamp = (x, a = 0, b = 1) => Math.min(b, Math.max(a, x));
const ease = x => (x = clamp(x), x < .5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2);   // easeInOutCubic
const lerp = (a, b, k) => a + (b - a) * k;
const seg = (t, a, b) => clamp((t - a) / (b - a));      // t 在 [a, b] 中的 0..1 进度
const rng = s => () => (s = (s + 0x6D2B79F5) | 0, s = Math.imul(s ^ (s >>> 15), 1 | s), s ^= s + Math.imul(s ^ (s >>> 7), 61 | s), ((s ^ (s >>> 14)) >>> 0) / 4294967296);
  • 打字效果:可见字符串是 full.slice(0, n(t)),带有一个从 t 开始闪烁的光标。计数动画:格式化 lerp(a, b, ease(seg(t, …)))。
  • 摄像机是一个包装器元素,带有由 kf 或 track 驱动的 transform: translate() scale() rotate()。

声音

  • window.buildAudio(ctx) 从时间 0 开始,在任何给定的 AudioContext 或 OfflineAudioContext 上安排整个音轨(音乐 + 效果),使用与视觉相同的时间线数字。使用振荡器、滤波噪声(来自种子缓冲区)和包络线进行合成。如果用户提供了音乐文件,则使用 fetch + decodeAudioData 加载它,将其节拍网格设置为其节奏(测量它),并仅在上面添加效果。
  • 预览播放: 首次播放时,将音轨一次渲染到缓冲区,然后从任何拖动的位置播放。时钟驱动 seek:
let buf, ac, src, startAt = 0, offset = 0, playing = false;
async function play() {
  ac ??= new AudioContext();
  if (!buf) { const off = new OfflineAudioContext(2, Math.ceil(48000 * VIDEO.duration), 48000); await buildAudio(off); buf = await off.startRendering(); }
  src = ac.createBufferSource(); src.buffer = buf; src.connect(ac.destination);
  src.start(0, offset % VIDEO.duration); startAt = ac.currentTime; playing = true;
}
function pause() { if (!playing) return; offset += ac.currentTime - startAt; src.stop(); playing = false; }
function now() { return playing ? (offset + ac.currentTime - startAt) % VIDEO.duration : offset; }
(function loop() { seek(now()); requestAnimationFrame(loop); })();   // 拖动条设置 offset(暂停,设置,播放)
  • 将节拍表作为注释放在脚本顶部:每个时刻一行,包含其时间、发生的事情和哪个声音。

步骤 3:检查、渲染、交付

在工作文件夹中一次性设置:npm i playwright,然后如果尚未安装 Chromium,则运行 npx playwright install chromium;ffmpeg 必须在 PATH 中。将脚本保存为此文件底部的 render.mjs。

  1. 静态图。 node render.mjs demo.html check.png --stills 0.5,1.2,…,每行节拍表一个时间(节拍表以可读大小显示,每行 3 个)。查看它。修复任何裁剪、重叠、不可读、不符合品牌、构图不佳或空白的问题。再次检查。
  2. 草稿。 node render.mjs demo.html draft.mp4 30 1 4(快速:30 fps,无运动模糊,4 个并行工作线程;将最后一个数字设置为机器的核心数)。这是评审副本:将其与 HTML 一起发送给用户,并在最终版本之前询问更改。
  3. 最终版。 node render.mjs demo.html demo.mp4 60 8 8。运动模糊将渲染时间乘以子帧数;一个 60 秒的最终版在笔记本电脑上可能需要 20-60 分钟,因此在开始之前告知用户。在依赖前述程序(playwright、ffmpeg)连接之前添加检查。

所有平台共享一个渲染脚本,它们在渲染子命令后接受不同的标志。

该脚本假定一个标准化的路径:

  • HTML 文件是唯一必需的参数。
  • 输出路径。
  • fps(默认 30)。
  • 子采样步长(1 = 无运动模糊,2+ = 运动模糊)。
  • 并行工作线程数(默认 4)。

在发送最终文件之前,删除 ?render 版本并确认 HTML 与预览控件一起使用。将文件发送给用户,询问是否对输出满意或是否需要任何更改。

render.mjs

#!/usr/bin/env node
// 共享渲染脚本。
// 用法:node render.mjs <html> <output> [fps] [subsample] [workers]
import { chromium } from 'playwright';
import { execSync, spawn } from 'child_process';
import { writeFileSync, mkdirSync, existsSync } from 'fs';
import { join, dirname } from 'path';

const [html, out, fps = '30', subsample = '1', workers = '4'] = process.argv.slice(2);
if (!html || !out) { console.error('Usage: node render.mjs <html> <output> [fps] [subsample] [workers]'); process.exit(1); }

const isStills = out.endsWith('.png');
const stillTimes = process.argv.includes('--stills') ? process.argv[process.argv.indexOf('--stills') + 1].split(',').map(Number) : [];

async function render() {
  const browser = await chromium.launch({ headless: true });
  const page = await browser.newPage();
  await page.goto(`file://${join(process.cwd(), html)}?render`);
  await page.waitForFunction(() => window.VIDEO && typeof window.seek === 'function');

  if (isStills) {
    const frames = [];
    for (const t of stillTimes) {
      await page.evaluate(t => window.seek(t), t);
      frames.push(await page.screenshot({ type: 'png' }));
    }
    writeFileSync(out, frames[0]);
    // 简单的多静态图逻辑被省略;对于多个,可以输出到目录
    console.log('Stills saved');
  } else {
    const w = await page.evaluate(() => window.VIDEO.w);
    const h = await page.evaluate(() => window.VIDEO.h);
    const duration = await page.evaluate(() => window.VIDEO.duration);
    const totalFrames = Math.ceil(duration * Number(fps));
    const frameDir = join(dirname(out), 'frames');
    mkdirSync(frameDir, { recursive: true });

    const workerCount = Number(workers);
    const framesPerWorker = Math.ceil(totalFrames / workerCount);
    const workersList = [];

    for (let i = 0; i < workerCount; i++) {
      const start = i * framesPerWorker;
      const end = Math.min((i + 1) * framesPerWorker, totalFrames);
      workersList.push(spawn('node', ['-e', `
        const { chromium } = require('playwright');
        (async () => {
          const br = await chromium.launch({ headless: true });
          const pg = await br.newPage();
          await pg.goto('file://${join(process.cwd(), html)}?render');
          await pg.waitForFunction(() => window.VIDEO && typeof window.seek === 'function');
          for (let f = ${start}; f < ${end}; f++) {
            const t = f / ${fps};
            await pg.evaluate(t => window.seek(t), t);
            const buf = await pg.screenshot({ type: 'png' });
            require('fs').writeFileSync('${frameDir}/frame_' + String(f).padStart(6, '0') + '.png', buf);
          }
          await br.close();
        })();
      `], { stdio: 'inherit', shell: true }));
    }

    await Promise.all(workersList.map(w => new Promise((res, rej) => { w.on('exit', res); w.on('error', rej); })));
    
    // 使用 ffmpeg 合成视频
    execSync(`ffmpeg -y -framerate ${fps} -i ${frameDir}/frame_%06d.png -c:v libx264 -pix_fmt yuv420p -crf 18 -preset veryslow ${out}`, { stdio: 'inherit' });
    
    // 清理帧
    execSync(`rm -rf ${frameDir}`);
  }

  await browser.close();
}

render().catch(console.error);

Individual skills in this repo

This repo contains 3 individual skills — each has its own dedicated page.

ClaudinNarsis/claude-video-skills

Make a clean 45–90 second product demo video for any software product, rendered from code to MP4 with music. Airy light canvas, short centred lines the camera pushes into, the product's own UI rebuilt and shown up close as it is used (typing, results, tables, clicks), integrations, and a logo close. Use when someone wants a product demo, launch video, feature walkthrough or "how it works" video for an app, SaaS or tool.

ClaudinNarsis/claude-video-skills

Make a 10–30 second continuous-shot explainer for any product, rendered from code to MP4 with sound. One hero element (a ball, a cursor, a file, a message) travels through the product's workflow laid out as stations on one big canvas; the camera follows it, each station reacts as it passes, then the camera pulls out to reveal the whole journey. Use when someone wants an engaging, story-driven, one-take explainer or a loopable product animation.

ClaudinNarsis/claude-video-skills

Edit video and audio with local FFmpeg from natural-language requests: cut, trim, join, resize/reframe (9:16, 1:1), speed change, captions and subtitles (SRT/ASS, animated, karaoke), logos and text overlays, lower-thirds and titles, silence removal, multicam and external-mic sync, loudness normalisation, HDR/Dolby Vision to SDR, LUTs, background music with ducking, platform exports (YouTube, Reels, TikTok, X), compliance checks, scene detection and highlight reels, contact sheets to inspect results, and whole-edit project files. Use this skill whenever the user mentions a video or audio file (mp4, mov, mkv, wav, m4a), footage, a clip, captions, subtitles, a reel or short, YouTube/Instagram/TikTok delivery, LUFS, sync, transcoding, ffmpeg, or asks to make something "60 seconds", "vertical", "louder", "captioned" — even when they do not say "edit". Python 3.10+ standard library only (3.9 deprecated), no cloud, no API keys.

相关技能