render_video/src/render/ffmpeg.rs

159 lines
3.1 KiB
Rust
Raw Normal View History

2023-10-28 21:38:17 +00:00
use super::cmd;
use crate::time::{format_time, Time};
use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf};
use rational::Rational;
2023-10-29 21:58:46 +00:00
use std::{borrow::Cow, process::Command};
2023-10-28 21:38:17 +00:00
pub(crate) struct FfmpegInput {
2023-10-29 21:58:46 +00:00
pub(crate) concat: bool,
2023-10-28 21:38:17 +00:00
pub(crate) loop_input: bool,
pub(crate) fps: Option<Rational>,
pub(crate) start: Option<Time>,
pub(crate) duration: Option<Time>,
pub(crate) path: PathBuf
}
impl FfmpegInput {
pub(crate) fn new(path: PathBuf) -> Self {
Self {
2023-10-29 21:58:46 +00:00
concat: false,
2023-10-28 21:38:17 +00:00
loop_input: false,
fps: None,
start: None,
duration: None,
path
}
}
fn append_to_cmd(self, cmd: &mut Command) {
2023-10-29 21:58:46 +00:00
if self.concat {
cmd.arg("-f").arg("concat").arg("-safe").arg("0");
}
2023-10-28 21:38:17 +00:00
if self.loop_input {
cmd.arg("-loop").arg("1");
}
if let Some(fps) = self.fps {
cmd.arg("-r").arg(fps.to_string());
}
if let Some(start) = self.start {
cmd.arg("-ss").arg(format_time(start));
}
if let Some(duration) = self.duration {
cmd.arg("-t").arg(format_time(duration));
}
cmd.arg("-i").arg(self.path);
}
}
2023-10-29 21:58:46 +00:00
pub(crate) enum Filter {
Concat {
inputs: Vec<Cow<'static, str>>,
n: usize,
output: Cow<'static, str>
},
FadeIn {
input: Cow<'static, str>,
start: Time,
duration: Time,
output: Cow<'static, str>
},
FadeOut {
input: Cow<'static, str>,
start: Time,
duration: Time,
output: Cow<'static, str>
},
Overlay {
video_input: Cow<'static, str>,
overlay_input: Cow<'static, str>,
x: Cow<'static, str>,
y: Cow<'static, str>,
output: Cow<'static, str>
},
GenerateSilence {
output: Cow<'static, str>
}
}
impl Filter {
fn is_video_filter(&self) -> bool {
matches!(
self,
Self::Concat { .. }
| Self::FadeIn { .. }
| Self::FadeOut { .. }
| Self::Overlay { .. }
)
}
fn is_audio_filter(&self) -> bool {
matches!(
self,
Self::Concat { .. }
| Self::FadeIn { .. }
| Self::FadeOut { .. }
| Self::GenerateSilence { .. }
)
}
}
2023-10-28 21:38:17 +00:00
pub(crate) struct Ffmpeg {
inputs: Vec<FfmpegInput>,
2023-10-29 21:58:46 +00:00
filters: Vec<Filter>,
2023-10-28 21:38:17 +00:00
output: PathBuf
}
impl Ffmpeg {
pub fn new(output: PathBuf) -> Self {
Self {
inputs: Vec::new(),
2023-10-29 21:58:46 +00:00
filters: Vec::new(),
2023-10-28 21:38:17 +00:00
output
}
}
pub fn run(self) -> anyhow::Result<()> {
let mut cmd = cmd();
cmd.arg("ffmpeg").arg("-hide_banner");
2023-10-29 21:58:46 +00:00
// determine whether the video need to be re-encoded
let venc = self.filters.iter().any(|f| f.is_video_filter());
let aenc = self.filters.iter().any(|f| f.is_audio_filter());
2023-10-28 21:38:17 +00:00
// initialise a vaapi device if one exists
let vaapi_device: PathBuf = "/dev/dri/renderD128".into();
2023-10-29 21:58:46 +00:00
let vaapi = venc && vaapi_device.exists();
2023-10-28 21:38:17 +00:00
if vaapi {
cmd.arg("-vaapi_device").arg(&vaapi_device);
}
// append all the inputs
for i in self.inputs {
i.append_to_cmd(&mut cmd);
}
// always try to synchronise audio
cmd.arg("-async").arg("1");
2023-10-29 21:58:46 +00:00
// TODO apply filters
// append encoding options
if vaapi {
cmd.arg("-c:v").arg("h264_vaapi");
cmd.arg("-rc_mode").arg("CQP");
cmd.arg("-global_quality").arg("22");
} else if venc {
cmd.arg("-c:v").arg("libx264");
cmd.arg("-crf").arg("22");
} else {
cmd.arg("-c:v").arg("copy");
}
2023-10-28 21:38:17 +00:00
unimplemented!()
}
}