add filtering

This commit is contained in:
jb-alvarado 2022-02-20 22:15:34 +01:00
parent 5d607cabe0
commit 5083c62ec7
3 changed files with 278 additions and 21 deletions

View File

@ -1,10 +1,237 @@
use crate::utils::MediaProbe;
use serde::{Deserialize, Serialize};
use std::path::Path;
fn deinterlace(probe: MediaProbe) -> String {
if probe.video_streams.unwrap()[0].field_order.is_some()
&& probe.video_streams.unwrap()[0].field_order.unwrap() != "progressive".to_string() {
"yadif=0:-1:0"
use crate::utils::{is_close, Config, Program};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Filters {
pub audio_chain: Option<String>,
pub video_chain: Option<String>,
pub audio_map: Option<String>,
pub video_map: Option<String>,
}
impl Filters {
pub fn new() -> Self {
Filters {
audio_chain: None,
video_chain: None,
audio_map: Some("0:a".to_string()),
video_map: Some("0:v".to_string()),
}
}
""
fn add_filter(&mut self, filter: String, codec_type: String) {
match codec_type.as_str() {
"audio" => match &self.audio_chain {
Some(ac) => {
if filter.starts_with(";") || filter.starts_with("[") {
self.audio_chain = Some(format!("{}{}", ac, filter))
} else {
self.audio_chain = Some(format!("{},{}", ac, filter))
}
}
None => {
self.audio_chain = Some(filter);
self.audio_map = Some("[aout1]".to_string());
}
},
"video" => match &self.video_chain {
Some(vc) => {
if filter.starts_with(";") || filter.starts_with("[") {
self.video_chain = Some(format!("{}{}", vc, filter))
} else {
self.video_chain = Some(format!("{},{}", vc, filter))
}
}
None => {
self.video_chain = Some(filter);
self.video_map = Some("[vout1]".to_string());
}
},
_ => (),
}
}
}
fn deinterlace(field_order: Option<String>, chain: &mut Filters) {
if field_order.is_some() && field_order.unwrap() != "progressive".to_string() {
chain.add_filter("yadif=0:-1:0".into(), "video".into())
}
}
fn pad(aspect: f64, chain: &mut Filters, config: &Config) {
if is_close(aspect, config.processing.aspect, 0.03) {
if aspect < config.processing.aspect {
chain.add_filter(
format!(
"pad=ih*{}/{}/sar:ih:(ow-iw)/2:(oh-ih)/2",
config.processing.width, config.processing.height
)
.into(),
"video".into(),
)
} else if aspect > config.processing.aspect {
chain.add_filter(
format!(
"pad=iw:iw*{}/{}/sar:(ow-iw)/2:(oh-ih)/2",
config.processing.width, config.processing.height
)
.into(),
"video".into(),
)
}
}
}
fn fps(fps: f64, chain: &mut Filters, config: &Config) {
if fps != config.processing.fps {
chain.add_filter(
format!("fps={}", config.processing.fps).into(),
"video".into(),
)
}
}
fn scale(width: i64, height: i64, aspect: f64, chain: &mut Filters, config: &Config) {
if width != config.processing.width || height != config.processing.height {
chain.add_filter(
format!(
"scale={}:{}",
config.processing.width, config.processing.height
)
.into(),
"video".into(),
)
}
if is_close(aspect, config.processing.aspect, 0.03) {
chain.add_filter(
format!("setdar=dar={}", config.processing.aspect).into(),
"video".into(),
)
}
}
fn fade(node: &mut Program, chain: &mut Filters, codec_type: String) {
let mut t = "".to_string();
if codec_type == "audio".to_string() {
t = "a".to_string()
}
if node.seek > 0.0 {
chain.add_filter(format!("{t}fade=in:st=0:d=0.5"), codec_type.clone())
}
if node.out != node.duration && node.out - node.seek - 1.0 > 0.0 {
chain.add_filter(
format!("{t}fade=out:st={}:d=1.0", (node.out - node.seek - 1.0)).into(),
codec_type,
)
}
}
fn overlay(node: &mut Program, chain: &mut Filters, config: &Config, last_ad: bool, next_ad: bool) {
if config.processing.add_logo
&& Path::new(&config.processing.logo).is_file()
&& node.category != "advertisement".to_string()
{
let opacity = format!(
"format=rgba,colorchannelmixer=aa={}",
config.processing.logo_opacity
);
let logo_loop = "loop=loop=-1:size=1:start=0";
let mut logo_chain = format!("[v];movie={},{logo_loop},{opacity}", config.processing.logo);
if last_ad {
logo_chain.push_str(",fade=in:st=0:d=1.0:alpha=1")
}
if next_ad {
logo_chain.push_str(
format!(",fade=out:st={}:d=1.0:alpha=1", node.out - node.seek - 1.0).as_str(),
)
}
logo_chain
.push_str(format!("[l];[v][l]{}:shortest=1", config.processing.logo_filter).as_str());
chain.add_filter(logo_chain, "video".into());
}
}
pub fn filter_chains(node: &mut Program, config: &Config, last: bool, next: bool) -> Vec<String> {
let mut filters = Filters::new();
let probe = node.probe.clone();
match probe {
Some(p) => {
// let a_stream = &p.audio_streams.unwrap()[0];
let v_stream = &p.video_streams.unwrap()[0];
let aspect_string = v_stream.display_aspect_ratio.clone().unwrap();
let aspect_vec: Vec<&str> = aspect_string.split(':').collect();
let w: f64 = aspect_vec[0].parse().unwrap();
let h: f64 = aspect_vec[1].parse().unwrap();
let source_aspect: f64 = w as f64 / h as f64;
let frame_rate_vec: Vec<&str> = v_stream.r_frame_rate.split('/').collect();
let rate: f64 = frame_rate_vec[0].parse().unwrap();
let factor: f64 = frame_rate_vec[1].parse().unwrap();
let frames_per_second: f64 = rate / factor;
deinterlace(v_stream.field_order.clone(), &mut filters);
pad(source_aspect, &mut filters, &config);
fps(frames_per_second, &mut filters, &config);
scale(
v_stream.width.unwrap(),
v_stream.height.unwrap(),
source_aspect,
&mut filters,
&config,
);
fade(node, &mut filters, "audio".into());
fade(node, &mut filters, "video".into());
overlay(node, &mut filters, &config, last, next);
}
None => {
println!("Clip has no media probe object. No filter applied!")
}
}
let mut filter_cmd = vec!["-filter_complex".to_string()];
let mut filter_str: String = "".to_string();
let mut filter_map: Vec<String> = vec![];
if filters.audio_chain.is_some() {
filter_str.push_str(
format!(
"[0:a]{}{};",
filters.audio_chain.unwrap(),
filters.audio_map.clone().unwrap()
)
.as_str(),
);
filter_map.append(&mut vec!["-map".to_string(), filters.audio_map.unwrap()]);
} else {
filter_map.append(&mut vec!["-map".to_string(), "0:a".to_string()]);
}
if filters.video_chain.is_some() {
filter_str.push_str(
format!(
"[0:v]{}{}",
filters.video_chain.unwrap(),
filters.video_map.clone().unwrap()
)
.as_str(),
);
filter_map.append(&mut vec!["-map".to_string(), filters.video_map.unwrap()]);
} else {
filter_map.append(&mut vec!["-map".to_string(), "0:v".to_string()]);
}
filter_cmd.push(filter_str);
filter_cmd.append(&mut filter_map);
filter_cmd
}

View File

@ -4,11 +4,11 @@ use std::{
process::{Command, Stdio},
};
use crate::utils::{program, sec_to_time};
use crate::utils::{program, sec_to_time, Config};
pub fn play(settings: Option<Vec<String>>) -> io::Result<()> {
let get_source = program();
let dec_settings = settings.unwrap();
pub fn play(config: Config) -> io::Result<()> {
let get_source = program(config.clone());
let dec_settings = config.processing.settings.unwrap();
let mut enc_proc = Command::new("ffplay")
.args([
@ -29,9 +29,10 @@ pub fn play(settings: Option<Vec<String>>) -> io::Result<()> {
if let Some(mut enc_input) = enc_proc.stdin.take() {
for node in get_source {
println!("Play: {:#?}", node);
// println!("Play: {:#?}", node);
println!("Node begin: {:?}", sec_to_time(node.begin.unwrap()));
let cmd = node.cmd.unwrap();
let filter = node.filter.unwrap();
let mut dec_cmd = vec![
"-v",
@ -41,6 +42,7 @@ pub fn play(settings: Option<Vec<String>>) -> io::Result<()> {
];
dec_cmd.append(&mut cmd.iter().map(String::as_str).collect());
dec_cmd.append(&mut filter.iter().map(String::as_str).collect());
dec_cmd.append(&mut dec_settings.iter().map(String::as_str).collect());
let mut dec_proc = Command::new("ffmpeg")

View File

@ -1,10 +1,12 @@
use crate::utils::{
get_config,
json_reader::{read_json, Program},
modified_time, MediaProbe,
modified_time, Config, MediaProbe,
};
use crate::filter::filter_chains;
pub struct CurrentProgram {
config: Config,
json_mod: String,
json_path: String,
nodes: Vec<Program>,
@ -12,11 +14,11 @@ pub struct CurrentProgram {
}
impl CurrentProgram {
fn new() -> Self {
let config = get_config();
fn new(config: Config) -> Self {
let json = read_json(&config, true);
Self {
config: config,
json_mod: json.modified.unwrap(),
json_path: json.current_file.unwrap(),
nodes: json.program.into(),
@ -25,12 +27,11 @@ impl CurrentProgram {
}
fn check_update(&mut self) {
let config = get_config();
let mod_time = modified_time(self.json_path.clone());
if !mod_time.unwrap().to_string().eq(&self.json_mod) {
// when playlist has changed, reload it
let json = read_json(&config, true);
let json = read_json(&self.config, true);
self.json_mod = json.modified.unwrap();
self.nodes = json.program.into();
@ -40,6 +41,10 @@ impl CurrentProgram {
fn append_probe(&mut self, node: &mut Program) {
node.probe = Some(MediaProbe::new(node.source.clone()))
}
fn add_filter(&mut self, node: &mut Program, last: bool, next: bool) {
node.filter = Some(filter_chains(node, &self.config, last, next));
}
}
impl Iterator for CurrentProgram {
@ -49,14 +54,32 @@ impl Iterator for CurrentProgram {
if self.idx < self.nodes.len() {
self.check_update();
let mut current = self.nodes[self.idx].clone();
let mut last = false;
let mut next = false;
if self.idx > 0 && self.nodes[self.idx - 1].category == "advertisement" {
last = true
}
self.idx += 1;
if self.idx <= self.nodes.len() - 1 && self.nodes[self.idx].category == "advertisement" {
next = true
}
self.append_probe(&mut current);
self.add_filter(&mut current, last, next);
Some(current)
} else {
let config = get_config();
let json = read_json(&config, false);
let mut last = false;
let mut next = false;
if self.nodes[self.idx - 1].category == "advertisement" {
last = true
}
let json = read_json(&self.config, false);
self.json_mod = json.modified.unwrap();
self.json_path = json.current_file.unwrap();
self.nodes = json.program.into();
@ -64,13 +87,18 @@ impl Iterator for CurrentProgram {
let mut current = self.nodes[0].clone();
if self.nodes[self.idx].category == "advertisement" {
next = true
}
self.append_probe(&mut current);
self.add_filter(&mut current, last, next);
Some(current)
}
}
}
pub fn program() -> CurrentProgram {
CurrentProgram::new()
pub fn program(config: Config) -> CurrentProgram {
CurrentProgram::new(config)
}