Remotion 音效库开发实战:向 @remotion/sfx 添加新音效的完整工作流
Remotion(Make videos programmatically with React)内置了一个 CC0 授权的音效 URL 库 @remotion/sfx,它本身只是一个"字符串常量包",真正复杂的是其背后横跨素材仓库、文档站、目录注册与波形生成的多包协作流程。本文以官方贡献技能文档 .agents/skills/add-sfx/SKILL.md 为主线,完整拆解从素材入库、导出声明、文档页创建到构建发布的 7 个步骤,并结合 sfx 包源码、remotion-media 生成脚本 和 波形采样脚本 说明每一步在仓库中的实际落点,读完即可独立完成一个新音效从 0 到上线的全链路操作。
前置条件:素材必须先进入 remotion.media
在动 @remotion/sfx 之前,必须先在 remotion-dev/remotion.media 素材仓库中登记音效文件,本仓库中的 packages/remotion-media 就是该流程在本 monorepo 中的对应物。仓库对素材有硬性要求:
- WAV 格式(未压缩 PCM,保证跨浏览器与渲染器解码一致);
- CC0(Creative Commons 0)授权,且必须保留出处信息;
- 峰值归一化到 -3dB,避免不同音效之间响度失衡。
此外,文档指出 .env 文件在 worktree 环境中可能缺失,只在主分支(非 worktree)上保证存在,构建部署前需确认环境完整。
source of truth:generate.ts
remotion.media 仓库中的 generate.ts 是唯一事实来源(source of truth)——一个音效必须先在 soundEffects 数组中登记,才能被复制到发布目录并写入 variants.json。对照本仓库的 packages/remotion-media/generate.ts 可以看到这段逻辑的真实实现:
// Sound effects (pre-existing files, CC0 licensed)
const soundEffects = [
{
fileName: 'whoosh.wav',
attribution:
'Woosh by 1bob -- https://freesound.org/s/831936/ -- License: Creative Commons 0',
},
// ... 共 32 条音效条目
];
for (const sfx of soundEffects) {
copyFileSync(sfx.fileName, path.join(outDir, sfx.fileName));
const stat = await Bun.file(path.join(outDir, sfx.fileName)).stat();
variants.push({
videoCodec: 'none',
audioCodec: 'pcm',
container: 'wav',
fileNames: [sfx.fileName],
size: stat.size,
category: 'sound-effects',
attribution: sfx.attribution,
});
}
可以看到:每条 soundEffects 记录只要求 fileName 与 attribution(格式统一为 描述 by 作者 -- 来源链接 -- License: Creative Commons 0),脚本会用 copyFileSync 把 WAV 复制到 files/ 输出目录,并把 size、attribution 等元数据追加进 variants.json(见 variants.json)。当前该数组已收录 32 个音效,packages/remotion-media 目录下也能找到对应的 whip.wav、vine-boom.wav、record-scratch.wav 等源文件。
七步工作流详解
步骤 1:向 remotion.media 仓库登记(必须最先做)
操作顺序不可颠倒,具体为:
- 将 WAV 文件放到仓库根目录;
- 在
generate.ts的soundEffects数组中新增条目:
{
fileName: "my-sound.wav",
attribution:
"Description by Author -- https://source-url -- License: Creative Commons 0",
},
- 运行
bun generate.ts,触发文件复制到files/并重新生成variants.json; - 部署(deploy)使
https://remotion.media/my-sound.wav可公网访问。
这一步的意义在于:@remotion/sfx 导出的是远程 URL 字符串而非文件本身,若素材没有先部署上线,后续所有文档页的试听按钮、波形采样都会失败。
步骤 2:在 packages/sfx/src/index.ts 中新增导出
打开 packages/sfx/src/index.ts,按既有模式追加一行:
export const mySound = 'https://remotion.media/my-sound.wav';
两个命名约束值得注意:
- 变量名统一使用 camelCase;
- 规避 JavaScript 保留字,例如文件叫
switch.wav时导出名应为uiSwitch。
从源码看,该文件的 32 个导出全部采用 export const xxx = 'https://remotion.media/xxx.wav' as const; 的形式——as const 字面量断言保证了类型系统里得到的是精确字符串字面量类型而非宽泛的 string,这让 <Audio src={whip} /> 这类用法在 IDE 中能获得精确提示。其中 uiSwitch(对应 switch.wav)正是保留字规则的实际案例。
步骤 3:创建文档页 packages/docs/docs/sfx/<name>.mdx
以现存的 whip.mdx 为标准模板,新页面必须包含以下完整结构:
---
image: /generated/articles-docs-sfx-whip.png
title: 'whip' # 与 index.ts 中的 camelCase 导出名一致
crumb: '@remotion/sfx'
---
# whip<AvailableFrom v="4.0.429" />
import {PlayButton} from './PlayButton';
<PlayButton src="https://remotion.media/whip.wav" />
<br />
A URL pointing to a whip sound effect WAV file.
## Example
```tsx twoslash title="MyComp.tsx"
import {whip} from '@remotion/sfx';
import {Audio} from '@remotion/media';
const MyVideo = () => {
return <Audio src={whip} />;
};
```
## Value
```bash
https://remotion.media/whip.wav
```
## Duration
0.173 seconds (2 channels, 96000 Hz, 24-bit)
## Attribution
SWSH_Badminton Racquet_Recording_01_JW Audio by JW_Audio -
[freesound.org/s/838766](https://freesound.org/s/838766/) - License: Creative Commons 0
## Convert
[Open this file on remotion.dev/convert](https://remotion.dev/convert?url=https%3A%2F%2Fremotion.media%2Fwhip.wav)
## See also
- `whoosh`
各小节职责:
- Frontmatter:
image为自动生成的封面图、title必须等于导出名、crumb固定为'@remotion/sfx'用于面包屑导航; <AvailableFrom>标签:标注该音效自哪个版本可用,取值为"下一个发布版本"(见步骤 7 前的版本规则);<PlayButton>:来自同目录的 PlayButton 组件,让用户在文档内直接试听;- Example:使用
@remotion/media的<Audio>组件演示实际用法; - Duration:需下载文件后用 macOS 的
afinfo命令获取时长、声道数、采样率与位深(whip 为 0.173s / 双声道 / 96kHz / 24-bit); - Convert:一条带完整 URL 编码的深链接,指向 remotion.dev/convert 转换服务;
- See also:关联其他音效页形成内链网络。
步骤 4:注册侧边栏与目录
两处必须同步登记,否则新页面在文档站中"不可见":
- packages/docs/sidebars.ts(约 L712–L746)——在
@remotion/sfx分类下追加'sfx/<name>'。当前该分类共列出 32 个条目,从sfx/whip一直排到sfx/record-scratch; - packages/docs/docs/sfx/table-of-contents.tsx——新增一个
<SfxItem>(底层是<TOCItem>),其中<PlayButton size={32}>会在目录卡片上渲染一个 32px 的播放按钮,name属性填 camelCase 导出名(注意条目link用 kebab-case 文件名的 URL 形式,如ui-switch,而name显示为uiSwitch)。
步骤 5:重新生成 SFX 波形
bun packages/docs/generate-sfx-waveforms.ts
这一步常被忽略,但它是文档站试听体验的关键。查看 generate-sfx-waveforms.ts 的实现可知其完整管线:
- 用正则
/export const\s+([a-zA-Z0-9_]+)\s*=\s*'https:\/\/remotion\.media\/([^']+\.wav)'/g从packages/sfx/src/index.ts的源码文本中解析出所有导出(若一个都解析不到会直接抛错,这也解释了为什么步骤 2 必须先行); - 对每个音效调用
ffmpeg -i <file> -ac 1 -f f32le -,从本仓库packages/remotion-media目录读取同名 WAV(缺文件同样抛错,印证了步骤 1 必须先完成); - 将 PCM 均分为 2000 个桶,逐桶取绝对值峰值,再按全局最大峰值归一化到 0–255 的字节序列;
- 以 base64 编码写入 sfx-waveforms.ts,导出
SFX_WAVEFORM_SAMPLE_COUNT = 2000与sfxWaveforms映射表。
也就是说:新增一个音效而不重新采样,文档站的波形展示就会缺失该条目。
步骤 6:更新 Skills 规则文件
在 packages/skills/skills/remotion-markup/sfx.md 的音效 URL 列表中追加新 URL。该文件是 Remotion 面向 AI 编码助手的标记规则(skill rule),内容即 32 个 remotion.media 音效 URL 的清单,并附带用 <Audio src={...} /> 的示例写法。同步更新它可保证 AI 助手在生成含音效的 Remotion 代码时能识别新加入的素材。
步骤 7:构建
cd packages/sfx && bun run make
对照 packages/sfx/package.json 的 scripts 定义,make 实际展开为:
tsgo && bun --env-file=../.env.bundle bundle.ts
即先用 TypeScript 原生编译器(tsgo)产出 dist/index.d.ts 类型声明,再用 bundle.ts 打包 ESM 产物 dist/esm/index.mjs(exports 字段中 import/module 均指向该文件)。--env-file=../.env.bundle 表明 bundle 过程依赖 monorepo 根部的 .env.bundle 环境变量——这正呼应了前置条件中"worktree 里可能缺少 .env"的提示。
命名规范
技能文档给出了文件名到导出名的映射约定,直接对照源码中的真实例子:
| 文件名 | 导出名 | 说明 |
|---|---|---|
my-sound.wav |
mySound |
kebab-case 转 camelCase |
switch.wav |
uiSwitch |
规避 JS 保留字 |
page-turn.wav |
pageTurn |
常规映射 |
源码中可找到全部对应关系:export const pageTurn = '.../page-turn.wav'、export const uiSwitch = '.../switch.wav'、export const mouseClick = '.../mouse-click.wav' 等,规则无一例外。
版本号规则
- 当前版本以 packages/core/src/version.ts 中的
VERSION为准(当前为4.0.520,与 packages/sfx/package.json 的 version 字段一致); - 文档页
<AvailableFrom>的取值为当前补丁号 + 1,因为新音效必然随下一个补丁版本发布。例如whip.mdx标注<AvailableFrom v="4.0.429" />,说明它是在 4.0.429 首次发布的音效。
全链路依赖关系小结
把 7 个步骤串起来,可以看到一条严格的先后依赖链:
generate.ts登记 + 部署 →remotion.media上 URL 可访问(素材层);packages/sfx/src/index.ts导出 → 成为一切下游的解析入口(库层);- 文档页 mdx +
sidebars.ts+table-of-contents.tsx→ 用户可见(文档层); generate-sfx-waveforms.ts采样 → 文档站波形数据(展示层,且强依赖步骤 2 与packages/remotion-media中的本地 WAV);sfx.md规则清单 → AI 助手可用(工具链层);bun run make→ 类型与 ESM 产物就绪(发布层)。
任何一环遗漏(尤其是步骤 5 的波形重采样和步骤 6 的 skills 清单),都会造成"包已发布但文档站试听/波形缺失"或"AI 无法感知新音效"的不一致状态。理解这条链路后,添加新音效就不再是零散的 checklist 背诵,而是一次有明确数据流向的多包协作操作。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0622
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00