use std::{
io::Write,
path::PathBuf,
process::{Child, ChildStdin, Command, Stdio},
};
#[derive(Debug, Clone)]
pub struct FileWriterBuilder {
pub file_path: PathBuf,
pub width: u32,
pub height: u32,
pub fps: u32,
pub vf_args: Vec<String>,
pub video_codec: String,
pub pixel_format: String,
}
impl Default for FileWriterBuilder {
fn default() -> Self {
Self {
file_path: PathBuf::from("output.mp4"),
width: 1920,
height: 1080,
fps: 60,
vf_args: vec!["eq=saturation=1.0:gamma=1.0".to_string()],
video_codec: "libx264".to_string(),
pixel_format: "yuv420p".to_string(),
}
}
}
#[allow(unused)]
impl FileWriterBuilder {
pub fn with_file_path(mut self, file_path: PathBuf) -> Self {
self.file_path = file_path;
self
}
pub fn with_size(mut self, width: u32, height: u32) -> Self {
self.width = width;
self.height = height;
self
}
pub fn with_fps(mut self, fps: u32) -> Self {
self.fps = fps;
self
}
pub fn enable_fast_encoding(mut self) -> Self {
self.video_codec = "libx264rgb".to_string();
self.pixel_format = "rgb32".to_string();
self
}
pub fn output_gif(mut self) -> Self {
self.file_path = self.file_path.with_file_name(format!(
"{}.gif",
self.file_path.file_stem().unwrap().to_string_lossy()
));
self.fps = 30;
self.video_codec = "gif".to_string();
self.pixel_format = "rgb8".to_string();
self
}
pub fn build(self) -> FileWriter {
let parent = self.file_path.parent().unwrap();
if !parent.exists() {
std::fs::create_dir_all(parent).unwrap();
}
let mut command = Command::new("ffmpeg");
#[rustfmt::skip]
let command = command.args([
"-y",
"-f", "rawvideo",
"-s", format!("{}x{}", self.width, self.height).as_str(),
"-pix_fmt", "rgba",
"-r", self.fps.to_string().as_str(),
"-i", "-",
"-an",
"-loglevel", "error",
"-vcodec", self.video_codec.as_str(),
"-pix_fmt", self.pixel_format.as_str(),
&self.file_path.to_string_lossy(),
]).stdin(Stdio::piped());
if !self.vf_args.is_empty() {
command.args(["-vf", self.vf_args.join(",").as_str()]);
}
let mut child = command.spawn().expect("Failed to spawn ffmpeg");
FileWriter {
child_in: child.stdin.take(),
child,
}
}
}
pub struct FileWriter {
child: Child,
child_in: Option<ChildStdin>,
}
impl Drop for FileWriter {
fn drop(&mut self) {
self.child_in
.as_mut()
.unwrap()
.flush()
.expect("Failed to flush ffmpeg");
drop(self.child_in.take());
self.child.wait().expect("Failed to wait ffmpeg");
}
}
impl FileWriter {
pub fn write_frame(&mut self, frame: &[u8]) {
self.child_in
.as_mut()
.unwrap()
.write_all(frame)
.expect("Failed to write frame");
}
}