2023-10-30 15:05:21 +00:00
|
|
|
use super::{cmd, filter::Filter};
|
2023-10-28 21:38:17 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) struct Ffmpeg {
|
|
|
|
inputs: Vec<FfmpegInput>,
|
2023-10-29 21:58:46 +00:00
|
|
|
filters: Vec<Filter>,
|
2023-10-30 15:05:21 +00:00
|
|
|
output: PathBuf,
|
|
|
|
|
|
|
|
filter_idx: usize
|
2023-10-28 21:38:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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-30 15:05:21 +00:00
|
|
|
output,
|
|
|
|
|
|
|
|
filter_idx: 0
|
2023-10-28 21:38:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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-30 15:05:21 +00:00
|
|
|
if aenc {
|
|
|
|
cmd.arg("-c:a").arg("aac");
|
|
|
|
cmd.arg("-b:a").arg("128000");
|
|
|
|
} else {
|
|
|
|
cmd.arg("-c:a").arg("copy");
|
|
|
|
}
|
2023-10-29 21:58:46 +00:00
|
|
|
|
2023-10-28 21:38:17 +00:00
|
|
|
unimplemented!()
|
|
|
|
}
|
|
|
|
}
|