prepare writing ffmpeg commands

This commit is contained in:
Dominic 2023-10-28 23:38:17 +02:00
parent d59ca4aceb
commit 823d8ce5dc
Signed by: msrd0
GPG key ID: DCC8C247452E98F9
6 changed files with 434 additions and 11 deletions

77
src/render/ffmpeg.rs Normal file
View file

@ -0,0 +1,77 @@
use super::cmd;
use crate::time::{format_time, Time};
use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf};
use rational::Rational;
use std::process::Command;
pub(crate) struct FfmpegInput {
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 {
loop_input: false,
fps: None,
start: None,
duration: None,
path
}
}
fn append_to_cmd(self, cmd: &mut Command) {
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>,
output: PathBuf
}
impl Ffmpeg {
pub fn new(output: PathBuf) -> Self {
Self {
inputs: Vec::new(),
output
}
}
pub fn run(self) -> anyhow::Result<()> {
let mut cmd = cmd();
cmd.arg("ffmpeg").arg("-hide_banner");
// initialise a vaapi device if one exists
let vaapi_device: PathBuf = "/dev/dri/renderD128".into();
let vaapi = vaapi_device.exists();
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");
unimplemented!()
}
}

156
src/render/mod.rs Normal file
View file

@ -0,0 +1,156 @@
#![allow(warnings)]
pub mod ffmpeg;
use crate::{
iotro::intro,
time::{format_date, Time},
Project, ProjectSourceMetadata, Resolution
};
use anyhow::{bail, Context};
use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf};
use rational::Rational;
use std::{
fs::{self, File},
io::Write as _,
process::{Command, Stdio}
};
const INTRO_LEN: Time = Time {
seconds: 3,
micros: 0
};
const OUTRO_LEN: Time = Time {
seconds: 5,
micros: 0
};
const TRANSITION: &str = "fadeblack";
const TRANSITION_LEN: Time = Time {
seconds: 0,
micros: 200_000
};
fn cmd() -> Command {
let mut cmd = Command::new("busybox");
cmd.arg("ash")
.arg("-exuo")
.arg("pipefail")
.arg("-c")
.arg("exec");
cmd
}
fn ffprobe() -> Command {
let mut cmd = cmd();
cmd.arg("ffprobe")
.arg("-v")
.arg("error")
.arg("-of")
.arg("default=noprint_wrappers=1:nokey=1");
cmd
}
fn read_output(cmd: &mut Command) -> anyhow::Result<String> {
let out = cmd.stderr(Stdio::inherit()).output()?;
if !out.status.success() {
bail!(
"Executed command failed with exit status {:?}",
out.status.code()
);
}
String::from_utf8(out.stdout).context("Command returned non-utf8 output")
}
fn ffprobe_video(query: &str, input: &Path) -> anyhow::Result<String> {
read_output(
ffprobe()
.arg("-select_streams")
.arg("v:0")
.arg("-show_entries")
.arg(query)
.arg(input)
)
}
fn ffprobe_audio(query: &str, concat_input: &Path) -> anyhow::Result<String> {
read_output(
ffprobe()
.arg("-select_streams")
.arg("a:0")
.arg("-show_entries")
.arg(query)
.arg("-safe")
.arg("0")
.arg("-f")
.arg("concat")
.arg(concat_input)
)
}
fn ffmpeg() -> Command {
let mut cmd = cmd();
cmd.arg("ffmpeg")
.arg("-hide_banner")
.arg("-vaapi_device")
.arg("/dev/dri/renderD128");
cmd
}
fn render_svg(fps: Rational, tbn: u32, input: &Path, duration: Time, output: &Path) {
let mut cmd = ffmpeg();
cmd.arg("-framerate").arg(fps.to_string());
cmd.arg("-loop").arg("1");
cmd.arg("-i").arg(input);
cmd.arg("-c:v").arg("libx264");
}
pub(crate) struct Renderer<'a> {
/// The directory with all the sources.
directory: &'a Path,
/// The slug (i.e. 23ws-malo2-231016).
slug: String,
/// The target directory.
target: PathBuf
}
impl<'a> Renderer<'a> {
pub(crate) fn new(directory: &'a Path, project: &Project) -> anyhow::Result<Self> {
let slug = format!(
"{}-{}",
project.lecture.course,
format_date(project.lecture.date)
);
let target = directory.join(&slug);
Ok(Self {
directory,
slug,
target
})
}
pub(crate) fn preprocess(&self, project: &mut Project) -> anyhow::Result<()> {
assert!(!project.progress.preprocessed);
let recording_txt = self.target.join("recording.txt");
let mut file = File::create(recording_txt)?;
for filename in &project.source.files {
writeln!(file, "file {:?}", self.directory.join(filename).to_string());
}
drop(file);
println!("\x1B[1m ==> Concatenating Video and Normalising Audio ...");
let mut ffmpeg = Ffmpeg::new();
// project.source.metadata = Some(ProjectSourceMetadata {
// source_duration: ffprobe_video("format=duration", input)?.parse()?
// });
let intro_svg = self.target.join("intro.svg");
// fs::write(&intro_svg, intro(res, date));
let intro_mp4 = self.target.join("intro.mp4");
Ok(())
}
}