157 lines
3.3 KiB
Rust
157 lines
3.3 KiB
Rust
|
#![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(())
|
||
|
}
|
||
|
}
|