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