attempt to trim every part from the recording; also out of ram
This commit is contained in:
parent
85297910ae
commit
164bb8faa3
2 changed files with 103 additions and 124 deletions
|
@ -47,14 +47,12 @@ pub(crate) enum Filter {
|
|||
output: Cow<'static, str>
|
||||
},
|
||||
|
||||
/// Fast forward. Will output before, ff, and after parts separately.
|
||||
/// Fast forward.
|
||||
FastForward {
|
||||
input: Cow<'static, str>,
|
||||
ffinput: Cow<'static, str>,
|
||||
start: Time,
|
||||
duration: Time,
|
||||
multiplier: usize,
|
||||
output: [Cow<'static, str>; 3]
|
||||
output: Cow<'static, str>
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,90 +197,27 @@ impl Filter {
|
|||
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('a', input),
|
||||
ain[0],
|
||||
ain[1],
|
||||
ain[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},setpts=PTS-STARTPTS{};",
|
||||
vin[1], vcut[1]
|
||||
);
|
||||
writeln!(
|
||||
complex,
|
||||
"{}atrim=start={start}:duration={duration},asetpts=PTS-STARTPTS{};",
|
||||
ain[1], acut[1]
|
||||
);
|
||||
writeln!(
|
||||
complex,
|
||||
"{}trim=start={end},setpts=PTS-STARTPTS{};",
|
||||
vin[2], vcut[2]
|
||||
);
|
||||
writeln!(
|
||||
complex,
|
||||
"{}atrim=start={end},asetpts=PTS-STARTPTS{};",
|
||||
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)
|
||||
"{}setpts=PTS/{multiplier}{vff};",
|
||||
channel('v', input)
|
||||
);
|
||||
writeln!(
|
||||
complex,
|
||||
"{}atempo={multiplier}{};",
|
||||
channel('a', input),
|
||||
channel('a', output)
|
||||
);
|
||||
writeln!(
|
||||
complex,
|
||||
"{vff}{}overlay=x=main_w/2-overlay_w/2:y=main_h/2-overlay_h/2{};",
|
||||
channel('v', ffinput),
|
||||
channel('v', output)
|
||||
);
|
||||
|
||||
// and finally we place everything into the output
|
||||
for (i, o) in [
|
||||
(&vcut[0], &output[0]),
|
||||
(&voverlay, &output[1]),
|
||||
(&vcut[2], &output[2])
|
||||
] {
|
||||
write!(complex, "{i}null{};", channel('v', o));
|
||||
}
|
||||
writeln!(complex);
|
||||
for (i, o) in [
|
||||
(&acut[0], &output[0]),
|
||||
(&aff, &output[1]),
|
||||
(&acut[2], &output[2])
|
||||
] {
|
||||
write!(complex, "{i}anull{};", channel('a', o));
|
||||
}
|
||||
writeln!(complex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,9 @@ const TRANSITION_LEN: Time = Time {
|
|||
micros: 200_000
|
||||
};
|
||||
const FF_MULTIPLIER: usize = 8;
|
||||
// logo sizes at full hd, will be scaled to source resolution
|
||||
const FF_LOGO_SIZE: usize = 128;
|
||||
const LOGO_SIZE: usize = 96;
|
||||
|
||||
fn cmd() -> Command {
|
||||
let mut cmd = Command::new("busybox");
|
||||
|
@ -227,7 +230,7 @@ impl<'a> Renderer<'a> {
|
|||
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/logo.svg"))
|
||||
)?;
|
||||
let logo_png = self.target.join("logo.png");
|
||||
svg2png(&logo_svg, &logo_png, 150 * source_res.width() / 1920)?;
|
||||
svg2png(&logo_svg, &logo_png, LOGO_SIZE * source_res.width() / 1920)?;
|
||||
|
||||
// copy fastforward then render to png
|
||||
let fastforward_svg = self.target.join("fastforward.svg");
|
||||
|
@ -242,7 +245,7 @@ impl<'a> Renderer<'a> {
|
|||
svg2png(
|
||||
&fastforward_svg,
|
||||
&fastforward_png,
|
||||
128 * source_res.width() / 1920
|
||||
FF_LOGO_SIZE * source_res.width() / 1920
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
|
@ -270,20 +273,85 @@ impl<'a> Renderer<'a> {
|
|||
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";
|
||||
// the recording is fun because of all the fast forwarding
|
||||
let mut part2 = VecDeque::new();
|
||||
let mut part2_start_of_the_end = None;
|
||||
let mut part2_end_of_the_start = None;
|
||||
|
||||
// ok so ff is fun. we will add the ff'ed section as well as the part between
|
||||
// the previous ff'ed section and our new section, unless we are the first
|
||||
project.source.fast.sort();
|
||||
for (i, (ff_st, ff_end)) in project.source.fast.iter().rev().enumerate() {
|
||||
if let Some(prev_end) = part2_end_of_the_start {
|
||||
let recffbetween = format!("recff{i}between");
|
||||
ffmpeg.add_filter(Filter::Trim {
|
||||
input: rec.clone().into(),
|
||||
start: Some(*ff_end),
|
||||
duration: Some(prev_end),
|
||||
output: recffbetween.clone().into()
|
||||
});
|
||||
part2.push_front(recffbetween.into());
|
||||
} else {
|
||||
part2_start_of_the_end = Some(*ff_end);
|
||||
}
|
||||
part2_end_of_the_start = Some(*ff_st);
|
||||
|
||||
let recffpart = format!("recff{i}part");
|
||||
ffmpeg.add_filter(Filter::Trim {
|
||||
input: rec.clone().into(),
|
||||
start: Some(*ff_st),
|
||||
duration: Some(*ff_end - *ff_st),
|
||||
output: recffpart.clone().into()
|
||||
});
|
||||
|
||||
let recff = format!("recff{i}");
|
||||
ffmpeg.add_filter(Filter::FastForward {
|
||||
input: recffpart.into(),
|
||||
ffinput: ff.clone().into(),
|
||||
multiplier: FF_MULTIPLIER,
|
||||
output: recff.clone().into()
|
||||
});
|
||||
part2.push_front(recff.into());
|
||||
}
|
||||
|
||||
// if the recording was not ff'ed, perform a normal trim
|
||||
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();
|
||||
let end = project.source.end.unwrap();
|
||||
let part2_last_part_duration;
|
||||
if part2.is_empty() {
|
||||
let rectrim = "rectrim";
|
||||
part2_last_part_duration = end - start;
|
||||
ffmpeg.add_filter(Filter::Trim {
|
||||
input: rec.into(),
|
||||
start: Some(start),
|
||||
duration: Some(part2_last_part_duration),
|
||||
output: rectrim.into()
|
||||
});
|
||||
part2.push_back(rectrim.into());
|
||||
}
|
||||
// otherwise add the first and last parts separately
|
||||
else {
|
||||
let rectrimst = "rectrimst";
|
||||
ffmpeg.add_filter(Filter::Trim {
|
||||
input: rec.clone().into(),
|
||||
start: Some(start),
|
||||
duration: Some(part2_end_of_the_start.unwrap() - start),
|
||||
output: rectrimst.into()
|
||||
});
|
||||
part2.push_front(rectrimst.into());
|
||||
|
||||
let rectrimend = "rectrimend";
|
||||
part2_last_part_duration = end - part2_start_of_the_end.unwrap();
|
||||
ffmpeg.add_filter(Filter::Trim {
|
||||
input: rec.into(),
|
||||
start: Some(part2_start_of_the_end.unwrap()),
|
||||
duration: Some(part2_last_part_duration),
|
||||
output: rectrimend.into()
|
||||
});
|
||||
part2.push_back(rectrimend.into());
|
||||
}
|
||||
|
||||
// fade out the intro
|
||||
let introfade = "introfade";
|
||||
|
@ -299,7 +367,7 @@ impl<'a> Renderer<'a> {
|
|||
// fade in the recording
|
||||
let recfadein = "recfadein";
|
||||
ffmpeg.add_filter(Filter::Fade {
|
||||
input: part2,
|
||||
input: part2.pop_front().unwrap(),
|
||||
direction: "in",
|
||||
start: Time {
|
||||
seconds: 0,
|
||||
|
@ -308,18 +376,18 @@ impl<'a> Renderer<'a> {
|
|||
duration: TRANSITION_LEN,
|
||||
output: recfadein.into()
|
||||
});
|
||||
part2 = recfadein.into();
|
||||
part2.push_front(recfadein.into());
|
||||
|
||||
// fade out the recording
|
||||
let recfadeout = "recfadeout";
|
||||
ffmpeg.add_filter(Filter::Fade {
|
||||
input: part2,
|
||||
input: part2.pop_back().unwrap(),
|
||||
direction: "out",
|
||||
start: duration - TRANSITION_LEN,
|
||||
start: part2_last_part_duration - TRANSITION_LEN,
|
||||
duration: TRANSITION_LEN,
|
||||
output: recfadeout.into()
|
||||
});
|
||||
part2 = recfadeout.into();
|
||||
part2.push_back(recfadeout.into());
|
||||
|
||||
// fade in the outro
|
||||
let outrofade = "outrofade";
|
||||
|
@ -335,32 +403,8 @@ impl<'a> Renderer<'a> {
|
|||
});
|
||||
part3 = outrofade.into();
|
||||
|
||||
// prepare list for concat
|
||||
let mut parts = VecDeque::new();
|
||||
parts.push_back(part2);
|
||||
|
||||
// fast-forward the requested parts in reverse order
|
||||
project.source.fast.sort();
|
||||
for (i, (ff_st, ff_end)) in project.source.fast.iter().rev().enumerate() {
|
||||
let recff = [
|
||||
format!("recff{i}b").into(),
|
||||
format!("recff{i}ff").into(),
|
||||
format!("recff{i}a").into()
|
||||
];
|
||||
ffmpeg.add_filter(Filter::FastForward {
|
||||
input: parts.pop_front().unwrap(),
|
||||
ffinput: ff.clone().into(),
|
||||
start: *ff_st - start,
|
||||
duration: *ff_end - *ff_st,
|
||||
multiplier: FF_MULTIPLIER,
|
||||
output: recff.clone()
|
||||
});
|
||||
parts.push_front(recff[2].clone());
|
||||
parts.push_front(recff[1].clone());
|
||||
parts.push_front(recff[0].clone());
|
||||
}
|
||||
|
||||
// concatenate everything
|
||||
let mut parts = part2;
|
||||
parts.push_front(part1);
|
||||
parts.push_back(part3);
|
||||
let concat = "concat";
|
||||
|
|
Loading…
Reference in a new issue