more filter logic
This commit is contained in:
parent
fd5d3ab1fd
commit
a0f1caa34b
4 changed files with 216 additions and 60 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
#![allow(clippy::manual_range_contains)]
|
||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
#![forbid(elided_lifetimes_in_paths, unsafe_code)]
|
#![forbid(elided_lifetimes_in_paths, unsafe_code)]
|
||||||
|
|
||||||
|
@ -27,7 +28,7 @@ struct Args {
|
||||||
course: String
|
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)]
|
#[derive(Clone, Copy, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
|
||||||
enum Resolution {
|
enum Resolution {
|
||||||
/// 640x360
|
/// 640x360
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::cmd;
|
use super::{cmd, filter::Filter};
|
||||||
use crate::time::{format_time, Time};
|
use crate::time::{format_time, Time};
|
||||||
use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf};
|
use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf};
|
||||||
use rational::Rational;
|
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 {
|
pub(crate) struct Ffmpeg {
|
||||||
inputs: Vec<FfmpegInput>,
|
inputs: Vec<FfmpegInput>,
|
||||||
filters: Vec<Filter>,
|
filters: Vec<Filter>,
|
||||||
output: PathBuf
|
output: PathBuf,
|
||||||
|
|
||||||
|
filter_idx: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ffmpeg {
|
impl Ffmpeg {
|
||||||
|
@ -112,7 +58,9 @@ impl Ffmpeg {
|
||||||
Self {
|
Self {
|
||||||
inputs: Vec::new(),
|
inputs: Vec::new(),
|
||||||
filters: Vec::new(),
|
filters: Vec::new(),
|
||||||
output
|
output,
|
||||||
|
|
||||||
|
filter_idx: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,6 +100,12 @@ impl Ffmpeg {
|
||||||
} else {
|
} else {
|
||||||
cmd.arg("-c:v").arg("copy");
|
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!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
200
src/render/filter.rs
Normal file
200
src/render/filter.rs
Normal file
|
@ -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}]")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
#![allow(warnings)]
|
#![allow(warnings)]
|
||||||
|
|
||||||
pub mod ffmpeg;
|
pub mod ffmpeg;
|
||||||
|
mod filter;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
iotro::intro,
|
iotro::intro,
|
||||||
|
|
Loading…
Reference in a new issue