prepare writing ffmpeg commands
This commit is contained in:
parent
d59ca4aceb
commit
823d8ce5dc
6 changed files with 434 additions and 11 deletions
77
src/render/ffmpeg.rs
Normal file
77
src/render/ffmpeg.rs
Normal 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
156
src/render/mod.rs
Normal 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(())
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue