fframes

真正够快的视频氛围编程框架

用 Rust 写每一帧,用 SVG 描述画面,Skia 与 ffmpeg 在 GPU 上完成渲染。

fframes 发布视频的一帧,128 秒的发布视频全部由 fframes 渲染
128 秒的发布视频,用氛围式编程写就花了 48 分钟,渲染花了 36 秒。它是一个 fframes 项目:examples/fframes-intro

让 agent 帮你做视频

fframes 为编程 agent 附带了一个 skill。添加一次,然后像向动态设计师下 brief 一样,描述你想要的视频。

这个 skill 会带着你的 agent 从一个空文件夹走到渲染好的 .mp4:安装 fframes、创建项目、设计动效、置入声音,并在交给你之前检查结果。

fframes 的 API 显式且详尽。编写显式、详尽的代码,正是一个配备了合适 skill 的 agent 擅长的事。skill 的指南是纯 Markdown,作为人类也很值得一读。

npx skills add https://fframes.studio
输入
一段 brief。目标时长、画幅、节奏、要出现的文字。
输出
项目目录、渲染好的视频,以及一份它自己跑过的检查结果。

为无法观看的作者而打造

编程 agent 无法观看视频,也听不到音轨。每个项目都自带一条命令行,把视频变成 agent 能读的东西:PNG、文本和数字。

你的 agent

  • 不渲染像素也能逐帧检查

    inspect 会报告缺失的字体或图片、被画布裁切的文字、无效的 SVG 以及 panic,每一条都带着它的时间和场景。

  • 查看动效

    strip 把等间距的帧平铺成一张联络表,onion 混合多帧来展示运动的路径与缓动,frame 写出全尺寸 PNG。

  • 测量声音

    audio analyze 按场景报告响度(LUFS)、真实峰值、削波和静音。

你

  • 带声音实时观看

    preview 打开一个 GPU 窗口,可以播放、暂停、拖动定位并逐帧步进。

  • 在浏览器里拖着预览

    编辑器把视频编译为 WebAssembly 运行,并带一条时间轴。

  • 交付

    render 写出最终文件;render --draft 以一半分辨率编码单个场景,约一秒完成。

每条命令都会给出可读的结果

它们把视频变成 agent 能读的东西:PNG、文本和数字,而不是一个需要人眼盯着的播放器。

  • inspect

    每 0.25 秒一帧中存在的问题,以及每个场景的首帧和末帧;出错时退出码为 2

  • timeline

    各场景及其帧范围、秒范围,以及每条音轨及其混音设置

  • audio analyze --waveform w.png

    响度报告,以及一张带有场景线和提示刻度的波形图

  • strip <scene> -n 12

    strip.png,一张带标注的联络表

  • frame <scene>@end,<scene>@50%

    frames/ 中的全尺寸 PNG

  • onion "<scene>@0..<scene>@1s" -n 6

    onion.png,混合后的运动

  • snapshot

    与已批准 PNG 的对比,.diff.png 标出变化之处

  • preview

    实时窗口,给人类看

  • render [--draft]

    视频,或者它的一部分

每条命令都在项目内以 cargo run --release -- <command> 运行。

<scene> 是你的视频中某个场景结构体的名字。时间可以写作 120(帧)、3.2s、50%、<scene>@1.2s,以及 <scene>@0..<scene>@1s 这样的范围。加上 --json 可得到可解析的输出。

为什么这么快

GPU 负责绘制,静态标记在编译期被哈希,编码器直接链接,SVG 不够用时还有着色器可以插进来。

  • GPU 负责绘制

    Skia 后端在 Metal(macOS)或 Vulkan(Linux、Windows)上渲染,比内置的 CPU 后端快约 10 倍。

  • 静态标记被缓存

    一帧中没有 {expressions} 的部分会在编译期被哈希,并由渲染器复用。

  • ffmpeg 负责编码

    fframes 直接链接 ffmpeg 的 libav 库,而不是调用一个独立工具。

  • SVG 不够用时用着色器

    把 SkSL 或粘贴进来的 Shadertoy GLSL 作为任意一帧的一个图层来运行。

Skia GPU 后端相对内置 CPU 后端的渲染加速
10×
渲染 128 秒发布视频的耗时
36s
用氛围式编程写完同一条视频的耗时
48min
render --draft 编码单个场景的耗时
~1s

你的 agent 写什么

每一帧都是一个由 Rust 函数返回的 SVG 树,用 svgr! 宏写。下面这段来自 examples/hello-world 的源码,有删节:一个方块沿时间轴运动,一行文字打印当前帧。

impl Video for HelloWorldVideo<'_> {
    const FPS: usize = 30;
    const WIDTH: usize = 1920;
    const HEIGHT: usize = 1080;

    fn render_frame(&self, frame: Frame, ctx: &FFramesContext) -> fframes::Svgr<'_> {
        fframes::svgr!(
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1920 1080"
                 width={ctx.current_video_size.width}
                 height={ctx.current_video_size.height}>
                <rect x="400" y="400" width="200" height="200" fill="blue"
                    transform={frame.animate(fframes::timeline!(
                        at 0.,  animate Transform::translate(0, 0) => Transform::translate(200, 480),  Easing::Linear,
                        at 2. => 6.0,  animate Transform::translate(200, 480) => Transform::translate(750, -400), Easing::Linear,
                        at 6.0 => 10.0, animate Transform::translate(750, -400) => Transform::translate(1310, 480), Easing::Linear,
                    ))}
                />
                <text x="100" y="440" font-family="JetBrains Mono" font-size="74" fill="#3b5563">
                    {format!("This frame index: {}, second: {:.2}", frame.index, frame.seconds())}
                </text>
            </svg>
        )
    }
}

时间轴就写在 timeline! 里:at 给出秒数,animate 给出起止变换,Easing 给出曲线。不需要打开浏览器去拖关键帧。

完整的 API 参考在 docs.rs/fframes,skill 里的 API 速查表是它的压缩版。

示例项目

仓库 examples/ 下的 15 个目录,每一个都是可以直接 cargo run 的完整项目。在仓库根目录用 just run 或 just render 就能打开或导出其中一个。

不用 agent 也行

装上生成器,三条命令就能拿到一个可以预览和渲染的项目。

在 macOS 和 Linux(arm64、x86_64)上,首次构建会下载预编译的 Skia 和 ffmpeg 库,在性能较好的机器上不到一分钟。其他目标与特性组合则需从源码编译,最多约 20 分钟。后续构建只需数秒。

cargo install --locked cargo-fframes
cargo fframes new my-video
cd my-video && cargo run --release -- preview
--template
single-scene / multi-scene模板形态
--format
landscape / portrait / square / uhd画幅
--fps
整数帧率
--title
字符串写入项目标题
--backend
skia / cpucpu 会跳过 Skia,没有预览窗口,渲染更慢
--git
https://github.com/dmtrKovalenko/fframes跟踪 main 分支安装

环境要求

需要 Rust,开发编辑器时还需要 NodeJS。fframes 静态链接 ffmpeg 的 libav 库:macOS 与 Linux(arm64、x86_64)会下载预编译构建,其他目标或设置了 FFMPEG_FORCE_BUILD=1 时则从源码编译。无论哪种情况,它们所链接的系统编码器都必须已安装。

对会被缓存或拿到别的机器上运行的原生构建,示例启用了 build-portable。它在源码构建中省略 -march=native 与 -mtune=native,同时保留匹配的预编译下载,避免换一个 CPU 就撞上 SIGILL。

[target.'cfg(not(any(target_arch = "wasm32", windows)))'.dependencies]
fframes = { workspace = true, features = ["build-portable"] }

完整的环境说明见仓库根目录的 README 里。

macOS 会下载预编译的 Skia 与 ffmpeg 构建,你只需要装上 Homebrew 这一层的系统依赖。

brew install pkg-config ffmpeg x264 x265 opus nasm ninja

参与开发 fframes

开发 fframes 本身的 agent 应从AGENTS.md开始。对人类开发者,请先装上 just 命令运行器,再初始化仓库。

最后一行的 just watch-editor 需要在另一个终端里运行,它负责开发编辑器本身。

npm install --global pnpm
cargo install --locked just cargo-watch wasm-bindgen-cli wasm-pack
just init-repo
just watch-editor

fframes 以MIT 许可证 发布。