Communitygithub.com

devlucasalm/motion-promo-video-skill

创建高质量的 motion 设计宣传视频 (9:16 / 16:9, 60fps, 含音效/音乐),适用于产品、SaaS、手表等,基于 HTML+Playwright+ffmpeg。适用于 motion 视频、广告视频、Reels、动画宣传片。

motion-promo-video-skill 是什么?

该技能利用 HTML、Playwright 和 ffmpeg 生成高质量的 motion 设计宣传视频。它能够根据用户提供的产品信息,自动创建确定性的 HTML 动画,并渲染为 60fps 的 MP4 文件,包含同步的音效和背景音乐。技能支持 9:16 和 16:9 两种画幅,针对不同的目标受众(如年轻用户或高端用户)提供差异化的视觉和音频风格。工作流程包括图片背景移除、素材放大、字体选择、动画编排、音频合成与母带处理。渲染过程采用分块并行处理,支持断点续传,用户可灵活调整部分内容而无需重新渲染整个视频。该技能在 Claude Code 等 AI 代理平台中使用,通过自然语言描述生成视频。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add devlucasalm/motion-promo-video-skill

在你喜欢的 AI 中提问

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

文档

Motion 宣传视频 (HTML → MP4)

交付: 即用型 MP4 (H.264, yuv420p, AAC) 通过 SendUserFile 发送。请用简体中文回答,简洁明了。

1. 最小化需求说明 (不要过度询问)

按默认值设定,只询问会影响结果的部分:

  • 格式: 默认 9:16 (1080×1920);如需 16:9 (1920×1080) 则指定。时长: Instagram 广告 20–35 秒;节奏: 缓慢 (每个步骤约 1.5–4 秒) — 用户已抱怨过“看起来像 2 倍速”。
  • 受众: 年轻/SaaS 用户 → 明亮的音效 (钟声、弹出声、点击声)。年长/高端用户 (手表、珠宝) → 沉稳: 铺垫音、柔和的冲击声、嗖嗖声、微光音;不要儿童式的弹出声。
  • 品牌: 调色板、字体、Logo、CTA (WhatsApp/URL)、截图/照片。音乐: 如果用户提到某首曲目 (Spotify 等),请勿访问音频 — 告知用户,使用原创配乐,如果用户提供 mp3 可提供同步服务 (并提醒广告授权)。

2. 准备素材

  • 产品照片: 使用 rembg (模型 isnet-general-use) 去除背景 → 带 alpha 通道的 PNG,用于实现“3D”效果 (浮动、带透视的 rotateY/rotateX、通过 -webkit-mask-image 以 PNG alpha 遮罩的发光闪亮层)。
  • 低分辨率截图: 使用 Lanczos 3 倍放大 + UnsharpMask。当用户要求“无广告”时,移除原始照片中的广告文字。
  • 字体: Inter / Inter Display ExtraBold (SaaS、扁平风格), Lora serif + 金色斜体 (高端), Poppins。

3. 用户认可的风格

  • 纯色色块、大卡片、巨型排版 (逐词遮罩揭示)、大量连续运动 (缓慢视差、缓慢缩放 1→1.15、星星/粒子)、通过三个彩色面板的擦除过渡 (与节拍同步)。
  • 无相机抖动 (用户已拒绝晃动): 抖动最大 0.12,无相机旋转,漂移 ≤3 像素。
  • 叙事结构: 钩子/问题 → 解决方案 → 2–3 个功能/集成 → 证据/益处 → CTA。每场 3–5 秒,之间用擦除过渡。在 SaaS 场景中,先具体展示问题 (未回复的消息、亏损的销售 −R$) 再展示解决方案。
  • 文字简短,每行 2–4 个单词,用简体中文,其中一个单词突出显示 (颜色/斜体)。遵守 Instagram 安全区域 (9:16 格式下,避免顶部约 250 像素和底部约 400 像素用于 CTA/字幕)。

4. 引擎 (确定性 HTML)

一个 video.html 文件,其中包含 window.seek(T) 函数,该函数根据时间 T 定位所有内容 (无需 requestAnimationFrame/CSS 动画)。核心:

const cl=(x,a=0,b=1)=>Math.min(b,Math.max(a,x)),lerp=(a,b,p)=>a+(b-a)*p;
const E={outBack:(p,c=1.8)=>1+(c+1)*Math.pow(p-1,3)+c*Math.pow(p-1,2),outExpo:p=>p>=1?1:1-Math.pow(2,-10*p),outQuart:p=>1-Math.pow(1-p,4),inOutQuart:p=>p<.5?8*p**4:1-Math.pow(-2*p+2,4)/2,inOutCubic:p=>p<.5?4*p**3:1-Math.pow(-2*p+2,3)/2,lin:p=>p,inCubic:p=>p**3,inOutSine:p=>-(Math.cos(Math.PI*p)-1)/2};
const ez=(T,a,d,f=E.outExpo)=>f(cl((T-a)/d));
function mk(parent,html,cls='',style=''){const d=document.createElement('div');d.className='abs '+cls;d.style.cssText=style;d.innerHTML=html;parent.appendChild(d);return d} // .abs{position:absolute;left:50%;top:50%}
function P(el,o){const x=o.x||0,y=o.y||0,s=o.s==null?1:o.s,op=o.o==null?1:o.o,r=o.r||0,rx=o.rx||0,ry=o.ry||0;el.style.transform=`perspective(2400px) translate(-50%,-50%) translate(${x}px,${y}px) rotateX(${rx}deg) rotateY(${ry}deg) rotate(${r}deg) scale(${s})`;el.style.opacity=op;el.style.visibility=op<=.003?'hidden':'visible'}
function words(el,arr){el.innerHTML=arr.map(w=>`<span class="mk"><span class="wd ${w[1]||''}">${w[0]}</span></span>`).join(' ')} // .mk{display:inline-block;overflow:hidden;padding:.14em .05em .24em;margin:-.14em -.05em -.24em}
function lineIO(el,T,tin,tout,step=.1,d=1.0){[...el.querySelectorAll('.wd')].forEach((w,i)=>{const a=ez(T,tin+i*step,d,E.outQuart),b=tout?ez(T,tout+i*step*.5,.5,E.inCubic):0;w.style.transform=`translateY(${(1-a)*118-b*118}%)`;w.style.opacity=cl(a*2.2,0,1)*(1-b)})}
function addScene(a,b,bg,build,upd,cam){/* 创建 div.sc 带 bg, inner; scenes.push({a,b,root,inner,obj:build(inner),upd,cam}) */}
function runScenes(T){/* 仅当 T 在 [a-.02,b+.02] 范围内显示; inner.style.transform=来自 cam(t,T) 的 translate/scale; s.upd(t,obj,T) */}
// 擦除: 3 个面板 [深色、金色/强调色、下一场景的背景色],每个面板由 ez(T,t0+i*.09,.38,E.inOutQuart) 控制进入和退出 (t0=擦除-.55);交替方向 x / xr
function seek(T){runScenes(T);drawWipes(T);drawFX(T);drawGrain(T);/* 最终黑色淡出 */}

固定层位于世界之上: 尘埃/粒子画布、擦除、胶片颗粒 (alpha .07)、暗角、用于淡入/淡出的黑色遮罩。 产品 3D 效果: P(hero,{ry:Math.sin(T*.7)*8, rx:Math.sin(T*.5)*3, y:Math.sin(T*1.1)*14, s}) + sweep() (由 PNG 遮罩的发光条纹,mix-blend-mode:soft-light) + 后面有金色径向光晕 + drop-shadow 阴影。 手表式开场 (用户在 v1 中已认可): 760 像素的 SVG,60 个刻度 (每 5 个更粗/金色),金色弧线绘制 (stroke-dashoffset),指针每 0.5 秒前进 6°,使用 E.outBack 缓动,已走过的刻度点亮;文字“时间对每个人流逝。/少数人留下印记。”;旋转的星形 ✦。当产品允许时,可重复使用这种主题开场。 可变速度时间线: SEG=[[ini,fim,fator],…] 应用于 seek 以及音频的 M(t) 函数,以确保音效跟随剪辑点。

5. 渲染 (确定性,可恢复)

  • Playwright Chromium viewport 1080×1920 (或 1920×1080),page.evaluate('seek(t)') + screenshot(type='jpeg',quality=92)。
  • 60fps 带运动模糊: 每帧 2 个子帧 (t=(f+(s+.5)/2-.5)/60),使用 numpy float32 平均 → JPEG q95 → ffmpeg -f image2pipe -c:v mjpeg -i - -c:v libx264 -preset fast -crf 18 -pix_fmt yuv420p seg/NNNNN.mp4。
  • 每个块 120 帧,存放在 seg/ 目录下,2 个并行进程 (处理偶数和奇数块),跳过已存在的块 (容器可能重启)。使用 setsid nohup ./go.sh & 运行,并使用 Monitor 配合 until-loop 检查完成文件 (foreground sleep 会被阻塞;如果 30 分钟内过期,则重新激活 Monitor)。每张截图约 0.38 秒,2 个 CPU: 24 秒 ≈ 1440 帧 ≈ 25–30 分钟。
  • 只修改了开头?只需删除受影响的块并重新渲染;如果只改变了音频,使用 -c:v copy 重新混流。
  • 拼接: ffmpeg -f concat -safe 0 -i list.txt -i audio.wav -c:v libx264 -crf 21 -pix_fmt yuv420p -c:a aac -b:a 192k -shortest -movflags +faststart out.mp4。
  • 在完整渲染之前,在关键时间点截取 2–4 张 seek() 截图,并检查文本裁切/溢出 (两种格式),以及 JS 错误 (page.on('pageerror'))。

6. 音频 (numpy/scipy,合成)

工具函数: lp/hp/bp (butter), add(buf,t,x,g), tt(d);声音: thump (46–96 Hz 正弦波衰减), impact (thump + 噪声 lp 700), riser (频带上升的噪声), swish (bp 1.8–5.5 kHz 噪声,sin² 窗口), shimmer (带衰减的非谐波部分), tick (~900 Hz 正弦波,衰减 90), pad (3 个失谐正弦波 lp 1.4 kHz 带淡入), pluck;混响: 通过衰减噪声卷积实现。

  • SaaS/年轻用户: 明亮的钟声、弹出声、点击声、嗖嗖声 — 不要使用沉重的低音 (用户已表示厌恶“全是低音”)。
  • 高端/年长用户: 在擦除时使用 riser+impact,文字揭示时使用 swish,产品出现时使用 shimmer,列表时使用 ticks,手表开场时每 0.5 秒使用一次钟表 tick,缓慢和弦的垫音 (Dm–Bb–F–C),约 90bpm 的柔和节拍。80–90 bpm 的节拍与每 4 秒的剪辑点相配合。
  • 母带处理 (避免“过载”): 切勿对热混音使用 tanh。应使用 x=x/np.abs(x).max()*.85,保存 wav 并运行 ffmpeg -af loudnorm=I=-16:TP=-2:LRA=11 -ar 44100。检查峰值 < −1 dBFS 且 crest factor > 10 dB。

7. 交付与迭代

发送 MP4 并附带一行说明;提及更改了什么以及未验证的内容 (例如:“已检查帧和指标,未进行听觉检查”)。常见反馈及回复: “太快” → 将时间线缩放 1.5 倍;“抖动” → 减少晃动;“声音不好” → 切换 SFX 配置文件 (明亮 vs. 沉稳);“过载” → 仅重新处理音频并重新混流;“我喜欢 v1 中的动画 X” → 带回场景 X,仅重新渲染受影响的块。 如果用户要求可复用的提示词/文档,生成包含此流程的文档。

相关技能