关于 vox-skill
vox-skill 是使用 React 和 Remotion 制作纪实风格旁白影片的生产技能,覆盖脚本、配音计时、分层场景、音频处理、质检与渲染,并支持竖屏影片。
主要功能
- 01按节拍规划旁白
- 02生成配音与计时数据
- 03用 React 构建分层动画场景
- 04制作代码绘制的视觉素材
- 05混合并处理旁白与音乐
- 06渲染横屏或竖屏影片
适用场景
- 01根据主题制作旁白纪录片
- 02让场景切换与口播词同步
- 03制作动画图表和文档画面
- 04完整渲染前检查场景布局
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jaredcassoutt/ vox-editorial --skill vox-skill安装后在 Claude Code 中输入/vox-skill确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
LICENSEREADME.mdexample/.prettierrc.jsoncues.jsonpackage.jsonremotion.config.tsscript.jsontiming.jsontsconfig.jsonexample/src/index.tsRoot.tsxexample/src/scenes/AnnexationScene.tsxBaylorFoundingSequence.tsxCloserScene.tsxFoundersScene.tsxNamingScene.tsxOlderThanStateScene.tsxPetitionScene.tsxRepublicScene.tsxSigningScene.tsxexample/src/shared/fonts.tspieces.tsxsystem.tsxtexas.tstiming.tsreferences/scene-patterns.mdvertical.mdvisual-system.mdscripts/captions.pycutout.swift详细介绍
Remotion 纸张 B-roll
输入一个主题,输出一部 1920×1080 的配音影片,以 React 构建。整体风格是编辑式纪录片:靠内容本身引人入胜来抓住观众,而不是靠大声喧哗。
所有动画都由代码驱动。无需经过 After Effects,也无需拖动时间轴。你只需编写组件并调整属性,就能导演这部影片。
先选择合适的技能
| 你的需求 | 使用 |
|---|---|
| 横屏 1920×1080、React/Remotion、以代码绘制图表和报纸 | 此技能 |
| 适用于 TikTok、Reels 或 Shorts 的竖屏 1080×1920 | 此技能、--vertical — 参见 references/vertical.md |
| 任意配音解说视频,制作流程尚未确定 | explainer-studio |
| 配有声音设计、快节奏剪辑的产品广告 | fastcut-ad |
决定质量的因素,按重要性排序
- 作为基础的事实。 一个真正出人意料、可核查的反转。没有它,影片便没有存在的理由,再精湛的工艺也无法补救。
- 配音稿。 它是节奏和主线。每次切镜都与一个说出的词同步。
- 层次。 画面中是否有三个相互独立运动的层次。
- 其他一切。
按这个顺序分配精力。
制作流程
共十个阶段。第 N 阶段的产物完成之前,不要开始第 N+1 阶段。
1. 找到反转,写出节拍
找到那个结果与观众预想恰恰相反的事实。不是一个统计数字,而是一个矛盾。
写出 8 到 12 个节拍,每行一个想法。 脚本就是时间轴。一个节拍是一句话或一个分句,足以承载一幅画面。
钩子是第 1 个节拍,并且要隐去主题。 不要出现专有名词:不能提国家、人物、机构或年份。它要陈述一件不可能成立的事。最后一个节拍要回答这个确切的问题。用两个问题检验草稿:观众能从钩子中说出主题吗?如果能,就重写。最后一句是否偿还了钩子留下的悬念?如果没有,其中一句就有问题。
将节拍写入 script.json:
{"topic": "...", "beats": [{"id": 1, "narration": "This university is older than the state it stands in."}]}
2. 先生成语音,再确定时间
必须在任何动画之前生成配音,因为它的时长决定每一帧的数量。
python3 -m venv .venv && ./.venv/bin/pip install -q edge-tts
./.venv/bin/python ~/.claude/skills/vox-skill/scripts/gen_vo.py
这会生成 vo/beat_NN.mp3、合并后的 public/audio/vo.mp3,以及 vo/timing.json,其中包含每个节拍的开始时间、结束时间、时长、帧区间和逐词边界。下游所有时长都从该文件读取。绝不要猜测帧数。
语音默认使用 en-GB-RyanNeural,速率为 +4%。使用 VOICE= 和 RATE= 覆盖。提交之前值得先试听,因为影片完成后重新配音,只有在你已完成第 3 步的情况下才算轻松:
| 语音 | 风格 |
|---|---|
en-US-AndrewMultilingualNeural | 较新的 HD 模型,温暖而自信。最佳默认选择。 |
en-US-ChristopherNeural | 标注为“可靠、有权威感”。经典旁白风格。 |
en-GB-RyanNeural / en-GB-ThomasNeural | 英式口音,风格更冷静 |
en-US-SteffanNeural | 最接近直白的新闻播报 |
语音模型没有混音处理重要。 参见第 6 阶段。
3. 从时间数据推导时间点,不要手动填写
根据波形手动微调帧数,只在语音不变时才有效。只要更换语音、速率或一个词,所有数字都会出错。
编写 cues.json,标明每个元素出现时对应的单词,然后据此生成时间模块:
python3 ~/.claude/skills/vox-skill/scripts/sync_timing.py --check # inspect
python3 ~/.claude/skills/vox-skill/scripts/sync_timing.py # write src/shared/timing.ts
场景通过 D 获取时长,通过 CUE 获取每个起始帧。此后重新配音只需运行三条命令并重新渲染,而不必花上一个下午。--check 还会检查每个 cue 是否超出场景时长。
4. 将节拍映射到场景
场景数量少于节拍数量。 两三个节拍通常可以共用同一幅持续呈现的画面;旁白继续时,场景保持不变会显得更从容。十个节拍对应七个场景,是不错的比例。
场景时长是其对应节拍的帧区间总和,取自 vo/timing.json。编写任何组件之前,先写下映射关系。
5. 制作素材
素材分为三类,按优先顺序排列。
代码绘制(最佳)。 报纸、图表、文档、地图、账本、打字机文字、划掉的列表。无需文件、无需抠图,可无限调整,还能原生动画。优先选择这种方式。参见 references/scene-patterns.md。
透明 PNG 抠图。 通过 Vision 在本地抠出主体,免费且离线:
swiftc -O ~/.claude/skills/vox-skill/scripts/cutout.swift -o /tmp/cutout
/tmp/cutout raw/portrait.jpg public/scene-04/portrait.png
python3 ~/.claude/skills/vox-skill/scripts/halftone.py public/scene-04/portrait.png
halftone.py 会应用纸张效果:降低饱和度、提高对比度、添加网点、让墨色偏暖。它能让现代照片呈现出 19 世纪纸张上的效果。
如果 Vision 找不到主体,则使用 PIL 作为备用方案:以约 55 的容差从四个角进行泛洪填充,然后仅保留最大的连通不透明区域,以保留衣领等内部填充区域。切勿从边缘中段开始泛洪填充,否则会渗入较宽的主体。
透明视频。 转码为带 alpha 通道的 VP9,然后使用 <OffthreadVideo transparent />:
ffmpeg -i in.mov -c:v libvpx-vp9 -pix_fmt yuva420p -vf scale=1920:-2 -b:v 0 -crf 32 out.webm
纸张背景应控制在 5 MB 以内,分辨率严格为 1920×1080。28 MB 的背景会让 Studio 丢帧,而丢帧会导致音频与画面失去同步,影响调试。
6. 基于固定的视觉系统构建场景
只需确定一次视觉系统,此后每个场景都是它的变体。完整的配色方案、字体比例和动画规则见 references/visual-system.md。以下三点不可妥协:
每个场景都由三层组成。
- 背景 — 每个场景都使用相同的纸张纹理,覆盖整个画面,
zIndex 0,上方叠加柔光效果。背景绝不改变。正是这种连贯性,让七个场景看起来像一部完整的影片。 - 中景 — 主体以抠图形式加入,每个主体都带有偏移的红色标记笔描边。
- 前景 — 遮挡主体的下半身,为镜头构图提供锚点。
缺少前景的场景看起来就像幻灯片。遮挡才能营造出景深。
红色标记笔描边是标志性元素。在图像后方渲染抠图的纯色剪影,并偏移几个像素,主要水平偏移,让描边从主体一侧显现:
<div aria-hidden style={{
...sameBoxAsImg,
backgroundColor: "#E04329",
maskImage: `url(${assetUrl})`, WebkitMaskImage: `url(${assetUrl})`,
maskSize: "100% 100%", WebkitMaskSize: "100% 100%",
maskRepeat: "no-repeat", WebkitMaskRepeat: "no-repeat",
transform: `translate(calc(-50% + ${strokeX}px), ${rise + strokeY}px)`,
}}/>
<Img src={assetUrl} style={{ ...sameBoxAsImg, transform: `translate(-50%, ${rise}px)` }} />
strokeX 约为 ±26,strokeY 约为 8。根据主体所在位置调整方向:画面左侧的主体向左描边,右侧的主体向右描边。
元素应当登场,而不是淡入。 使用 spring() 从约 390px 处上升,参数为 {damping: 18, mass: 0.75, stiffness: 110},并叠加持续 16 帧的透明度渐变。图表和签名通过 interpolate() 对 strokeDashoffset 进行绘制。错开元素的入场时间,确保没有两个元素在同一帧出现。
每个场景都使用带有精调属性的 zod schema。 为每个元素设置位置、缩放比例以及起始帧和结束帧。这样才能在 Studio 中调整影片,而不必每次微调都重新编译。
7. 评分并完善音频
判断背景音乐时,要听笔记本扬声器实际播放出来的效果,而不是看频谱图。
一段真实的纪录片配乐有 93% 的能量低于 250 Hz。照搬这个目标,会让背景音乐在所有没有低音炮的扬声器上都听不见,因为手机和笔记本会大幅衰减 ~200 Hz 以下的声音。要比较音乐在 250 Hz 以上的音量与语音在 250 Hz 以上的音量。这个数字就能决定听众是否听得到音乐。
# 语音片段与两句台词之间的间隙,均应用高通滤波
ffmpeg -ss <speech> -t 1.5 -i mix.wav -af "highpass=f=250,volumedetect" -f null /dev/null
ffmpeg -ss <gap> -t 0.3 -i mix.wav -af "highpass=f=250,volumedetect" -f null /dev/null
| 250 Hz 以上的音量差 | 评估 |
|---|---|
| 30 dB 或更高 | 听不见;听众会说“没有音乐” |
| 18-22 dB | 听得到且不突兀。目标值。 |
| 低于 8 dB | 挤压旁白空间 |
score.py 会将此项显示为小扬声器扣分,并列出整段内容中四个位置的可听音量,因此在渲染前就能发现一段音乐静音的情况。
python3 ~/.claude/skills/vox-skill/scripts/score.py music/bed.wav --seconds <SEQ_SECONDS>
bash ~/.claude/skills/vox-skill/scripts/mix_audio.sh \
public/audio/vo.mp3 music/bed.wav /tmp/mix.wav -20
score.py 还会打印自身的频谱平衡与这些目标值的对比。背景音乐应裁剪到序列长度,而不是语音长度,这样才能覆盖结尾停留画面。
背景音乐通过 music_db 偏移量设置音量,并在语音播放时通过侧链再降低约 6 dB,这样它就会在间隙中逐渐回升,而不是始终保持平直。**-18 到 -22 dB 是教科书式的数值,前提是混音覆盖全频段。**如果背景音乐的低频占比过高,那么在关键频段,它的音量会低 30 dB 以上。应根据测得的音量差设置偏移量,而不是照搬惯例。如果背景音乐的中频足够突出,通常设为 -12 dB 左右。
**短片要留意编排曲线。**曲线表示各部分占整段内容的比例,因此某部分在 0.20 处才达到完整状态,意味着在一段 65 秒的成片中,前十三秒都没有这部分内容。开场只有次低音无人机音效,会让人觉得“没有音乐”;而所有人都会评判开场。要从一开始就让各个音乐部分保持一定音量。
mix_audio.sh 还会对旁白进行母带处理,这也是“文本转语音”和“纪录片旁白”之间大部分差距的来源:
| 处理 | 原因 |
|---|---|
| 85 Hz 高通滤波 | 去除语音中不会用到的隆隆声 |
| 350 Hz 处降低 2 dB | 消除所有神经 TTS 都有的箱音 |
| 3 kHz 处提高 2.5 dB | 增强临场感,让辅音清晰落地 |
| 6.8 kHz 处降低 3 dB | 去齿音;TTS 的嘶嘶声比真人发声更刺耳 |
| 3:1 压缩 | 让较轻的分句与较响的分句音量一致 |
| loudnorm -16 LUFS | 适合手机和笔记本播放 |
让合成内容使用混音后的文件,而不是原始 VO 文件。
8. 渲染前检查静帧
**每个场景渲染一张静帧,并在开始完整渲染前放到联络表上检查。**一段 70 秒的影片需要渲染 10 分钟;静帧只需几秒。各种碰撞、换行和比例错误都能在这里显现。
for f in 200 541 695 1056; do
npx remotion still src/index.ts <SequenceId> out/qc/f$f.png --frame=$f --log=error
done
ffmpeg -y -i out/qc/f200.png -i out/qc/f541.png -i out/qc/f695.png -i out/qc/f1056.png \
-filter_complex "[0:v]scale=720:-1[a];[1:v]scale=720:-1[b];[2:v]scale=720:-1[c];[3:v]scale=720:-1[d];\
[a][b]hstack[t];[c][d]hstack[u];[t][u]vstack" out/qc/sheet.png
每一帧都要选场景内容最丰富的时刻,而不是第一帧。这一轮检查通常能发现:
| 现象 | 原因 |
|---|---|
| 印章或标签叠在主体上,难以辨认 | 红色叠在墨迹上。把它移到干净的纸面上,不要改样式。 |
| 两个元素互相贴着 | 绝对位置发生了偏移。移动其中一个,不要把两个都缩小。 |
| 跟踪字幕在短语中间换行 | 添加 whiteSpace: "nowrap"。 |
| 标题换行 | 同样的处理,并将字号缩小 15–20%。 |
| 代码绘制的 SVG 图像显示得很小 | viewBox 范围比路径宽松。将其边界设为路径的实际范围。 |
| 形状看起来像另一种物体 | 剪影需要有足够的宽度。肩膀太窄会像钥匙孔,而不是半身像。 |
9. 组装并渲染
使用 <Series> 将场景串联起来,每个场景对应一个 <Series.Sequence>,时长采用第 3 步确定的时长。
位置属性应与每个 <Composition> 的 defaultProps 保持一致。时序属性需在序列中重新调整,因为单独调试的场景几乎总是比它必须适配的节拍窗口更长。
通过标志控制音频,使合成默认静音,并单独交付各音轨以供混音:
const BAKE_AUDIO = false; // true → VO at 1.0, music ducked to ~0.2, 24f fade in, 50f fade out
npx remotion studio # preview and tune
npx remotion render src/index.ts <SequenceId> out/final.mp4 # render
ffmpeg -i out/final.mp4 -c:v copy -an out/video.mp4 # strip the silent track
只有在每个 <Composition> 的 defaultProps 都是内联对象字面量,而非导入的变量,并且项目根目录存在 .prettierrc.json 时,Studio 才能将属性编辑保存回磁盘。两项缺一不可。配置错误会导致每次重新加载时都丢失所有调试结果。
10. 选择海报帧
绝不要使用默认缩略图。 按这种方式制作的影片,第 0 帧只有空白纸张背景,还没有任何内容出现——这是文件中最糟糕的一帧,却是所有播放器都会选用的一帧。
python3 ~/.claude/skills/vox-skill/scripts/pick_thumbnail.py \
out/film.mp4 out/thumbnail.png --embed --report
它会为每个候选帧评分,并选出元素最丰富、构图最完整的时刻。“有意思”指的是多个不同元素分布在整个画面中,而非单纯的墨迹覆盖率——深色前景色带填满画面底部三分之一的镜头虽然覆盖率高,却毫无看点,因此评分的主要项是元素在网格中的空间分布。其余评分项包括边缘密度、设有上限的覆盖率,以及是否出现红色点缀。
--embed 会将其作为封面附加到 mp4 中。--report 会打印候选列表,方便你在更喜欢第二名时选用它。制作竖屏剪辑时传入 --grid 5,否则评分会偏向选择内容位于平台界面后方的帧。
快速参考
| 项目 | 值 |
|---|---|
| 输出 | 1920×1080,30fps,MP4 |
| 纸张底色 / 墨色 / 红色点缀 | #DAD9D5 / #1A1A1A / #E04329 |
| 标题字体 | Arial Black,紧凑字距,全部大写 |
| 衬线字体 | PlayfairDisplay、PTSerif(报纸、文档) |
| 打字机字体 | SpecialElite(结尾句) |
| 入场弹簧参数 | {damping: 18, mass: 0.75, stiffness: 110} |
| 上升距离 | 360–390px,从下方上升 |
| 不透明度渐变 | 16 帧,Easing.out(Easing.cubic) |
| 描边偏移 | strokeX ±26,strokeY 8 |
| 投影 | drop-shadow(0 28px 26px rgba(55,45,31,0.28)) |
| 搭建项目 | scripts/scaffold.sh <dir> <SequenceName> |
| 背景音乐 | scripts/score.py out.wav --seconds N |
| 主音轨 + 闪避 | scripts/mix_audio.sh vo.mp3 bed.wav out.wav -20 |
| 人声下的音乐 | 根据测得的 >250 Hz 频段分离度设置,约 18–22 dB。通常为 -12 dB。 |
| 音乐听起来平淡 | 提高 --motif,而非音量 |
| 竖屏剪辑 | scaffold.sh <dir> <Name> --vertical,然后查看 references/vertical.md |
| 字幕(竖屏必需) | 运行 scripts/captions.py,然后添加 <Captions /> |
| 海报帧 | scripts/pick_thumbnail.py out.mp4 --embed |
| “我听不到音乐” | 调整推子前,先测量 250 Hz 以上的频段 |
| 最终响度 | 综合 -16 LUFS |
常见错误
| 错误 | 修正方法 |
|---|---|
| 猜测帧数,之后再修正同步 | 从 vo/timing.json 读取每段时长。配音是时间基准。 |
| 每个节拍对应一个场景 | 合并节拍。旁白继续时,让画面保持不变,能传递出自信。 |
| 场景之间背景发生变化 | 背景绝不变化。固定不变的纹理能统一整部影片。 |
| 没有前景层 | 添加遮挡元素。没有它,镜头就是幻灯片,而不是画面。 |
| 为代码可以绘制的内容寻找图片素材 | 图表、报纸、文档、地图和列表始终更适合用代码制作。 |
| 所有元素都在同一帧进入 | 错开 20–30 帧。同时进入会显得像套用模板。 |
| 元素淡入 | 让它们弹出。淡入显得过于普通。 |
从常量导入 defaultProps | 将对象字面量直接写入代码,否则 Studio 会悄悄丢弃你的调整。 |
| 硬编码位置,而不是使用 props | 每个位置、缩放和帧数都要放进 zod schema。 |
| 使用 28 MB 的纸张背景 | 裁剪并调整尺寸,精确到 1920×1080,且小于 5 MB。 |
| 在场景 props 中输入帧数 | 在 cues.json 中为词语命名并生成帧数。重新配音不应意味着重新调整。 |
| 只因旋律好听就选择音乐 | 根据频谱选择。如果音乐的频率范围在 200-4000 Hz,无论音量多低都会与人声冲突。 |
| 音乐音量始终保持不变 | 让音乐自动降低音量。间隙中音量渐起的背景音乐听起来像经过混音;音量平直则像生硬地贴上去。 |
| 直接交付原始 TTS | 先进行母带处理。均衡和压缩链比选择哪种声音更重要。 |
| 交付默认缩略图 | 第 0 帧是空白背景。运行 pick_thumbnail.py。 |
| 竖版剪辑没有字幕 | 默认会静音观看。将字幕直接嵌入视频。 |
| 竖版画面中并排放三个主体 | 每个只有 340px。将它们上下堆叠,或分散到不同节拍中。 |
| 渲染影片后才查看效果 | 先为静帧制作联系表。渲染花十分钟,也看不出静帧无法呈现的内容。 |
| 主体没有公有领域形象参考 | 在同一个方框中将其画成墨迹形态。绝不要借用别人的脸。 |
警示信号
vo/timing.json还不存在,就准备编写场景组件- 准备为图表、文档或地图打开图像生成器
- 钩子直接点出主体
- 最后一个节拍没有回答钩子提出的问题
- 场景只有两层
- 因为弹簧效果“看起来太跳”而准备淡入(调整阻尼,不要淡入)
- 还没看过每个场景的静帧,就准备运行完整渲染
- 仅仅因为版式需要,就在画面中放入无法核实的数字或引文
参考资料
references/visual-system.md— 调色板、字体、动态效果,以及完整的分层模型references/scene-patterns.md— 七种可复用的场景原型及可运行代码scripts/gen_vo.py— 旁白、词语时间点和帧窗口scripts/cutout.swift— 使用 Vision 提取主体并生成透明 PNGscripts/halftone.py— 为抠图添加纸张/半色调效果scripts/sync_timing.py— 将 cues.json 和 timing.json 同步到 TypeScript 计时模块scripts/score.py— 为人声留出频谱空间的紧张纪录片风格背景音乐scripts/mix_audio.sh— 旁白母带处理链和侧链闪避scripts/pick_thumbnail.py— 选择并嵌入内容最丰富的帧作为封面scripts/captions.py— 根据旁白的词语边界生成嵌入式字幕scripts/scaffold.sh— 可运行的 Remotion 项目骨架(9:16 使用--vertical)references/vertical.md— TikTok、Reels 和 Shorts 的差异说明