maple_render_core/
vid_anim.rs1use std::{
2 io::Write,
3 path::Path,
4 process::{Command, Stdio},
5};
6
7use crate::{
8 error::{Error, Result},
9 renders::Renders,
10};
11
12pub struct VidAnim {
13 renders: Renders,
14 period: f64,
15 hold: f64,
16 first_frame: i32,
17}
18
19impl VidAnim {
20 pub fn new(renders: Renders) -> Self {
21 VidAnim { renders, period: 0.1, hold: 5.0, first_frame: -1 }
22 }
23
24 pub fn set_first_frame(&mut self, index: i32) {
25 self.first_frame = index;
26 }
27
28 pub fn set_timing(&mut self, period: f64, hold: f64) {
29 self.period = period;
30 self.hold = hold;
31 }
32
33 pub fn apply<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
34 let rate = 30;
35 let frame_rate = 1.0 / rate as f64;
36
37 let frames = self.renders.length() as i32;
38 if frames == 0 {
39 return Err(Error::VideoEncode("No frames to encode".to_string()));
40 }
41
42 let first_render = self.renders.get_render(0)?;
43 let width = first_render.get().width();
44 let height = first_render.get().height();
45
46 let mut ffmpeg = Command::new("ffmpeg")
47 .args([
48 "-y",
49 "-f",
50 "rawvideo",
51 "-pixel_format",
52 "rgb24",
53 "-video_size",
54 &format!("{}x{}", width, height),
55 "-framerate",
56 &rate.to_string(),
57 "-i",
58 "-",
59 "-c:v",
60 "libx264",
61 "-pix_fmt",
62 "yuv420p",
63 "-preset",
64 "medium",
65 "-crf",
66 "23",
67 ])
68 .arg(path.as_ref())
69 .stdin(Stdio::piped())
70 .stdout(Stdio::null())
71 .stderr(Stdio::null())
72 .spawn()
73 .map_err(|e| Error::VideoEncode(format!("Failed to start ffmpeg: {}", e)))?;
74
75 let mut stdin = ffmpeg
76 .stdin
77 .take()
78 .ok_or_else(|| Error::VideoEncode("Failed to open ffmpeg stdin".to_string()))?;
79
80 let mut curr = 0.0f64;
81 let mut target = -1.0f64;
82 let mut base = 0;
83 let mut current_frame_data: Option<Vec<u8>> = None;
84
85 loop {
86 let i = if self.first_frame >= 0 { (base + self.first_frame) % frames } else { base };
87
88 if curr >= target {
89 if base >= frames {
90 break;
91 }
92
93 let step = if i == frames - 1 { self.period + self.hold } else { self.period };
94 target = curr + step;
95
96 let render = self.renders.get_render(i)?;
97 let img = render.get();
98
99 let mut rgb_data = Vec::with_capacity((width * height * 3) as usize);
100 for pixel in img.pixels() {
101 rgb_data.push(pixel[0]);
102 rgb_data.push(pixel[1]);
103 rgb_data.push(pixel[2]);
104 }
105 current_frame_data = Some(rgb_data);
106
107 self.renders.remove_render(i);
108 base += 1;
109 }
110
111 if let Some(ref data) = current_frame_data {
113 stdin
114 .write_all(data)
115 .map_err(|e| Error::VideoEncode(format!("Failed to write frame: {}", e)))?;
116 }
117
118 curr += frame_rate;
119 }
120
121 drop(stdin);
122
123 let status = ffmpeg
124 .wait()
125 .map_err(|e| Error::VideoEncode(format!("Failed to wait for ffmpeg: {}", e)))?;
126
127 if !status.success() {
128 return Err(Error::VideoEncode("ffmpeg exited with error".to_string()));
129 }
130
131 Ok(())
132 }
133}