Install
$ agentstack add skill-chrisgliddon-bevy-skills-bevy-capture ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
bevy-capture — recording Bevy 0.18 cameras to MP4 or PNG sequences
Compatibility status
bevy_capture 0.4.1 (the latest crates.io release as of this writing) pins bevy = "^0.17.2" upstream. For Bevy 0.18 you need three small patches against the published source — see [references/bevy-0-18-patch.md](references/bevy-0-18-patch.md). The API surface documented here is what the patched crate exposes, which is identical to upstream apart from target_headless returning a tuple.
When upstream publishes a Bevy 0.18 release, drop the patch and pin a new version. The snippet below is verified to compile against bevy_capture = "0.4.1" + the patch on bevy = "0.18".
When to use this skill
- Recording a gameplay clip or cinematic from a
Camera3dorCamera2d. - Generating MP4 files for trailers or automated screenshot tests.
- Capturing a PNG-per-frame sequence for offline compositing or GIF export.
- Choosing between in-process H.264 (
Mp4Openh264Encoder) and ffmpeg-CLI encoders (Mp4FfmpegCliEncoder,Mp4FfmpegCliPipeEncoder). - Hitting "ffmpeg not found" or "cannot find type
Mp4Openh264Encoder" because a Cargo feature wasn't enabled. - Wanting to stop and flush a recording mid-session via
Capture::stop().
Canonical pattern
# Cargo.toml
[dependencies]
bevy = "0.18"
# Pick the features for the encoders you use; FramesEncoder needs none.
bevy_capture = { version = "0.4.1", features = ["mp4_openh264"] }
# Required until bevy_capture ships a native 0.18 release — see references/bevy-0-18-patch.md
[patch.crates-io]
bevy_capture = { path = "vendor/bevy_capture" } # local fork with the 3 patches applied
use bevy::prelude::*;
use bevy_capture::{
CameraTargetHeadless, Capture, CaptureBundle, CapturePlugin,
encoder::{
frames::FramesEncoder,
mp4_ffmpeg_cli::Mp4FfmpegCliEncoder,
mp4_ffmpeg_cli_pipe::Mp4FfmpegCliPipeEncoder,
mp4_openh264::Mp4Openh264Encoder,
},
};
use std::fs;
fn main() {
App::new()
.add_plugins((DefaultPlugins, CapturePlugin))
.add_systems(Startup, setup)
.add_systems(Update, drive_capture)
.run();
}
// ① Spawn a Camera with `CaptureBundle`. For an off-screen recording
// use `target_headless` which (in Bevy 0.18) returns the *pair* of
// components `(Camera, RenderTarget)` — `RenderTarget` is now a
// separate required component, no longer a field on `Camera`.
fn setup(mut commands: Commands, mut images: ResMut>) {
let (camera, render_target) =
Camera::default().target_headless(1920, 1080, &mut images);
commands.spawn((
Camera2d, // or Camera3d
camera,
render_target,
CaptureBundle::default(),
));
}
// ② Call `Capture::start(encoder)` once you're ready to record.
// The render loop encodes every subsequent frame until `stop()`.
fn drive_capture(
mut q: Query,
mut started: Local,
mut stopped: Local,
mut frame: Local,
) {
let Ok(mut capture) = q.single_mut() else { return };
if !*started {
*started = true;
fs::create_dir_all("captures").ok();
// Pick ONE encoder. All four implement `Encoder`.
// A) Mp4Openh264Encoder — in-process H.264, NO shell-out, no system ffmpeg.
capture.start(
Mp4Openh264Encoder::new(
fs::File::create("captures/out.mp4").unwrap(), 1920, 1080,
).expect("openh264 init"),
);
// B) Mp4FfmpegCliEncoder — collects frames, shells out to `ffmpeg` once at stop.
// capture.start(
// Mp4FfmpegCliEncoder::new("captures/out.mp4")
// .expect("tempdir").with_framerate(30).with_crf(23),
// );
// C) Mp4FfmpegCliPipeEncoder — pipes raw frames to a long-running ffmpeg child.
// capture.start(
// Mp4FfmpegCliPipeEncoder::new("captures/out.mp4")
// .expect("spawn ffmpeg").with_framerate(30).with_crf(18),
// );
// D) FramesEncoder — one PNG per frame into a directory; no encoding.
// capture.start(FramesEncoder::new("captures/frames"));
}
*frame += 1;
// ③ Stop flushes the encoder (calls `Encoder::finish` on drop).
// Guard with `stopped` so `capture.stop()` is called only once —
// repeated calls may double-flush a pipe or panic.
if *frame >= 300 && !*stopped {
*stopped = true;
capture.stop();
}
}
Verified against bevy = "0.18" + patched bevy_capture = "0.4.1" — cargo check --example bevy_capture clean. The snippet lives at bevy-skills-tester/skill-snippets/examples/bevy_capture.rs.
Encoder choice
See [references/encoders.md](references/encoders.md) for the deep dive. Quick table:
| Encoder | Mechanism | System dep | Cargo feature | When to use | |---|---|---|---|---| | Mp4Openh264Encoder | In-process OpenH264 (downloaded at build) | None | mp4_openh264 | CI without ffmpeg; single-binary distribution | | Mp4FfmpegCliEncoder | Collects frames, shells out once at stop | ffmpeg on $PATH | mp4_ffmpeg_cli | Short clips, full ffmpeg codec control | | Mp4FfmpegCliPipeEncoder | Long-running ffmpeg child, pipes per-frame | ffmpeg on $PATH | mp4_ffmpeg_cli_pipe | Long recordings, low memory | | FramesEncoder | One PNG per frame into a directory | None | (always available) | Compositing, GIF pipelines, WASM |
Gotchas
Camera.targetis gone in 0.18.RenderTargetis a separate required component. Usetarget_headless(w, h, &mut images)(returns the(Camera, RenderTarget)pair) or spawnRenderTarget::Image(handle)alongsideCamera3d. Seebevy-cameras.- Encoders are feature-gated.
mp4_openh264,mp4_ffmpeg_cli, andmp4_ffmpeg_cli_pipeare three separate features — enable each one you import.FramesEncoderis always available. Missing the feature gives a "cannot find type" compile error, not a friendly diagnostic. - OpenH264 license.
Mp4Openh264Encoderlinks Cisco's OpenH264 binary, distributed under Cisco's OBQI (royalty-covered for H.264 baseline). If your runtime-dependency policy forbids non-MIT/Apache binaries, pick one of the ffmpeg-CLI encoders instead. - ffmpeg-CLI encoders need
ffmpegon$PATH.Mp4FfmpegCliEncoder::newandMp4FfmpegCliPipeEncoder::newreturnResult— handle the spawn error, don't.unwrap().Mp4Openh264EncoderandFramesEncoderhave no system dependency. - Camera must be active and rendering.
CaptureBundlehooks into the render loop on that camera entity. Inactive or unsourced cameras emit no frames; capture appears to silently do nothing. - WASM: only
FramesEncoderworks. The MP4 encoders shell out (ffmpeg) or rely on the OpenH264 native binary, neither of which exist inwasm32-unknown-unknown. Seebevy-wasm-webgpufor the WASM build path. - Upstream PollType / MessageWriter renames. If you fork
bevy_capturefor 0.18 instead of using the patch, fixPollType::wait→PollType::wait_indefinitelyandEventWriter→MessageWriterin any example code you carry over.
See also
bevy-cameras— camera spawning and the 0.18RenderTarget-as-component model thatbevy_captureattaches to.bevy-cargo-features— picking Bevy feature flags alongsidebevy_capture's encoder gates.bevy-wasm-webgpu— WASM caveat: onlyFramesEncodersurviveswasm32builds.- [
references/encoders.md](references/encoders.md) — encoder-by-encoder deep dive. - [
references/bevy-0-18-patch.md](references/bevy-0-18-patch.md) — the three-line patch needed to buildbevy_capture0.4.1 against Bevy 0.18.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: chrisgliddon
- Source: chrisgliddon/bevy-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.