classic 的技术栈:Next.js、Rust WASM 与浏览器里的媒体处理

2026-08-09

先把状态说清楚,否则下面每一句都会被误读。

本文描述的是 OpenCut 旧版(classic)的行为。该代码库已归档、不再维护,opencut.app 线上目前仍运行该版本;重写版正在开发中,功能与操作可能变化。

也就是说,github.com/OpenCut-app/opencut-classic 这个仓库是只读的(最后推送 2026-05-17,star 213,核对日 2026-08-09),而 github.com/OpenCut-app/OpenCut 主仓的 README 明写「OpenCut is being rewritten from the ground up」,正在从头重写(star 81917,同一核对日)。本文拆的技术栈属于前者。想照着这篇去理解重写版的代码,会全错。

我们没有编译过 WASM,没有跑起过 bun dev:web,没有打开过编辑器界面。下面全部来自仓库里的 README、package.jsonrust/ 目录结构和 docs/ 下的架构文档。

四个顶层目录,先认清边界

classic 的 README 把项目分成四块:

目录内容
apps/web/Next.js web 应用
apps/desktop/用 GPUI 构建的原生桌面应用,README 自己标注 in progress
rust/平台无关的核心:GPU 合成器、特效、蒙版、WASM 绑定
docs/架构与子系统文档

这张表里信息量最大的一行是 rust/。README 原文说他们正在把业务逻辑从 TypeScript 迁移到这里——注意是「正在迁移」,不是「已经迁完」。所以你在 classic 里会同时看到两套东西:还留在 TypeScript 里的逻辑,和已经挪进 Rust 的部分。读代码时如果发现同一件事在两边都有影子,那不是你看错了,是迁移中的常态。

apps/desktop/ 那个 in progress 标注也别忽略。它是 opt-in 的:README 明确说只做 web 就完全跳过这个目录,要动它才去看 apps/desktop/README.md,而且是两步走——先装 Rust 工具链,再装桌面原生依赖。

classic 的 rust/ 下面有什么

下面这一段说的全是 classic 仓库里的 rust/,跟重写版主仓的 Rust 部分不是一回事,别对着重写版的代码找。classic 的 rust/crates/ 实际有六个 crate:bridgecompositoreffectsgpumaskstime。另外还有 rust/wasm/,它发布成一个 npm 包,包名 opencut-wasm

这个划分本身就是设计意图的说明书。compositor 管合成、effects 管特效、masks 管蒙版、time 管时间、gpu 管图形设备,bridge 顾名思义是往外接的那层,而 rust/wasm/ 是把这堆东西打包成浏览器能 import 的形态。

有一个细节值得单独拎出来:gpu crate 的 Cargo.toml 里有一段

[target.'cfg(target_arch = "wasm32")'.dependencies]

这意味着它针对 wasm32 目标有一套单独的依赖。翻译成人话:同一套 GPU 代码要同时服务浏览器和原生桌面两个宿主,浏览器那份需要额外的依赖来落地。你如果打算改 gpu 里的东西,就得意识到自己改的代码有两个编译目标,不能只在一个目标下想当然。

classic web 侧的 60 个依赖,看哪几行

classic 的 apps/web/package.json 里有 60 个 dependencies。挨个念一遍没有意义,但按用途分组之后,有几组能直接告诉你这个应用把重活放在了哪里。

用途依赖(只列能从包名确认用途的)
框架nextreactreact-dom
状态zustanduse-deep-compare-effect
媒体mediabunnywavesurfer.jssoundtouchjs
Rust 核心opencut-wasm
浏览器端 AI@huggingface/transformers
数据drizzle-ormpgpostgres@upstash/redis@upstash/ratelimit
认证与防护better-authbotid
部署@opennextjs/cloudflarewrangler(dev)

UI 那一组是一长串 radix-ui@radix-ui/react-*,加上 cmdksonnervaullucide-reactreact-icons@hugeicons/*embla-carousel-reactreact-resizable-panelsreact-window@hello-pangea/dndmotion;剩下还有 zodreact-hook-formculorinanoideventemitter3date-fns 这些通用件,以及 @content-collections/*(dev)、react-markdownunifiedrehype-*feed 一组内容相关的东西。

三处最值得停下来看:

**一是 mediabunnyopencut-wasm 同时在场。**媒体处理库和 Rust 编译出来的 WASM 核心一起进了浏览器包,这个组合说明媒体与渲染的重活是在本地完成的。classic README 给的三条「Why」里,第一条就是 Privacy——视频留在你自己设备上。依赖构成和这条主张是一致的。需要说明的是,那三条 Why(Privacy、Free features、Simple)是项目方在自己 README 里的主张,不是我们的评价;其中提到 CapCut 收费策略的部分,我们只做转述,不做任何评判。

**二是 @huggingface/transformers。**它出现在 dependencies 里,说明这个应用有在浏览器里跑模型的路径。但依赖清单只能告诉你「用途方向」,不能告诉你具体拿它做了什么功能、效果如何——那需要读实现,我们没读,所以到此为止。

**三是 soundtouchjs。**这个库对应的是变速不变调一类的音频处理。同理,包名给方向,不给结论。

我特意不写「OpenCut 用 wavesurfer.js 画了波形图」这种话,虽然它听上去很顺。依赖里有这个库是事实,它被用在哪个组件、渲染成什么样,是我们没有依据的部分。读别人仓库时这条线要一直守住,否则你写出来的架构理解里会混进一半自己脑补的内容。

★ 最关键的一条:TypeScript 和 Rust 到底谁管什么

classic 仓库里的 docs/effects-renderer.md 有一句划分职责的原文,它是理解整个技术栈的钥匙:

所有特效共用同一个 GPU 渲染器。TypeScript 决定跑哪些 shader 标识符、传哪些 uniform;Rust/wgpu 拥有设备创建、纹理和 pass 执行。

把这句话拆开:

  • TypeScript 侧是「声明」。加一个新特效的三步是:在 apps/web/src/lib/effects/definitions/ 建文件(文档举例 brightness.ts)→ 导出一个 EffectDefinition(参照同目录的 blur.ts)→ 在 index.ts 注册。EffectDefinition 的字段是 type(唯一字符串标识)、name(显示名)、keywords(供搜索)、params(面向用户的控件,滑块开关之类)、renderer(GPU pass 模板,解析成 shader 标识符加 uniforms)。全是描述,没有一行在碰显卡。
  • Rust/wgpu 侧是「执行」。设备怎么创建、纹理怎么管、pass 怎么跑,都不归 TypeScript。

这条边界直接决定了一件很实际的事:**你的改动到底要不要装 Rust 工具链。**如果你想加的是一个能用现有 shader 表达的调色类特效、或者只是调整参数控件,那全部工作在 apps/web/ 里,不需要碰 rust/。反过来,如果你要动的是渲染管线本身、纹理管理、或者新增一种 GPU 能力,那 TypeScript 侧再怎么写都没用。

pass 与 buildPasses:机制里最容易踩的一处

渲染器支持 passes 数组。单 pass 特效(比如调色)只有一个条目:

renderer: {
  passes: [
    { shader: "my-effect-shader", uniforms: ({ effectParams }) => ({ /* ... */ }) },
  ],
}

当特效必须处理自己的输出时就需要多 pass。文档举的例子是模糊(先横后纵)、辉光 bloom(提取 → 模糊 → 合成)、glow。

再往上一层,有些特效的 pass 数量本身随参数变化——文档举的例子是模糊在高强度时需要更多迭代来保证质量。这种情况加一个 buildPasses 函数:

renderer: {
  passes: [ /* 静态兜底 —— buildPasses 缺席时使用 */ ],
  buildPasses: ({ effectParams, width, height }) => {
    // 返回带预计算 uniform 的 EffectPass[]
  },
}

★ 这里有一条原文规则必须记住:buildPasses 存在时,所有渲染路径都用它,不再用静态 passes 数组;静态数组保留下来只作为结构参考与兜底。也就是说,你同时写了两份而只改了静态那份,改动会完全不生效,而且不会报错。文档另有一节强调 pass 解析必须走 resolveEffectPasses

docs/effects-renderer.md 还有 Writing shadersCoordinate systems 两节,我们只看到了标题、没有读正文,所以这两块这里不展开。

把它跑起来需要装什么

classic README 给的前置是 Bun、Docker 与 Docker Compose,并注明 Docker 是可选但推荐的(用来跑本地数据库和 Redis);只做前端可以跳过。

# 1. fork 并 clone 仓库
# 2. 复制环境变量文件
cp apps/web/.env.example apps/web/.env.local          # Unix/Linux/Mac
Copy-Item apps/web/.env.example apps/web/.env.local   # Windows PowerShell

# 3. 起数据库与 Redis
docker compose up -d db redis serverless-redis-http

# 4. 装依赖并起开发服务
bun install
bun dev:web

应用在 http://localhost:3000。README 说 .env.example 的默认值与 Docker Compose 配置对应,开箱即用。Windows 用户注意第 2 步是 PowerShell 的 Copy-Item,不是 cp

只有当你要改 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. 改动时重新构建

以上命令原样来自 README,未经我们实际执行,以仓库最新内容为准。这里的 bun link 两步是关键:先在 rust/wasm/pkg 注册,再在 apps/web 链过去,少一步的话 web 拿到的仍是 npm 上的 opencut-wasm 而不是你本地构建的那份,表现就是「改了 Rust 却毫无变化」。

一句话的判断路径

  • 只想改界面、控件、参数面板 → 待在 apps/web/,Bun 就够了。
  • 想加一个能用现有 shader 表达的特效 → 也在 apps/web/src/lib/effects/definitions/,写 EffectDefinition,注意 buildPasses 的覆盖规则。
  • 要动渲染管线、纹理、设备 → 得进 rust/,并且记住 gpu crate 有 wasm32 这个第二编译目标。
  • 要动桌面端 → 那是 opt-in 的 GPUI 应用,README 自己标了 in progress。

最后再提醒一次仓库状态:classic 已归档,不再接受提交与 issue;代码是 MIT 许可,仍可 fork 自行维护。至于重写版会长成什么样,README 里列的那些是官方声明的计划,不是现在能用的东西——那是另一篇的事。

延伸阅读


本文依据 OpenCut 官方仓库(github.com/OpenCut-app/OpenCut 与已归档的 github.com/OpenCut-app/opencut-classic)的 README、docs/ 架构文档、package.jsonCargo.tomlchangelog/ 整理,核对日 2026-08-09。本文内容为仓库源码与文档口径,我们没有编译或运行过任何版本的 OpenCut。

本文描述的是 OpenCut 旧版(classic)的行为。该代码库已归档、不再维护,opencut.app 线上目前仍运行该版本;重写版正在开发中,功能与操作可能变化。

许可条款请以官方 LICENSE 原文为准,本文不构成法律意见。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。