very basic rendering support
This commit is contained in:
parent
889dbbce5a
commit
4efbe0c44e
5 changed files with 198 additions and 11 deletions
|
@ -238,5 +238,6 @@ fn main() {
|
|||
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}");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
(self.inputs.len() - 1).to_string()
|
||||
}
|
||||
|
||||
pub fn add_filter(&mut self, filter: Filter) -> &mut Self {
|
||||
|
|
|
@ -46,6 +46,16 @@ pub(crate) enum Filter {
|
|||
GenerateSilence {
|
||||
video: 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();
|
||||
if let Some(start) = start {
|
||||
write!(args, "start={}", format_time(*start));
|
||||
write!(args, "start={start}");
|
||||
}
|
||||
if let Some(duration) = duration {
|
||||
if !args.is_empty() {
|
||||
args += ":";
|
||||
}
|
||||
write!(args, "duration={}", format_time(*duration));
|
||||
write!(args, "duration={duration}");
|
||||
}
|
||||
writeln!(
|
||||
complex,
|
||||
|
@ -161,11 +171,7 @@ impl Filter {
|
|||
duration,
|
||||
output
|
||||
} => {
|
||||
let args = format!(
|
||||
"{direction}:st={}:d={}",
|
||||
format_time(*start),
|
||||
format_time(*duration)
|
||||
);
|
||||
let args = format!("{direction}:st={start}:d={duration}");
|
||||
writeln!(
|
||||
complex,
|
||||
"{}fade={args}{};",
|
||||
|
@ -190,8 +196,84 @@ impl Filter {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,3 +284,16 @@ pub(super) fn channel(channel: char, id: &str) -> String {
|
|||
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)
|
||||
]
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ use anyhow::{bail, Context};
|
|||
use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf};
|
||||
use rational::Rational;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
fs::{self, File},
|
||||
io::Write as _,
|
||||
process::{Command, Stdio}
|
||||
|
@ -251,4 +252,64 @@ impl<'a> Renderer<'a> {
|
|||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
30
src/time.rs
30
src/time.rs
|
@ -1,6 +1,7 @@
|
|||
use anyhow::bail;
|
||||
use std::{
|
||||
fmt::{self, Display, Write as _},
|
||||
ops::{Add, Sub},
|
||||
str::FromStr
|
||||
};
|
||||
|
||||
|
@ -82,6 +83,35 @@ pub struct Time {
|
|||
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 {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
|
|
Loading…
Reference in a new issue