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)]
|
||||
#![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
|
||||
|
|
|
@ -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!()
|
||||
}
|
||||
|
|
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)]
|
||||
|
||||
pub mod ffmpeg;
|
||||
mod filter;
|
||||
|
||||
use crate::{
|
||||
iotro::intro,
|
||||
|
|
Loading…
Reference in a new issue