Skip to main content

mraphics_native/
recorder.rs

1use crate::Canvas;
2use mraphics_core::{Action, Camera, LogicalTimeline, Timeline};
3use std::{
4    cell::Cell,
5    io::Write,
6    process::{Command, Stdio},
7    rc::Rc,
8};
9
10pub struct Recorder<'canvas> {
11    pub timeline: LogicalTimeline<'canvas>,
12
13    pub output_path: String,
14}
15
16impl<'canvas> Recorder<'canvas> {
17    pub fn new() -> Self {
18        Self {
19            timeline: LogicalTimeline::new(),
20
21            output_path: String::from("video.mp4"),
22        }
23    }
24
25    pub fn with_fps(mut self, fps: f32) -> Self {
26        self.timeline.logical_fps = fps;
27        self
28    }
29
30    pub fn set_fps(&mut self, fps: f32) {
31        self.timeline.logical_fps = fps;
32    }
33
34    pub fn record<'res, T: Timeline<'res>, C: Camera>(
35        &mut self,
36        canvas: &'canvas mut Canvas<'res, T, C>,
37    ) {
38        let canvas_time_bak = canvas.timeline.current_time();
39
40        let is_recording = Rc::new(Cell::new(true));
41        let is_recording_clone = is_recording.clone();
42
43        let mut command = Command::new("ffmpeg");
44
45        #[rustfmt::skip]
46        command.stdin(Stdio::piped()).args([
47            "-y",
48            "-f", "rawvideo",
49            "-s", &format!("{}x{}", canvas.size.0, canvas.size.1),
50            "-pix_fmt", "rgba",
51            "-r", &self.timeline.logical_fps.to_string(),
52            "-i", "-",
53            "-vcodec", "libx264",
54            &self.output_path,
55        ]);
56
57        let mut process = command.spawn().expect("Failed to invoke FFmpeg");
58
59        let playhead = Rc::new(Cell::new(0.0));
60        let playhead_clone = playhead.clone();
61
62        let mut record_action = Action::new();
63        record_action.on_execute = Box::new(move || {
64            if canvas.timeline.all_stopped() {
65                process.stdin.as_ref().unwrap().flush().unwrap();
66
67                process.wait().unwrap();
68
69                is_recording_clone.replace(false);
70
71                canvas.timeline.seek(canvas_time_bak);
72
73                return;
74            }
75
76            canvas.timeline.seek(playhead_clone.get());
77            canvas.timeline.process();
78            canvas.render_offscreen();
79
80            let raw_img = canvas
81                .renderer
82                .as_ref()
83                .unwrap()
84                .read_texture_rgbau8(canvas.offscreen_texture.as_ref().unwrap(), canvas.size);
85
86            process.stdin.as_mut().unwrap().write_all(&raw_img).unwrap();
87        });
88
89        self.timeline.add_action(record_action);
90        self.timeline.start();
91
92        while is_recording.get() {
93            playhead.replace(self.timeline.current_time());
94            self.timeline.forward();
95        }
96    }
97}