Skip to content
Snippets Groups Projects
Unverified Commit 4efbe0c4 authored by Dominic Meiser's avatar Dominic Meiser
Browse files

very basic rendering support

parent 889dbbce
No related branches found
No related tags found
No related merge requests found
...@@ -238,5 +238,6 @@ fn main() { ...@@ -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}");
} }
...@@ -74,9 +74,9 @@ impl Ffmpeg { ...@@ -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 {
......
...@@ -46,6 +46,16 @@ pub(crate) enum Filter { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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)
]
}
...@@ -14,6 +14,7 @@ use anyhow::{bail, Context}; ...@@ -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> { ...@@ -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)
}
} }
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 { ...@@ -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;
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment