Skip to main content

fframes-video:用 Rust 编写视频并逐帧检查

用 Rust 与 SVG 场景编写视频,将声音安排到时间轴,通过 Skia 和 FFmpeg 渲染;同时用帧图、接触印样和音频测量检查画面与声音。

相关案例

仓库提供 hello-world、signal-lab、fframes-intro 等示例;README 也链接了作者制作的发布视频。本页不将作者给出的渲染耗时当作本站性能测试。

用途

源文件适用于解说、动效、宣传片、标题卡、社交短片,以及对代码视频的修改。CLI 把部分画面和声音检查转成 PNG、文字和数值,便于 Agent 核对。

必要前提

需要 Rust 及源文件列出的各平台 FFmpeg 编码库。GPU 预览在 macOS 使用 Metal,在 Linux 和 Windows 使用 Vulkan;可选的浏览器编辑器还需要 Node.js 与 wasm-pack。

操作说明

源文件提供了项目生成器:

cargo install --locked cargo-fframes
cargo fframes new my-video --format landscape --fps 30 --yes

进入生成项目后,用 release CLI 检查异常,查看 strip 和 frame 输出的图片,核对音频测量,再渲染视频。看帧图与接触印样是 Agent 的检查方式;预览窗口用于观看和试听。

限制

CPU 后端没有预览窗口。Skia 与 FFmpeg 的预编译包依赖目标平台和功能组合,不匹配时仍需源码编译。第一次快照测试只生成基线,须看图后再接受。仓库采用 MIT。

来源信息

仓库
dmtrkovalenko/fframes
最近来源活动
2026年9月29日 19:47
检测到的 SKILL.md 语言
英语
星标
1,380
分支
24

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

文件资源管理器
4 个文件

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
fframes-video
description
Create, animate, review and render videos in code with fframes (Rust, SVG scenes, ffmpeg encoding, Skia GPU rendering). Use whenever the user wants a video, animation, motion graphic, explainer, promo, social clip, title card, podcast visual, lyric/caption video or any rendered .mp4/.webm made programmatically, or asks to change, fix, speed up or check an fframes video. Covers installing fframes, creating a project, designing good-looking motion, sound (music, voice, SFX, loudness), watching it in a real-time preview window, and checking the result without watching it: frames and contact sheets as PNG, automatic problem detection, loudness numbers, fast draft and range renders.
# Making videos with fframes [fframes](https://github.com/dmtrKovalenko/fframes) is a Rust library that renders video from code. A video is a Rust struct with a `render_frame(frame) -> Svgr` method: for every frame it returns an SVG tree, built with the `svgr!` macro (SVG markup with `{rust expressions}`). Scenes split the timeline, `timeline!` and springs animate values, an audio map places sound, and ffmpeg encodes the result. Each project gets a command line (from `fframes::cli`). Use it for everything: render a video, open the real-time preview window, and look at frames, contact sheets and loudness numbers as files you can read. Read `references/design.md` before designing, `references/api.md` while writing code and `references/audio.md` for sound. ## 1. Install Rust from <https://rustup.rs>, then the system libraries ffmpeg is built with: ```bash # macOS brew install pkg-config ffmpeg x264 x265 opus nasm ninja # Debian / Ubuntu sudo apt-get install -y yasm nasm ffmpeg libx264-dev libx265-dev libopus-dev libclang-dev clang ninja-build libvpx-dev libasound2-dev # Arch sudo pacman -S ninja yasm nasm ffmpeg x264 x265 opus clang ``` Windows links a prebuilt FFmpeg 9 shared build instead (set `FFMPEG_DIR` to the unzipped `ffmpeg-n9.0-latest-win64-gpl-shared` build from BtbN/FFmpeg-Builds, add its `bin` to `PATH`, install LLVM with `winget install LLVM.LLVM` and set `LIBCLANG_PATH`). Details are in the fframes README. ## 2. Create a project ```bash curl -fsSL https://raw.githubusercontent.com/dmtrKovalenko/fframes/main/scripts/new-video.sh | bash -s -- my-video --yes # or install the generator once and use it directly: cargo install --locked cargo-fframes cargo fframes new my-video --format landscape --fps 30 --yes ``` Options: `--template single-scene|multi-scene`, `--format landscape|portrait|square|uhd`, `--fps`, `--title`, `--backend`, `--dir`. The project depends on the fframes release that matches the installed `cargo-fframes`; `--git` uses the repository's `main` branch instead. Always pass `--yes` so nothing waits for input. Templates: - `single-scene` (default): one scene. It adapts to every format; start here for single-shot clips, title cards and portrait video. - `multi-scene`: two scenes. Start here for anything with several scenes; it only accepts `landscape` and `uhd`. The template content is placeholder code that shows the API. Replace its layout, colors, fonts and decorations with a design made for the video the user asked for. **Use the Skia GPU backend (the default).** `cargo fframes new` picks Skia on Metal (macOS) or Vulkan (Linux, Windows). It renders about 10x faster than the CPU backend and gives you the real-time `preview` window. On macOS and Linux the first build downloads prebuilt Skia and ffmpeg and only compiles the Rust dependencies (about a minute; other targets, or `metal` and `vulkan` together, compile Skia from source for ~20 minutes). Start it right away and write the video while it runs: ```bash cd my-video && cargo build --release # run in the background, the first build is the slow one ``` `--backend cpu` needs no Skia but has no preview window; pick it only when there is no GPU. The project renders as generated: ``` my-video/ src/lib.rs # the video (from the template) src/main.rs # the command line media/ # fonts, images and audio compiled into the binary (one starter font) tests/frames.rs # frame snapshots and a check of every frame for problems README.md # the commands below ``` Every command below is `cargo run --release -- <command>`. Define a short shell function (a variable like `R="cargo run --release --"` does not split into words in zsh, the macOS shell): ```bash R() { cargo run --release -- "$@"; } ``` ## 3. The loop After every change: 1. `$R timeline` lists the scenes with their frame and second ranges, and every audio track. Check structure and pacing here before looking at pixels. 2. `$R inspect` checks a frame every 0.25 s plus the first and last frame of every scene. It reports missing images, fonts or glyphs, text cut off by the edge of the canvas, invalid SVG (zero-sized rectangles, bad radii), broken transforms and panics, each with its time and scene. It exits with code 2 on errors. Fix everything it reports. A warning that only appears on a scene's first frames is usually an entrance; check it with a strip. 3. `$R strip <scene or range> -n 12` writes a contact sheet (`strip.png`): evenly spaced frames in one labelled image. Open it with your image tool. It is the fastest way to judge layout, rhythm and motion. 4. `$R frame Intro@end,Outro@50%` writes full-size PNGs into `frames/` for detail checks (typography, alignment, contrast) and prints problems found in those frames. 5. `$R onion "Intro@0..Intro@1s" -n 6` blends frames into one image (`onion.png`) to show the path and spacing of a movement: easing, overshoot, stagger. 6. `$R preview Intro` opens a real-time window with sound for the user to watch (space play/pause, h/l seek a second, j/k step a frame, q quit). It blocks until closed, so start it in the background or ask the user to run it. Offer it whenever the user wants to see the video; you review with strips and frames, the user watches in the preview. 7. `$R render Intro --draft` encodes one scene at half resolution in about a second; `$R render` writes the final `out.mp4`. Rules: - Look at the PNGs before saying anything looks good. - Prefer `strip` to many `frame` calls; add `--scale 0.5` when composition is all you need. - Add `--json` when parsing output: the result goes to stdout, progress to stderr. - Keep `--release`: debug builds render many times slower. - `cargo test` compares settled frames with approved snapshots in `_frame_snapshots/` and fails if any frame has warnings. The first run only creates the snapshots: look at the PNGs before committing them, a created baseline is not a reviewed one. `FFRAMES_UPDATE_SNAPSHOTS=1 cargo test` accepts intended changes. Snapshot the middle of a scene (`Intro@3s`), not its end where it fades out. ### Addressing time `120` (frame), `3.2s`, `500ms`, `1:05.5`, `50%`, `start`, `end`; scenes by struct name, `Intro` (also matches `IntroScene`, case-insensitive), `#3` (scene index), `Intro[1]` (second scene of that type); inside a scene `Intro@1.2s`, `Intro@12`, `Intro@50%`, `Intro@end`. Ranges: `a..b` (end exclusive), `a..`, `..b`, `all`, or a scene name for the whole scene. Separate several times with commas. ### All commands | command | use it to | | --- | --- | | `timeline` | see scenes, durations, audio tracks and their mix settings | | `inspect [RANGE] [--every 0.1s \| --all-frames] [--fail-on warning]` | find problems without rendering pixels | | `strip [RANGE] -n N [--columns 4] [--width 480]` | review flow and motion in one image | | `frame TIMES [-o dir] [--svg]` | full-size PNGs (and the laid-out SVG) of chosen frames | | `onion RANGE -n N` | see a movement's trajectory and easing | | `svg TIME` | read a frame's final SVG as text (positions, text, colors) | | `preview [TIME] [--paused] [--mute]` | real-time window with sound, for the user | | `render [RANGE] [-o out.mp4] [--draft]` | encode the video or a part of it (audio cut to match) | | `snapshot TIMES [--update]` | compare frames with approved PNGs, `.diff.png` marks changes | | `audio analyze [RANGE] [--waveform w.png]` | loudness (LUFS), true peak, clipping, silence, per scene | | `audio at TIMES` | which sounds play at a moment, where in their file and how loud | | `audio render [RANGE] -o a.wav` | the mix as a WAV file | ### Browser editor fframes also has a web editor with a timeline and scrubbing, useful when a person wants to tweak a video interactively. It runs the video compiled to WebAssembly and needs Node.js plus `wasm-pack`; the setup is the `editor/` folder of the hello-world example in the fframes repository (<https://github.com/dmtrKovalenko/fframes/tree/main/examples/hello-world>). Mention it when the user asks for a GUI editor. For watching use `preview`, and for your own review use the CLI. ## 4. Writing the video ```rust impl Video for MyVideo<'_> { const FPS: usize = 30; const WIDTH: usize = 1920; const HEIGHT: usize = 1080; fn duration(&self) -> Duration<'_> { Duration::Auto } // sum of the scenes fn audio(&self) -> AudioMap<'_> { AudioMap::none() } // see references/audio.md fn define_scenes(&self) -> Scenes<'_> { Scenes::from(vec![&self.intro as &dyn Scene, &self.main]) } fn render_frame<'a>(&'a self, frame: Frame, ctx: &FFramesContext<'a, '_>) -> Svgr<'a> { fframes::svgr!(<svg xmlns="http://www.w3.org/2000/svg" width={Self::WIDTH} height={Self::HEIGHT}> <rect width={Self::WIDTH} height={Self::HEIGHT} fill={BACKGROUND} /> {ctx.render_scenes(&frame)} </svg>) } } ``` - One scene per idea, 2-6 s each. Inside a scene `frame.seconds()` counts from the scene start. - Animate with `frame.animate(&fframes::timeline!(at 0.2 => 0.8, animate 0.0_f32 => 1.0, Easing::EaseOut))` and springs, `Easing::Spring { mass: 1.0, stiffness: 180.0, damping: 20.0 }`. Leave the end time off a spring so it settles on its own. - Markup without `{}` is cached across frames. Keep decoration literal and put animated values on a wrapping `<g transform={..} opacity={..}>`. - `render_frame` runs for every frame on several threads: no panics, file reads or heavy work in it. Prepare data in the constructor or a `OnceLock`. Media lookups return `Option`; fall back to `Svgr::empty()` instead of `expect`. - Put every font file in `media/` and refer to it by family name, with a numeric `font-weight`. - Measure text rather than guess: `frame.text_width`, `frame.text_fit(.., TextOverflow::Ellipsis)`, `frame.text_break_lines` for paragraphs. `inspect` catches text leaving the canvas, not text leaving its own box, so check boxes in a `frame` PNG. - GPU shaders (SkSL or Shadertoy GLSL) run on the Skia backend through `fframes::Shader`; see `references/api.md`. ## 5. Making it look good The short version of `references/design.md`: - One idea per scene, large type, margins of 8-10% of the width, two or three colors plus neutrals and one accent for emphasis. - Elements enter with a spring or ease-out over 300-600 ms and leave faster, with ease-in over 200-300 ms. Related items stagger by 60-120 ms. Give the viewer 1-2 s to read after the motion settles, and never move everything at once. - Cross-fade scenes (`fn overlap(&self) -> Overlap { Overlap::Previous(0.4) }`) or carry an element across the cut. Keep a little motion during holds so they do not look frozen. - At 1920x1080: titles 96-140 px, body 44-60 px, at most about 8 words per line and 3 lines per card. Portrait 1080x1920 is watched on a phone: same pixel sizes or larger, content inside the middle 80% because platform UI covers the top and bottom. - Check every scene against the checklist in `references/design.md` with a strip. ## 6. Sound Put audio files in `media/` (compiled in, mono) or load a folder at runtime with `MediaDirectory` for stereo, then place them: ```rust AudioMap::from([ AudioTrack::new("music.mp3", Second(0.)..Eof).gain_db(-18.).fade_in(1.).fade_out(2.).duck_under_voice(), AudioTrack::new("vo.wav", Second(0.6)..Eof).voice(), AudioTrack::new("whoosh.wav", Second(3.1)..Eof).gain_db(-8.), ]) ``` Time sound effects from the same constants that drive the animation. Check levels with `$R audio analyze --waveform w.png` (about -14 LUFS integrated for web video, true peak below -1 dBTP, no unintended silence) and `$R audio at 3.1s` for what plays at an event. Then let the user listen in `$R preview`. ## 7. Finish 1. `$R inspect --fail-on warning` passes, or the remaining warnings are understood entrances. 2. A strip of every scene looks right and key frames are checked at full size. 3. `$R audio analyze` shows sensible levels. 4. `$R render -o out.mp4`, then confirm size, frame count and audio with `ffprobe -v error -show_entries stream=codec_type,width,height,nb_frames,duration out.mp4`. 5. Run `cargo test` if the project keeps snapshots, and commit `_frame_snapshots/*.png`. Tell the user the output path, the duration, the path of a strip image and the `preview` command to watch it. ## Troubleshooting - Build fails in `ffmpeg-sys-fframes`: a system library from step 1 is missing (`nasm`, `pkg-config`, the codec `-dev` packages). - Build fails in the Skia bindings (`skia-bindings`) with bindgen or libclang errors (Skia is only compiled from source when no prebuilt matches, e.g. `metal` and `vulkan` together): point `LIBCLANG_PATH` at a working libclang (on macOS Xcode's: `export LIBCLANG_PATH=$(xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/lib`). - Skia bindings fail to compile on macOS 27 / Xcode 27 with ``cannot find type `_Traits` `` in `std___hash_table___node_allocator`: only happens when Skia is compiled from source (the prebuilt binaries ship their bindings). `skia-bindings` 0.153.3 lacks the bindgen rule (rust-skia [#1335](https://github.com/rust-skia/rust-skia/pull/1335)); use one GPU backend so the prebuilt download matches, or patch a copy of `skia-bindings-0.153.3` (add `"std::__hash_table.*",` after `"std::__tree.*",` in `OPAQUE_TYPES` in `build_support/skia_bindgen.rs`) through `[patch.crates-io]`. - ``can't find crate for `fframes_media_dir_macro` `` on macOS 27 while the file exists: the proc macro was linked by an older Rust whose output the macOS 27 loader rejects (a "LINKEDIT string pool" error). Update Rust (`rustup update`; 1.98 works) and rebuild. - Text renders in the wrong font or not at all: `inspect` shows "No match for ... font-family"; add the font file to `media/` and use its exact family name.
在 GitHub 查看