Skip to main content

fframes-video

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.

Informations de source

Dépôt
dmtrkovalenko/fframes
Dernière activité de la source
29 septembre 2026 à 19:47
Langue détectée de SKILL.md
anglais
Étoiles
1 380
Forks
24

fframes-video: write and inspect Rust video scenes

A Skill for creating videos as Rust code and SVG scenes with fframes, placing audio on a timeline, rendering with Skia and FFmpeg, and reviewing frames, contact sheets and audio measurements.

Examples

The repository includes hello-world, signal-lab and fframes-intro projects. Its README links a launch video as an author-produced example; this page does not treat the author’s render time as a SkillsMP performance measurement.

Uses

The source covers explainers, motion graphics, promos, title cards, social clips and programmatic video changes. Its CLI turns visual and audio checks into PNGs, text and numbers that an agent can inspect.

Prerequisites

Install Rust and the platform-specific FFmpeg encoder libraries listed in the source. GPU preview uses Metal on macOS or Vulkan on Linux and Windows. The optional browser editor also needs Node.js and wasm-pack.

How to use

The source offers the project generator:

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

Inside the generated project, run the release CLI’s inspect checks, review strip and frame PNGs, check audio measurements, and render the output. The source distinguishes these image checks from a human watching and listening in the preview window.

Limitations

The CPU backend has no preview window. Prebuilt Skia and FFmpeg are available only for matching targets and feature combinations; other builds compile from source. The first snapshot test run creates baselines, which require visual review. The repository is MIT licensed.

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Explorateur de fichiers
4 fichiers

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
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.
Voir sur GitHub