ismail c5f76b3855
Some checks are pending
Build and Push Docker Image / build (push) Waiting to run
updates
2026-05-11 14:45:30 +03:00

210 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Video ExportHTML 动画导出为 MP4/GIF
动画 HTML 完成后,用户常想「能导出视频吗」。这份指南给出完整流程。
## 何时导出
**导出时机**
- 动画完整跑通、视觉验证过Playwright 截图确认各时间点状态正确)
- 用户在浏览器里看过至少一次,表示效果 OK
- **不要**在动画 bug 没修完的阶段导出——导出到视频后改起来更贵
**用户可能说的触发语**
- 「能导出成视频吗」
- 「转成 MP4」
- 「做成 GIF」
- 「60fps」
## 产出规格
默认一次给三种格式,让用户选:
| 格式 | 规格 | 适合场景 | 典型大小30s |
|---|---|---|---|
| MP4 25fps | 1920×1080 · H.264 · CRF 18 | 公众号嵌入、视频号、YouTube | 1-2 MB |
| MP4 60fps | 1920×1080 · minterpolate 插帧 · H.264 · CRF 18 | 高帧率展示、B站、作品集 | 1.5-3 MB |
| GIF | 960×540 · 15fps · palette 优化 | Twitter/X、README、Slack 预览 | 2-4 MB |
## 工具链
两个脚本在 `scripts/`
### 1. `render-video.js` — HTML → MP4
录一个 25fps 的 MP4 基础版本。依赖全局 playwright。
```bash
NODE_PATH=$(npm root -g) node /path/to/claude-design/scripts/render-video.js <html文件>
```
可选参数:
- `--duration=30` 动画时长(秒)
- `--width=1920 --height=1080` 分辨率
- `--trim=2.2` 从视频开头裁掉的秒数(去掉 reload + 字体加载时间)
- `--fontwait=1.5` 字体加载等待时间(秒),字体多时调高
输出:与 HTML 同目录,同名 `.mp4`
### 2. `add-music.sh` — MP4 + BGM → MP4
给无声 MP4 混入背景音乐按场景mood从内置 BGM 库里选,也可自带音频。自动匹配时长、加淡入淡出。
```bash
bash add-music.sh <input.mp4> [--mood=<name>] [--music=<path>] [--out=<path>]
```
**内置 BGM 库**(在 `assets/bgm-<mood>.mp3`
| `--mood=` | 风格 | 适配场景 |
|-----------|------|---------|
| `tech`(默认) | Apple Silicon / 苹果发布会,极简合成器+钢琴 | 产品发布、AI工具、Skill 宣传 |
| `ad` | upbeat 现代电子,有 build + drop | 社交媒体广告、产品预告、促销片 |
| `educational` | 温暖明亮、轻吉他/电钢琴inviting | 科普、教程介绍、课程预告 |
| `educational-alt` | 同类备选,换一首试试 | 同上 |
| `tutorial` | lo-fi 环境音,几乎无存在感 | 软件演示、编程教程、长演示 |
| `tutorial-alt` | 同类备选 | 同上 |
**行为**
- 音乐按视频时长裁剪
- 0.3s 淡入 + 1s 淡出(避免硬切)
- 视频流 `-c:v copy` 不重编码,音频 AAC 192k
- `--music=<path>` 优先级高于 `--mood`,可以直接指定任意外部音频
- 传错 mood 名会列出所有可用选项,不会静默失败
**典型流水线**(动画导出三件套 + 配乐):
```bash
node render-video.js animation.html # 录屏
bash convert-formats.sh animation.mp4 # 派生 60fps + GIF
bash add-music.sh animation-60fps.mp4 # 加默认 tech BGM
# 或针对不同场景:
bash add-music.sh tutorial-demo.mp4 --mood=tutorial
bash add-music.sh product-promo.mp4 --mood=ad --out=promo-final.mp4
```
### 3. `convert-formats.sh` — MP4 → 60fps MP4 + GIF
从已有 MP4 生成 60fps 版本和 GIF。
```bash
bash /path/to/claude-design/scripts/convert-formats.sh <input.mp4> [gif_width] [--minterpolate]
```
输出(与输入同目录):
- `<name>-60fps.mp4` — 默认用 `fps=60` 帧复制(兼容性广);加 `--minterpolate` 启用高质量插帧
- `<name>.gif` — palette 优化的 GIF默认 960 宽,可改)
**60fps 模式选择**
| 模式 | 命令 | 兼容性 | 使用场景 |
|---|---|---|---|
| 帧复制(默认)| `convert-formats.sh in.mp4` | QuickTime/Safari/Chrome/VLC 全通 | 通用交付、上传平台、社交媒体 |
| minterpolate 插帧 | `convert-formats.sh in.mp4 --minterpolate` | macOS QuickTime/Safari 可能拒打 | B站等需要真插帧的展示场景**交付前必须本地测**目标播放器 |
为什么默认改成帧复制minterpolate 输出的 H.264 elementary stream 有 known compat bug——之前默认 minterpolate 时多次踩到「macOS QuickTime 打不开」的问题。详见 `animation-pitfalls.md` §14。
`gif_width` 参数:
- 960默认—— 社交平台通用
- 1280 —— 更清晰但文件更大
- 600 —— Twitter/X 优先加载
## 完整流程(标准推荐)
用户说「导出视频」后:
```bash
cd <项目目录>
# 假设 $SKILL 指向本 skill 的根目录(自行按安装位置替换)
# 1. 录 25fps 基础 MP4
NODE_PATH=$(npm root -g) node "$SKILL/scripts/render-video.js" my-animation.html
# 2. 派生 60fps MP4 和 GIF
bash "$SKILL/scripts/convert-formats.sh" my-animation.mp4
# 产出清单:
# my-animation.mp4 (25fps · 1-2 MB)
# my-animation-60fps.mp4 (60fps · 1.5-3 MB)
# my-animation.gif (15fps · 2-4 MB)
```
## 技术细节(排错用)
### Playwright recordVideo 的坑
- 帧率固定 25fps无法直接录 60fpsChromium headless 的 compositor 上限)
- 从 context 创建就开始录,必须用 `trim` 裁掉前面的加载时间
- 默认 webm 格式,需要 ffmpeg 转 H.264 MP4 才能通用播放
`render-video.js` 已处理以上问题。
### ffmpeg minterpolate 参数
当前配置:`minterpolate=fps=60:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1`
- `mi_mode=mci` — motion compensation interpolation运动补偿
- `mc_mode=aobmc` — adaptive overlapped block motion compensation
- `me_mode=bidir` — 双向运动估计
- `vsbmc=1` — 可变 size block motion compensation
对 CSS **transform 动画**translate/scale/rotate效果好。
对**纯 fade** 可能产生轻微 ghosting——如果用户嫌弃退化为简单帧复制
```bash
ffmpeg -i input.mp4 -r 60 -c:v libx264 ... output.mp4
```
### GIF palette 为何要两阶段
GIF 只能 256 色。一次 pass 的 GIF 会把全动画色彩压到 256 色通用 palette对米色底+橙色这种细腻配色会糊。
两阶段:
1. `palettegen=stats_mode=diff` —— 先扫描全片,生成**针对此动画的 optimal palette**
2. `paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle` —— 用这个 palette 编码rectangle diff 只更新变化区域,大幅减小文件
对 fade 过渡用 `dither=bayer``none` 更平滑,但文件大一点。
## Pre-flight check导出前
导出前 30 秒自检:
- [ ] HTML 在浏览器里完整跑过一遍,无控制台错误
- [ ] 动画第 0 帧是完整初始状态(不是空白加载中)
- [ ] 动画最后一帧是稳定的收尾状态(不是半截)
- [ ] 字体/图片/emoji 全部正常渲染(参考 `animation-pitfalls.md`
- [ ] Duration 参数与 HTML 里的实际动画时长匹配
- [ ] HTML 中 Stage 检测 `window.__recording` 强制 loop=false手写 Stage 必查;用 `assets/animations.jsx` 自带)
- [ ] 结尾 Sprite 的 `fadeOut={0}`(视频末帧不淡出)
- [ ] 含「Created by Huashu-Design」水印仅动画场景必加第三方品牌作品加「非官方出品 · 」前缀。详见 SKILL.md §「Skill 推广水印」)
## 交付时附带的说明
导出完成后给用户的标准说明格式:
```
**完整交付**
| 文件 | 格式 | 规格 | 大小 |
|---|---|---|---|
| foo.mp4 | MP4 | 1920×1080 · 25fps · H.264 | X MB |
| foo-60fps.mp4 | MP4 | 1920×1080 · 60fps运动插帧· H.264 | X MB |
| foo.gif | GIF | 960×540 · 15fps · palette 优化 | X MB |
**说明**
- 60fps 用 minterpolate 做运动估计插帧transform 动画效果好
- GIF 用 palette 优化30s 动画可压到 3MB 左右
要换尺寸或帧率说一声。
```
## 常见用户追加需求
| 用户说 | 应对 |
|---|---|
| 「太大了」 | MP4提高 CRF 到 23-28GIF降分辨率到 600 或 fps 到 10 |
| 「GIF 太糊」 | 提高 `gif_width` 到 1280或者建议用 MP4 代替(微信朋友圈也支持) |
| 「要竖屏 9:16」 | 改 HTML 源的 `--width=1080 --height=1920`,重新录 |
| 「加水印」 | ffmpeg 加 `-vf "drawtext=..."``overlay=` 一个 PNG |
| 「要透明背景」 | MP4 不支持 alpha用 WebM VP9 + alpha 或 APNG |
| 「要无损」 | CRF 改 0 + preset veryslow文件会大 10 倍) |