diff --git a/src/render/filter.rs b/src/render/filter.rs index a846c7d..6a76ccc 100644 --- a/src/render/filter.rs +++ b/src/render/filter.rs @@ -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); } } diff --git a/src/render/mod.rs b/src/render/mod.rs index ec0677c..1c963e0 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -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";