From a0f1caa34b6d037fa690b82600a38a56f5eebb43 Mon Sep 17 00:00:00 2001 From: Dominic <git@msrd0.de> Date: Mon, 30 Oct 2023 16:05:21 +0100 Subject: [PATCH] more filter logic --- src/main.rs | 3 +- src/render/ffmpeg.rs | 72 +++------------- src/render/filter.rs | 200 +++++++++++++++++++++++++++++++++++++++++++ src/render/mod.rs | 1 + 4 files changed, 216 insertions(+), 60 deletions(-) create mode 100644 src/render/filter.rs diff --git a/src/main.rs b/src/main.rs index a9ea623..870fdcd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#![allow(clippy::manual_range_contains)] #![warn(rust_2018_idioms)] #![forbid(elided_lifetimes_in_paths, unsafe_code)] @@ -27,7 +28,7 @@ struct Args { course: String } -#[allow(non_camel_case_types)] +#[allow(non_camel_case_types, clippy::upper_case_acronyms)] #[derive(Clone, Copy, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] enum Resolution { /// 640x360 diff --git a/src/render/ffmpeg.rs b/src/render/ffmpeg.rs index 70e5dba..1202825 100644 --- a/src/render/ffmpeg.rs +++ b/src/render/ffmpeg.rs @@ -1,4 +1,4 @@ -use super::cmd; +use super::{cmd, filter::Filter}; use crate::time::{format_time, Time}; use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf}; use rational::Rational; @@ -45,66 +45,12 @@ impl FfmpegInput { } } -pub(crate) enum Filter { - Concat { - inputs: Vec<Cow<'static, str>>, - n: usize, - output: Cow<'static, str> - }, - - FadeIn { - input: Cow<'static, str>, - start: Time, - duration: Time, - output: Cow<'static, str> - }, - - FadeOut { - input: Cow<'static, str>, - start: Time, - duration: Time, - output: Cow<'static, str> - }, - - Overlay { - video_input: Cow<'static, str>, - overlay_input: Cow<'static, str>, - x: Cow<'static, str>, - y: Cow<'static, str>, - output: Cow<'static, str> - }, - - GenerateSilence { - output: Cow<'static, str> - } -} - -impl Filter { - fn is_video_filter(&self) -> bool { - matches!( - self, - Self::Concat { .. } - | Self::FadeIn { .. } - | Self::FadeOut { .. } - | Self::Overlay { .. } - ) - } - - fn is_audio_filter(&self) -> bool { - matches!( - self, - Self::Concat { .. } - | Self::FadeIn { .. } - | Self::FadeOut { .. } - | Self::GenerateSilence { .. } - ) - } -} - pub(crate) struct Ffmpeg { inputs: Vec<FfmpegInput>, filters: Vec<Filter>, - output: PathBuf + output: PathBuf, + + filter_idx: usize } impl Ffmpeg { @@ -112,7 +58,9 @@ impl Ffmpeg { Self { inputs: Vec::new(), filters: Vec::new(), - output + output, + + filter_idx: 0 } } @@ -152,6 +100,12 @@ impl Ffmpeg { } else { cmd.arg("-c:v").arg("copy"); } + if aenc { + cmd.arg("-c:a").arg("aac"); + cmd.arg("-b:a").arg("128000"); + } else { + cmd.arg("-c:a").arg("copy"); + } unimplemented!() } diff --git a/src/render/filter.rs b/src/render/filter.rs new file mode 100644 index 0000000..fc97e38 --- /dev/null +++ b/src/render/filter.rs @@ -0,0 +1,200 @@ +use crate::time::{format_time, Time}; +use std::{borrow::Cow, fmt::Write as _}; + +pub(crate) enum Filter { + /// Trim audio and video alike + Trim { + input: Cow<'static, str>, + start: Option<Time>, + duration: Option<Time>, + output: Cow<'static, str> + }, + + /// Apply an alpha channel on the video. No audio. + Alpha { + input: Cow<'static, str>, + alpha: f32, + output: Cow<'static, str> + }, + + /// Overlay the one video over the other. The audio is copied. + Overlay { + video_input: Cow<'static, str>, + overlay_input: Cow<'static, str>, + x: Cow<'static, str>, + y: Cow<'static, str>, + output: Cow<'static, str> + }, + + /// Concatenate audio and video. + Concat { + inputs: Vec<Cow<'static, str>>, + n: usize, + output: Cow<'static, str> + }, + + /// Fade audio and video. + Fade { + input: Cow<'static, str>, + direction: &'static str, + start: Time, + duration: Time, + output: Cow<'static, str> + }, + + /// Generate silence. The video is copied. + GenerateSilence { + video: Cow<'static, str>, + output: Cow<'static, str> + } +} + +impl Filter { + pub(crate) fn is_video_filter(&self) -> bool { + matches!( + self, + Self::Trim { .. } + | Self::Alpha { .. } + | Self::Concat { .. } + | Self::Fade { .. } + | Self::Overlay { .. } + ) + } + + pub(crate) fn is_audio_filter(&self) -> bool { + matches!( + self, + Self::Trim { .. } + | Self::Concat { .. } + | Self::Fade { .. } + | Self::GenerateSilence { .. } + ) + } + + fn append_to_complex_filter(&self, complex: &mut String, filter_idx: &mut usize) { + match self { + Self::Trim { + input, + start, + duration, + output + } => { + let mut args = String::new(); + if let Some(start) = start { + write!(args, "start={}", format_time(*start)); + } + if let Some(duration) = duration { + if !args.is_empty() { + args += ":"; + } + write!(args, "duration={}", format_time(*duration)); + } + writeln!( + complex, + "{}trim={args},setpts=PTS-STARTPTS{};", + channel('v', input), + channel('v', output) + ); + writeln!( + complex, + "{}atrim={args},asetpts=PTS-STARTPTS{};", + channel('a', input), + channel('a', output) + ); + }, + + Self::Alpha { + input, + alpha, + output + } => { + writeln!( + complex, + "{}format=yuva444p,colorchannelmixer=aa={alpha}{};", + channel('v', input), + channel('v', output) + ); + }, + + Self::Overlay { + video_input, + overlay_input, + x, + y, + output + } => { + writeln!( + complex, + "{}{}overlay=x={x}:y={y}{};", + channel('v', video_input), + channel('v', overlay_input), + channel('v', output) + ); + writeln!( + complex, + "{}anull{};", + channel('a', video_input), + channel('a', output) + ); + }, + + Self::Concat { inputs, n, output } => { + for i in inputs { + write!(complex, "{}{}", channel('v', i), channel('a', i)); + } + writeln!( + complex, + "concat=n={n}:v=1:a=1{}{};", + channel('v', output), + channel('a', output) + ); + }, + + Self::Fade { + input, + direction, + start, + duration, + output + } => { + let args = format!( + "{direction}:st={}:d={}", + format_time(*start), + format_time(*duration) + ); + writeln!( + complex, + "{}fade={args}{};", + channel('v', input), + channel('v', output) + ); + writeln!( + complex, + "{}afade=t={args}{};", + channel('a', input), + channel('a', output) + ); + }, + + Self::GenerateSilence { video, output } => { + writeln!( + complex, + "{}null{};", + channel('v', video), + channel('v', output) + ); + writeln!(complex, "aevalsrc=0:s=48000{};", channel('a', output)); + }, + + _ => unimplemented!() + } + } +} + +fn channel(channel: char, id: &str) -> String { + if id.chars().any(|ch| !ch.is_digit(10)) { + format!("[{channel}_{id}]") + } else { + format!("[{id}:{channel}]") + } +} diff --git a/src/render/mod.rs b/src/render/mod.rs index 843d910..f606a72 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,6 +1,7 @@ #![allow(warnings)] pub mod ffmpeg; +mod filter; use crate::{ iotro::intro, -- GitLab