#![warn(missing_docs)]
use std::error;
use std::result;
use std::fmt;
use std::process::{Command, Child, Stdio, ExitStatus};
use std::io::Write;
use std::ffi::OsStr;
pub type Color = u32;
pub fn get_color(r: u8, g: u8, b: u8, a: u8) -> Color {
Color::from_be_bytes([r, g, b, a])
}
#[derive(Debug)]
pub enum Error {
IOError(std::io::Error),
FFMpegExitedAbnormally(ExitStatus),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::FFMpegExitedAbnormally(code) => if let Some(code) = code.code() {
write!(f, "ffmpeg exited abnormally with code {code}")
} else {
write!(f, "ffmpeg exited abnormally")
},
Error::IOError(e) => write!(f, "io error: {e}"),
}
}
}
impl error::Error for Error {}
impl From<std::io::Error> for Error {
fn from(e: std::io::Error) -> Error { Error::IOError(e) }
}
pub type Result<T> = result::Result<T, Error>;
pub struct FFMpeg {
child: Child,
width: usize,
height: usize,
fps: u32,
}
pub fn start(out_file: impl AsRef<OsStr>, width: usize, height: usize, fps: u32) -> Result<FFMpeg> {
FFMpeg::start(out_file, width, height, fps)
}
impl FFMpeg {
pub fn start(out_file: impl AsRef<OsStr>, width: usize, height: usize, fps: u32) -> Result<FFMpeg> {
let child = Command::new("ffmpeg")
.args(["-loglevel", "verbose", "-y"])
.args(["-f", "rawvideo"])
.args(["-pix_fmt", "rgba"])
.args(["-s", &format!("{width}x{height}")])
.args(["-r", &format!("{fps}")])
.args(["-i", "-"])
.arg(out_file)
.stdin(Stdio::piped())
.spawn()?;
Ok(FFMpeg { child, width, height, fps })
}
pub fn width(&self) -> usize { self.width }
pub fn height(&self) -> usize { self.height }
pub fn fps(&self) -> u32 { self.fps }
pub fn resolution(&self) -> (usize, usize) { (self.width, self.height) }
pub fn send_frame(&mut self, pixels: &[Color]) -> Result<()> {
assert_eq!(pixels.len(), self.width * self.height);
let stdin = self.child.stdin.as_mut().expect("we set stdin to piped");
let pixels_u8: &[u8] = unsafe {
let ptr = pixels.as_ptr();
let len = pixels.len();
use std::mem::size_of;
std::slice::from_raw_parts(ptr as *const u8, len * (size_of::<Color>() / size_of::<u8>()))
};
stdin.write_all(pixels_u8)?;
Ok(())
}
pub fn finalize(mut self) -> Result<()> {
let retcode = self.child.wait()?;
if !retcode.success() {
return Err(Error::FFMpegExitedAbnormally(retcode));
}
Ok(())
}
}
impl std::ops::Drop for FFMpeg {
fn drop(&mut self) {
_ = self.child.wait();
}
}