very basic rendering support

This commit is contained in:
Dominic 2023-10-30 21:26:17 +01:00
parent 889dbbce5a
commit 4efbe0c44e
Signed by: msrd0
GPG key ID: DCC8C247452E98F9
5 changed files with 198 additions and 11 deletions

View file

@ -238,5 +238,6 @@ fn main() {
fs::write(&project_path, toml::to_string(&project).unwrap().as_bytes()).unwrap(); fs::write(&project_path, toml::to_string(&project).unwrap().as_bytes()).unwrap();
} }
// render(&directory, &project).unwrap(); let video = renderer.render(&mut project).unwrap();
println!("\x1B[1m ==> DONE :)\x1B[0m Video: {video}");
} }

View file

@ -74,9 +74,9 @@ impl Ffmpeg {
} }
} }
pub fn add_input(&mut self, input: FfmpegInput) -> &mut Self { pub fn add_input(&mut self, input: FfmpegInput) -> String {
self.inputs.push(input); self.inputs.push(input);
self (self.inputs.len() - 1).to_string()
} }
pub fn add_filter(&mut self, filter: Filter) -> &mut Self { pub fn add_filter(&mut self, filter: Filter) -> &mut Self {

View file

@ -46,6 +46,16 @@ pub(crate) enum Filter {
GenerateSilence { GenerateSilence {
video: Cow<'static, str>, video: Cow<'static, str>,
output: Cow<'static, str> output: Cow<'static, str>
},
/// Fast forward. Too complex to explain. Its magic.
FastForward {
input: Cow<'static, str>,
ffinput: Cow<'static, str>,
start: Time,
duration: Time,
multiplier: usize,
output: Cow<'static, str>
} }
} }
@ -85,13 +95,13 @@ impl Filter {
} => { } => {
let mut args = String::new(); let mut args = String::new();
if let Some(start) = start { if let Some(start) = start {
write!(args, "start={}", format_time(*start)); write!(args, "start={start}");
} }
if let Some(duration) = duration { if let Some(duration) = duration {
if !args.is_empty() { if !args.is_empty() {
args += ":"; args += ":";
} }
write!(args, "duration={}", format_time(*duration)); write!(args, "duration={duration}");
} }
writeln!( writeln!(
complex, complex,
@ -161,11 +171,7 @@ impl Filter {
duration, duration,
output output
} => { } => {
let args = format!( let args = format!("{direction}:st={start}:d={duration}");
"{direction}:st={}:d={}",
format_time(*start),
format_time(*duration)
);
writeln!( writeln!(
complex, complex,
"{}fade={args}{};", "{}fade={args}{};",
@ -190,9 +196,85 @@ impl Filter {
writeln!(complex, "aevalsrc=0:s=48000{};", channel('a', output)); writeln!(complex, "aevalsrc=0:s=48000{};", channel('a', output));
}, },
_ => unimplemented!() Self::FastForward {
input,
ffinput,
start,
duration,
multiplier,
output
} => {
let end = *start + *duration;
// ok so let's start by duplicating the audio and video 3 times
let vin = next_tmp_3(filter_idx);
let ain = next_tmp_3(filter_idx);
writeln!(
complex,
"{}split=3{}{}{};",
channel('v', input),
vin[0],
vin[1],
vin[2]
);
writeln!(
complex,
"{}asplit=3{}{}{};",
channel('v', input),
vin[0],
vin[1],
vin[2]
);
// next we cut those audio/videos into before, ff, after
let vcut = next_tmp_3(filter_idx);
let acut = next_tmp_3(filter_idx);
writeln!(complex, "{}trim=duration={start}{};", vin[0], vcut[0]);
writeln!(complex, "{}atrim=duration={start}{};", ain[0], acut[0]);
writeln!(
complex,
"{}trim=start={start}:duration={duration}{};",
vin[1], vcut[1]
);
writeln!(
complex,
"{}atrim=start={start}:duration={duration}{};",
ain[1], acut[1]
);
writeln!(complex, "{}trim=start={end}{};", vin[2], vcut[2]);
writeln!(complex, "{}atrim=start={end}{};", ain[2], acut[2]);
// now we speed up the ff part
let vff = next_tmp(filter_idx);
let aff = next_tmp(filter_idx);
writeln!(complex, "{}setpts=PTS/{multiplier}{vff};", vcut[1]);
writeln!(complex, "{}atempo={multiplier}{aff};", acut[1]);
// and we overlay the vff part
let voverlay = next_tmp(filter_idx);
writeln!(
complex,
"{vff}{}overlay=x=main_w/2-overlay_w/2:y=main_h/2-overlay_h/2{voverlay};",
channel('v', ffinput)
);
// and finally we concatenate everything back together
writeln!(
complex,
"{}{}{voverlay}{aff}{}{}concat=n=3:v=1:a=1{}{};",
vcut[0],
acut[0],
vcut[2],
acut[2],
channel('v', output),
channel('a', output)
);
} }
} }
// add a newline after every filter to ease debugging
writeln!(complex);
}
} }
pub(super) fn channel(channel: char, id: &str) -> String { pub(super) fn channel(channel: char, id: &str) -> String {
@ -202,3 +284,16 @@ pub(super) fn channel(channel: char, id: &str) -> String {
format!("[{id}:{channel}]") format!("[{id}:{channel}]")
} }
} }
fn next_tmp(filter_idx: &mut usize) -> String {
*filter_idx += 1;
format!("[tmp{filter_idx}]")
}
fn next_tmp_3(filter_idx: &mut usize) -> [String; 3] {
[
next_tmp(filter_idx),
next_tmp(filter_idx),
next_tmp(filter_idx)
]
}

View file

@ -14,6 +14,7 @@ use anyhow::{bail, Context};
use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf}; use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf};
use rational::Rational; use rational::Rational;
use std::{ use std::{
borrow::Cow,
fs::{self, File}, fs::{self, File},
io::Write as _, io::Write as _,
process::{Command, Stdio} process::{Command, Stdio}
@ -251,4 +252,64 @@ impl<'a> Renderer<'a> {
Ok(()) Ok(())
} }
pub(crate) fn render(&self, project: &mut Project) -> anyhow::Result<PathBuf> {
let mut output = self.target.join(format!(
"{}-{}p.mp4",
self.slug,
project.source.metadata.as_ref().unwrap().source_res.width()
));
let mut ffmpeg = Ffmpeg::new(output.clone());
// add all of our inputs
let intro = ffmpeg.add_input(FfmpegInput::new(self.target.join("intro.mp4")));
let rec = ffmpeg.add_input(FfmpegInput::new(self.target.join("recording.mp4")));
let outro = ffmpeg.add_input(FfmpegInput::new(self.target.join("outro.mp4")));
let logo = ffmpeg.add_input(FfmpegInput::new(self.target.join("logo.png")));
let ff = ffmpeg.add_input(FfmpegInput::new(self.target.join("fastforward.png")));
let mut part1: Cow<'static, str> = intro.into();
let mut part2: Cow<'static, str> = rec.into();
let mut part3: Cow<'static, str> = outro.into();
// trim the recording
let rectrim = "rectrim";
let start = project.source.start.unwrap();
let duration = project.source.end.unwrap() - start;
ffmpeg.add_filter(Filter::Trim {
input: part2,
start: Some(start),
duration: Some(duration),
output: rectrim.into()
});
part2 = rectrim.into();
// TODO ff
// TODO fade
// concatenate everything
let concat = "concat";
ffmpeg.add_filter(Filter::Concat {
inputs: vec![part1, part2, part3],
n: 3,
output: concat.into()
});
// overlay the logo
let overlay = "overlay";
ffmpeg.add_filter(Filter::Overlay {
video_input: concat.into(),
overlay_input: logo.into(),
x: "main_w-overlay_w-130".into(),
y: "main_h-overlay_h-65".into(),
output: overlay.into()
});
// we're done :)
ffmpeg.set_filter_output(overlay);
ffmpeg.run()?;
Ok(output)
}
} }

View file

@ -1,6 +1,7 @@
use anyhow::bail; use anyhow::bail;
use std::{ use std::{
fmt::{self, Display, Write as _}, fmt::{self, Display, Write as _},
ops::{Add, Sub},
str::FromStr str::FromStr
}; };
@ -82,6 +83,35 @@ pub struct Time {
pub micros: u32 pub micros: u32
} }
impl Add for Time {
type Output = Self;
fn add(self, rhs: Self) -> Self {
let mut seconds = self.seconds + rhs.seconds;
let mut micros = self.micros + rhs.micros;
if micros >= 1_000_000 {
seconds += 1;
micros -= 1_000_000;
}
Self { seconds, micros }
}
}
impl Sub for Time {
type Output = Self;
fn sub(mut self, rhs: Self) -> Self {
if rhs.micros > self.micros {
self.seconds -= 1;
self.micros += 1_000_000;
}
Self {
seconds: self.seconds - rhs.seconds,
micros: self.micros - rhs.micros
}
}
}
impl FromStr for Time { impl FromStr for Time {
type Err = anyhow::Error; type Err = anyhow::Error;