Skip to content

Instantly share code, notes, and snippets.

@VV0JC13CH
Last active November 24, 2023 07:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save VV0JC13CH/17e440bd5f4ef96b027a05063949a314 to your computer and use it in GitHub Desktop.
Save VV0JC13CH/17e440bd5f4ef96b027a05063949a314 to your computer and use it in GitHub Desktop.
Simple GIF recorder in Bevy 0.12 (POC/test)

This code should work in Bevy 0.12

  1. Add to cargo (I do not plan to maintain this fork in the future):
[dependencies]
engiffen = {git = "https://github.com/VV0JC13CH/engiffen", branch = "master"}
chrono = "0.4.31"
  1. Create relative path inside your project: "./screenshots/temp It has to be relative to place where you execute "cargo run"
  2. Add window::WindowDrawPlugin to your Bevy loop.
  3. Press F11 to start recording GIF, Press F11 to stop recording GIF.
  4. Press F12 to make a single screenshot

./screenshots/ <= place for screenshots (PNG) and recordings (GIF) ./screenshots/temp <= place for frames of recording in PNG

Line 26 Time interval between taking screenshots/frames (0.1s)
Line 98 FPS of GIF animation (10frames/s)

Known issues:

  • path for ./screenshots/temp has to exists prior recording
  • https://github.com/TooManyBees/engiffen requires updates of dependencies. I created PR, to update dependencies where it was possible with least effort ;-)
  • there is a 1 frame lag during GIF creation. This is fine, it's only a POC.
// Source: https://github.com/bevyengine/bevy/blob/main/examples/window/screenshot.rs
use bevy::prelude::*;
use bevy::render::view::screenshot::ScreenshotManager;
use bevy::time::common_conditions::on_timer;
use bevy::utils::Duration;
use bevy::window::PrimaryWindow;
// timestamps:
use chrono::{DateTime, Utc};
// GIF recordings:
use engiffen::{engiffen, load_images, Quantizer};
use std::fs;
use std::fs::File;
// PLUGIN BEGIN
pub struct WindowDrawPlugin;
impl Plugin for WindowDrawPlugin {
fn build(&self, app: &mut App) {
app.add_state::<GifRecordingState>()
.add_systems(
Update,
(
record_gif_on_f11,
record_gif_generate_temp_frames
.run_if(on_timer(Duration::from_secs_f64(0.1)))
.run_if(in_state(GifRecordingState::Recording)),
take_screenshot_on_f12,
),
)
.add_systems(
OnExit(GifRecordingState::Recording),
record_gif_convert_pngs_to_gif,
);
}
}
// PLUGIN END
// RESOURCES
// STATES
#[derive(States, Debug, Clone, Copy, Eq, PartialEq, Hash, Default)]
pub enum GifRecordingState {
#[default]
Available,
Processing,
Recording,
}
// COMPONENTS
// HELPERS
// SYSTEMS
fn record_gif_on_f11(
input: Res<Input<KeyCode>>,
state: Res<State<GifRecordingState>>,
mut next_state: ResMut<NextState<GifRecordingState>>,
) {
if input.just_pressed(KeyCode::F11) {
next_state.set(match state.get() {
GifRecordingState::Available => GifRecordingState::Recording,
GifRecordingState::Recording => GifRecordingState::Processing,
GifRecordingState::Processing => GifRecordingState::Processing,
});
}
}
fn record_gif_generate_temp_frames(
main_window: Query<Entity, With<PrimaryWindow>>,
mut screenshot_manager: ResMut<ScreenshotManager>,
mut counter: Local<u32>,
) {
let path = format!("./screenshots/temp/temp_{}.png", *counter);
*counter += 1;
screenshot_manager
.save_screenshot_to_disk(main_window.single(), path)
.unwrap();
}
fn record_gif_convert_pngs_to_gif(mut next_state: ResMut<NextState<GifRecordingState>>) {
convert_pngs_to_gif();
next_state.set(GifRecordingState::Available);
}
fn convert_pngs_to_gif() {
let mut frames_paths = Vec::new();
for file in fs::read_dir("./screenshots/temp/").unwrap() {
frames_paths.push(file.unwrap().path())
}
frames_paths.sort();
let now: DateTime<Utc> = Utc::now();
let images = load_images(&frames_paths);
if let Ok(mut output) = File::create(format!("./screenshots/{}.gif", now)) {
// encode an animated gif at 10 frames per second
let gif = engiffen(&images, 10, Quantizer::Naive).expect("GIF creation failure!");
gif.write(&mut output).expect("Error writing to file");
info!("GIF animation saved: {:?}", output);
// Remove PNG files after creating the GIF
for png_path in &frames_paths {
if let Err(err) = fs::remove_file(png_path) {
eprintln!("Error removing PNG file {}: {}", png_path.display(), err);
// Handle the error as needed
}
}
} else {
eprintln!("Error creating file");
// Handle the error as needed
}
}
fn take_screenshot_on_f12(
input: Res<Input<KeyCode>>,
main_window: Query<Entity, With<PrimaryWindow>>,
mut screenshot_manager: ResMut<ScreenshotManager>,
) {
if input.just_pressed(KeyCode::F12) {
info!("Screenshot taken.");
let now: DateTime<Utc> = Utc::now();
let path = format!("./screenshots/{}.png", now);
screenshot_manager
.save_screenshot_to_disk(main_window.single(), path)
.unwrap();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment