六个 crate 的分工:compositor、effects、gpu、masks、time、bridge
先说清楚版本:本文描述的是 OpenCut 旧版(classic)的行为。该代码库已归档、不再维护,
opencut.app线上目前仍运行该版本;重写版正在开发中,功能与操作可能变化。核对日 2026-08-09。文中所有关于六个 crate 的说法,都只对OpenCut-app/opencut-classic成立,不能拿去描述重写版仓库OpenCut-app/OpenCut。
看一个项目的 Rust 目录,最没用的读法是把每个 crate 的名字翻译一遍然后当作结论。名字只是作者的意图签名,真正能拿来做决定的,是「这个划分让哪一类改动只需要动一个地方」。这篇就按这个思路,把 OpenCut classic 的 rust/ 拆开讲。
目录里到底有什么
classic 仓库的 rust/crates/ 下是六个 crate:bridge、compositor、effects、gpu、masks、time。同级还有一个 rust/wasm/,它被发布为 npm 包 opencut-wasm,apps/web 的依赖清单里就有这一项。
classic 的 README 对 rust/ 只给了一句定位:平台无关的核心,包含 GPU 合成器、特效、蒙版、WASM 绑定,并且他们正在把业务逻辑从 TypeScript 迁移过来。
把这句话和目录对一下,你会得到一张不完全对齐的表:
| crate | README 那句话里的对应项 | 我们的依据强度 |
|---|---|---|
compositor | GPU 合成器 | 名字与 README 措辞直接对应 |
effects | 特效 | 名字与 README 措辞直接对应 |
masks | 蒙版 | 名字与 README 措辞直接对应 |
bridge | WASM 绑定(与 rust/wasm/ 一并) | 措辞对应,但边界未见明确文档 |
gpu | 未单列 | 有额外的构建配置线索,见下文 |
time | 未单列 | 只有名字,无文档对应 |
我要强调最后一行:README 那句只点了四件事,time 在里面没有对应词。我们读的是目录,没有读这几个 crate 的源码,所以这篇不会告诉你 time 里有什么。如果你在别处看到有人绘声绘色地讲 OpenCut 的时间轴 crate 如何实现,先问一句依据是什么。这也是判断依据本身的一部分:一个 crate 名字能不能支撑结论,取决于有没有第二个来源印证它。
读懂这套划分的钥匙:TS 和 Rust 各拥有什么
classic 的 docs/effects-renderer.md 里有一句分工边界写得非常干净,它是理解整个 rust/ 目录的钥匙:
所有特效共用同一个 GPU 渲染器。TypeScript 决定跑哪些 shader 标识符、传哪些 uniform;Rust/wgpu 拥有设备创建、纹理和 pass 执行。
这句话把「配置」和「执行」切开了。配置留在 TypeScript:一个特效长什么样、暴露哪些参数、需要几趟渲染,全部由 TS 侧的 EffectDefinition 描述——type、name、keywords、params、renderer 这几个字段,都是写在 apps/web/src/lib/effects/definitions/ 下的普通 TS 文件里的。执行留在 Rust:设备、纹理、pass 的实际跑动。
这条边界能立刻回答一个高频问题:加一个新特效要不要写 Rust? 按文档给的三步,不用——在 apps/web/src/lib/effects/definitions/ 建文件、导出一个 EffectDefinition、在同目录 index.ts 注册,三步都在 TS 侧。多趟渲染也是在 TS 侧描述的:renderer.passes 是数组,单 pass 特效(比如调色)只有一个条目;当特效必须处理自己的输出时就需要多 pass,文档举的例子是模糊(先横后纵)、辉光 bloom(提取 → 模糊 → 合成)这类。pass 数量随参数变化时,加一个 buildPasses 函数返回带预计算 uniform 的 pass 列表。
这里有个容易踩的规则,原文写得很明确:buildPasses 存在时,所有渲染路径都用它,静态 passes 数组不再参与,只作为结构参考与兜底。也就是说你把 buildPasses 写歪了,静态数组不会来救你。文档另有一节强调 pass 解析必须走 resolveEffectPasses。
所以,effects 这个 crate 的存在,不代表特效定义在 Rust 里。名字和职责在这里恰好是错位的,这正是「不要照着名字下结论」的现实例子。
gpu 为什么单独成一个 crate
六个 crate 里,gpu 是我们能拿到额外硬线索的那个。它的 Cargo.toml 里有一段 [target.'cfg(target_arch = "wasm32")'.dependencies]——针对 wasm32 目标声明了单独的依赖。
这一段说明的事情很具体:同一套 GPU 代码需要同时服务两个目标。一个是浏览器,apps/web 是 Next.js 应用,Rust 核心通过 rust/wasm/ 编译出的 opencut-wasm 进去;另一个是原生,classic 的 apps/desktop/ 是用 GPUI 构建的桌面应用,README 给它标了 in progress。
把跨目标差异收进一个 crate,好处是它只在一个 Cargo.toml 里体现。对你的实际意义是一条定位规则:遇到「浏览器上行为和桌面上不一致」这类怀疑,先去看 gpu 的 target 段,而不是先翻 compositor。 反过来,如果你的改动跟目标平台无关,那大概率不该动 gpu 的这段配置。
要提醒的是,这条只是构建配置层面的事实。我们没有编译过任何版本的 OpenCut,gpu 里具体分了哪些后端、wasm 与原生路径怎么收敛,本文一个字都不写。
bridge、rust/wasm/ 与那条你必须知道的本地链路
bridge 和 rust/wasm/ 一起对应 README 里的「WASM 绑定」。它们各自的边界在哪,我们没有见到明确文档,所以不下判断;但有一件与它们直接相关、且完全有依据的事:默认情况下,apps/web 用的是 npm 上的 opencut-wasm,不是你本地编译的那份。
classic 的 README 把这条单列成「本地 WASM 开发」,并注明只有在改 rust/wasm 且希望 web 用你本地构建时才需要。一次性前置:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Rust 工具链
cargo install wasm-pack # 构建 WASM 包
cargo install cargo-watch # 供 bun dev:wasm 监听文件变化
然后是四步:
bun run build:wasm # 1. 从仓库根构建一次
cd rust/wasm/pkg && bun link # 2. 注册生成的包
cd apps/web && bun link opencut-wasm # 3. 把 apps/web 链到本地包
bun dev:wasm # 4. 改动时重新构建
第 2、3 步是这条链路的关键:没有这两步,你改了 Rust、重新构建了,web 侧照样跑的是发布版本的包。 这类「改了没生效」的问题排查起来最费时间,因为它不报错。判断依据也很朴素——先确认 bun link 做过,再去怀疑代码。
顺带说,只做 web 完全不需要碰这条链路:classic 的常规起法是复制 apps/web/.env.example 到 apps/web/.env.local,用 docker compose up -d db redis serverless-redis-http 起数据库和 Redis(README 说 Docker 可选但推荐,只做前端可以跳过),然后 bun install、bun dev:web,应用在 http://localhost:3000。桌面端同样是 opt-in 的,要做 apps/desktop 才需要先装 Rust 工具链再装桌面原生依赖。
这套划分什么时候不再是有效依据
三件事必须放在一起看,否则容易把结论用过期:
第一,边界本来就在移动。 README 自己说的是「正在把业务逻辑从 TypeScript 迁移过来」,是进行时。今天 TS 拥有的东西,作者的计划里有一部分是要往 Rust 挪的。
第二,仓库已经归档了。 OpenCut-app/opencut-classic 的状态是已归档、不再维护,最后一次推送在 2026-05-17。归档意味着不再接受提交与 issue。对你的实际影响是:上面那句「迁移进行中」不会再往前走了,六个 crate 的划分就冻结在最后这次推送。代码是 MIT 许可,仍然可以 fork 自行维护,具体条款以官方 LICENSE 原文为准。
第三,别把这张图套到重写版上。 重写版主仓 OpenCut-app/OpenCut 的 Cargo.toml 里,workspace 只有 apps/desktop 一个成员,crates/* 是被注释掉的;apps/desktop/src/ 下是 Rust + gpui,只有 main.rs、shell.rs、theme.rs、7 个 components/ 与 4 个 panels/。也就是说,本文讲的六个 crate 是 classic 的现状,重写版仓库当前并没有这套 crate 划分。重写版 README 明写「OpenCut 正在被从头重写」,它列出的 Editor API、第三方插件、桌面与移动与浏览器共用一套代码库、MCP server、无头模式、编辑器内脚本标签页,都是官方 README 声明的计划、尚未发布,我们没有见到可用实现——不要把其中任何一条当成 OpenCut 今天已有的能力。重写版还没准备好接受外部贡献。这里不对进度、也不对重写这件事本身作任何评价。
一句话的落点
如果你只带走一条:在 classic 里,判断改动落点先看「配置还是执行」——配置在 TypeScript,设备、纹理、pass 执行在 Rust/wgpu,这条来自官方文档,比六个 crate 的名字可靠得多。crate 名字用来缩小范围,官方文档那句边界用来做决定。
另外,classic README 给的三条项目理由(Privacy、Free features、Simple)属于项目方自述立场,不是我们的评价,也不构成对任何商业产品的判断。README 里还感谢了 Vercel 与 fal.ai 对开源软件的支持,如实记一笔。
延伸阅读
- OpenCut Actions 系统:快捷键、按钮、右键菜单的统一触发层
- classic 的技术栈:Next.js、Rust WASM 与浏览器里的媒体处理
- 为什么模糊要分两趟:单 pass、多 pass 与 buildPasses
本文依据 OpenCut 官方仓库(github.com/OpenCut-app/OpenCut 与已归档的 github.com/OpenCut-app/opencut-classic)的 README、docs/ 架构文档、package.json、Cargo.toml 与 rust/crates/ 目录整理,核对日 2026-08-09。本文内容为仓库源码与文档口径,我们没有编译或运行过任何版本的 OpenCut。
本文描述的是 OpenCut 旧版(classic)的行为。该代码库已归档、不再维护,
opencut.app线上目前仍运行该版本;重写版正在开发中,功能与操作可能变化。
许可条款请以官方 LICENSE 原文为准,本文不构成法律意见。