use ffmpeg_common::{utils, Result};
use std::fmt;
#[derive(Debug, Clone)]
pub struct VideoFilter {
name: String,
params: Vec<(String, String)>,
}
impl VideoFilter {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
params: Vec::new(),
}
}
pub fn param(mut self, key: impl Into<String>, value: impl ToString) -> Self {
self.params.push((key.into(), value.to_string()));
self
}
pub fn scale(width: i32, height: i32) -> Self {
Self::new("scale")
.param("w", width)
.param("h", height)
}
pub fn scale_aspect(width: i32) -> Self {
Self::new("scale")
.param("w", width)
.param("h", -1)
}
pub fn crop(width: u32, height: u32, x: u32, y: u32) -> Self {
Self::new("crop")
.param("w", width)
.param("h", height)
.param("x", x)
.param("y", y)
}
pub fn pad(width: u32, height: u32) -> Self {
Self::new("pad")
.param("width", width)
.param("height", height)
.param("x", "(ow-iw)/2")
.param("y", "(oh-ih)/2")
}
pub fn rotate(angle: f64) -> Self {
Self::new("rotate").param("angle", angle)
}
pub fn transpose(direction: TransposeDirection) -> Self {
Self::new("transpose").param("dir", direction as u8)
}
pub fn hflip() -> Self {
Self::new("hflip")
}
pub fn vflip() -> Self {
Self::new("vflip")
}
pub fn fps(framerate: f64) -> Self {
Self::new("fps").param("fps", framerate)
}
pub fn deinterlace() -> Self {
Self::new("yadif")
}
pub fn denoise(strength: f64) -> Self {
Self::new("hqdn3d").param("luma_spatial", strength)
}
pub fn sharpen() -> Self {
Self::new("unsharp")
}
pub fn blur(radius: u32) -> Self {
Self::new("boxblur").param("lr", radius).param("lp", 1)
}
pub fn overlay(x: impl Into<String>, y: impl Into<String>) -> Self {
Self::new("overlay")
.param("x", x.into())
.param("y", y.into())
}
pub fn drawtext(text: impl Into<String>) -> Self {
Self::new("drawtext")
.param("text", utils::escape_filter_string(&text.into()))
}
pub fn fade_in(duration: f64) -> Self {
Self::new("fade")
.param("type", "in")
.param("duration", duration)
}
pub fn fade_out(duration: f64, start_time: f64) -> Self {
Self::new("fade")
.param("type", "out")
.param("duration", duration)
.param("start_time", start_time)
}
pub fn setpts(expr: impl Into<String>) -> Self {
Self::new("setpts").param("expr", expr.into())
}
pub fn select(expr: impl Into<String>) -> Self {
Self::new("select").param("expr", expr.into())
}
pub fn eq() -> Self {
Self::new("eq")
}
pub fn brightness(mut self, value: f64) -> Self {
if self.name == "eq" {
self.params.push(("brightness".to_string(), value.to_string()));
}
self
}
pub fn contrast(mut self, value: f64) -> Self {
if self.name == "eq" {
self.params.push(("contrast".to_string(), value.to_string()));
}
self
}
pub fn saturation(mut self, value: f64) -> Self {
if self.name == "eq" {
self.params.push(("saturation".to_string(), value.to_string()));
}
self
}
pub fn format(pix_fmt: impl Into<String>) -> Self {
Self::new("format").param("pix_fmts", pix_fmt.into())
}
}
impl fmt::Display for VideoFilter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)?;
if !self.params.is_empty() {
write!(f, "=")?;
let params: Vec<String> = self.params
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect();
write!(f, "{}", params.join(":"))?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct AudioFilter {
name: String,
params: Vec<(String, String)>,
}
impl AudioFilter {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
params: Vec::new(),
}
}
pub fn param(mut self, key: impl Into<String>, value: impl ToString) -> Self {
self.params.push((key.into(), value.to_string()));
self
}
pub fn volume(level: f64) -> Self {
Self::new("volume").param("volume", level)
}
pub fn loudnorm() -> Self {
Self::new("loudnorm")
.param("I", -16)
.param("TP", -1.5)
.param("LRA", 11)
}
pub fn dynaudnorm() -> Self {
Self::new("dynaudnorm")
}
pub fn highpass(frequency: u32) -> Self {
Self::new("highpass").param("f", frequency)
}
pub fn lowpass(frequency: u32) -> Self {
Self::new("lowpass").param("f", frequency)
}
pub fn afade_in(duration: f64) -> Self {
Self::new("afade")
.param("type", "in")
.param("duration", duration)
}
pub fn afade_out(duration: f64, start_time: f64) -> Self {
Self::new("afade")
.param("type", "out")
.param("duration", duration)
.param("start_time", start_time)
}
pub fn aresample(sample_rate: u32) -> Self {
Self::new("aresample").param("sample_rate", sample_rate)
}
pub fn atempo(tempo: f64) -> Self {
Self::new("atempo").param("tempo", tempo)
}
pub fn adelay(delays: impl Into<String>) -> Self {
Self::new("adelay").param("delays", delays.into())
}
pub fn aecho(delays: impl Into<String>, decays: impl Into<String>) -> Self {
Self::new("aecho")
.param("delays", delays.into())
.param("decays", decays.into())
}
pub fn acompressor() -> Self {
Self::new("acompressor")
}
pub fn alimiter() -> Self {
Self::new("alimiter")
}
pub fn agate() -> Self {
Self::new("agate")
}
pub fn anequalizer(frequency: u32, width: f64, gain: f64) -> Self {
Self::new("anequalizer")
.param("frequency", frequency)
.param("width", width)
.param("gain", gain)
}
pub fn channelmap(map: impl Into<String>) -> Self {
Self::new("channelmap").param("map", map.into())
}
pub fn amerge() -> Self {
Self::new("amerge")
}
pub fn channelsplit() -> Self {
Self::new("channelsplit")
}
}
impl fmt::Display for AudioFilter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)?;
if !self.params.is_empty() {
write!(f, "=")?;
let params: Vec<String> = self.params
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect();
write!(f, "{}", params.join(":"))?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum TransposeDirection {
CounterClockwiseFlip = 0,
Clockwise = 1,
CounterClockwise = 2,
ClockwiseFlip = 3,
}
#[derive(Debug, Clone, Default)]
pub struct FilterGraph {
nodes: Vec<FilterNode>,
edges: Vec<FilterEdge>,
}
#[derive(Debug, Clone)]
struct FilterNode {
id: String,
filter: String,
inputs: Vec<String>,
outputs: Vec<String>,
}
#[derive(Debug, Clone)]
struct FilterEdge {
from: String,
to: String,
}
impl FilterGraph {
pub fn new() -> Self {
Self::default()
}
pub fn add_filter(
mut self,
filter: impl Into<String>,
inputs: Vec<String>,
outputs: Vec<String>,
) -> Self {
let id = format!("f{}", self.nodes.len());
self.nodes.push(FilterNode {
id: id.clone(),
filter: filter.into(),
inputs,
outputs,
});
self
}
pub fn connect(mut self, from: impl Into<String>, to: impl Into<String>) -> Self {
self.edges.push(FilterEdge {
from: from.into(),
to: to.into(),
});
self
}
pub fn build(&self) -> String {
let mut parts = Vec::new();
for node in &self.nodes {
let mut part = String::new();
if !node.inputs.is_empty() {
part.push_str(&node.inputs.join(""));
}
part.push_str(&node.filter);
if !node.outputs.is_empty() {
part.push_str(&node.outputs.join(""));
}
parts.push(part);
}
parts.join(";")
}
}
pub mod chains {
use super::*;
pub fn thumbnail() -> Vec<VideoFilter> {
vec![
VideoFilter::select("eq(pict_type\\,I)"),
VideoFilter::scale(320, -1),
]
}
pub fn gif_optimize(width: u32, fps: f64) -> Vec<VideoFilter> {
vec![
VideoFilter::fps(fps),
VideoFilter::scale(width as i32, -1),
VideoFilter::new("split").param("outputs", 2),
VideoFilter::new("palettegen"),
VideoFilter::new("paletteuse"),
]
}
pub fn stabilize() -> Vec<VideoFilter> {
vec![
VideoFilter::new("vidstabdetect").param("shakiness", 5),
VideoFilter::new("vidstabtransform"),
]
}
pub fn cinematic() -> Vec<VideoFilter> {
vec![
VideoFilter::eq()
.contrast(1.2)
.brightness(-0.05)
.saturation(0.8),
VideoFilter::new("curves")
.param("preset", "vintage"),
VideoFilter::new("vignette"),
]
}
pub fn upscale_enhance(scale: u32) -> Vec<VideoFilter> {
vec![
VideoFilter::scale(scale as i32, -1),
VideoFilter::sharpen(),
VideoFilter::new("hqdn3d").param("luma_spatial", 4),
]
}
pub fn audio_master() -> Vec<AudioFilter> {
vec![
AudioFilter::highpass(80),
AudioFilter::acompressor()
.param("threshold", -20)
.param("ratio", 4)
.param("attack", 5)
.param("release", 50),
AudioFilter::anequalizer(100, 100.0, 2.0),
AudioFilter::anequalizer(1000, 500.0, -1.0),
AudioFilter::anequalizer(10000, 2000.0, 1.0),
AudioFilter::alimiter()
.param("limit", -0.5),
AudioFilter::loudnorm(),
]
}
pub fn podcast_audio() -> Vec<AudioFilter> {
vec![
AudioFilter::highpass(100),
AudioFilter::lowpass(15000),
AudioFilter::agate()
.param("threshold", -35)
.param("range", -40),
AudioFilter::acompressor()
.param("threshold", -15)
.param("ratio", 3),
AudioFilter::loudnorm(),
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_video_filters() {
let scale = VideoFilter::scale(1920, 1080);
assert_eq!(scale.to_string(), "scale=w=1920:h=1080");
let crop = VideoFilter::crop(640, 480, 100, 50);
assert_eq!(crop.to_string(), "crop=w=640:h=480:x=100:y=50");
let text = VideoFilter::drawtext("Hello, World!");
assert!(text.to_string().contains("drawtext=text="));
}
#[test]
fn test_audio_filters() {
let volume = AudioFilter::volume(0.5);
assert_eq!(volume.to_string(), "volume=volume=0.5");
let tempo = AudioFilter::atempo(1.5);
assert_eq!(tempo.to_string(), "atempo=tempo=1.5");
}
#[test]
fn test_filter_graph() {
let graph = FilterGraph::new()
.add_filter("scale=640:480", vec!["[0:v]".to_string()], vec!["[scaled]".to_string()])
.add_filter("overlay", vec!["[scaled]".to_string(), "[1:v]".to_string()], vec!["[out]".to_string()]);
let result = graph.build();
assert!(result.contains("[0:v]scale=640:480[scaled]"));
assert!(result.contains("[scaled][1:v]overlay[out]"));
}
}