diff --git a/Cargo.lock b/Cargo.lock index faacf2db..e02d39d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -817,9 +817,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f39818dcfc97d45b03953c1292efc4e80954e1583c4aa770bac1383e2310a4" +checksum = "3f83d0ebf42c6eafb8d7c52f7e5f2d3003b89c7aa4fd2b79229209459a849af8" dependencies = [ "cc", "cxxbridge-flags", @@ -829,9 +829,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e580d70777c116df50c390d1211993f62d40302881e54d4b79727acb83d0199" +checksum = "07d050484b55975889284352b0ffc2ecbda25c0c55978017c132b29ba0818a86" dependencies = [ "cc", "codespan-reporting", @@ -844,15 +844,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56a46460b88d1cec95112c8c363f0e2c39afdb237f60583b0b36343bf627ea9c" +checksum = "99d2199b00553eda8012dfec8d3b1c75fce747cf27c169a270b3b99e3448ab78" [[package]] name = "cxxbridge-macro" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747b608fecf06b0d72d440f27acc99288207324b793be2c17991839f3d4995ea" +checksum = "dcb67a6de1f602736dd7eaead0080cf3435df806c61b24b13328db128c58868f" dependencies = [ "proc-macro2", "quote", @@ -1070,9 +1070,9 @@ dependencies = [ [[package]] name = "file-rotate" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9135a7f0c84014f3e46849b952f24725ce809ab8ab273ba67089650992ede7" +checksum = "f549a61332cfe588f485923f46bfb2c273b0a3634b379cec00acb1d2236a3acb" dependencies = [ "chrono", "flate2", @@ -1503,9 +1503,9 @@ dependencies = [ [[package]] name = "iana-time-zone-haiku" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fde6edd6cef363e9359ed3c98ba64590ba9eecba2293eb5a723ab32aee8926aa" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" dependencies = [ "cxx", "cxx-build", @@ -2264,9 +2264,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] diff --git a/docs/multi_audio.md b/docs/multi_audio.md index c768e39f..b1c65c46 100644 --- a/docs/multi_audio.md +++ b/docs/multi_audio.md @@ -31,7 +31,7 @@ out: srt://127.0.0.1:40051 -map 0:v -map 0:a:0 - -map 0:a:1 + -map 0:a:1 -s 512x288 -c:v libx264 -c:a aac diff --git a/ffplayout-engine/src/input/ingest.rs b/ffplayout-engine/src/input/ingest.rs index 5674b941..28e01f4e 100644 --- a/ffplayout-engine/src/input/ingest.rs +++ b/ffplayout-engine/src/input/ingest.rs @@ -2,16 +2,15 @@ use std::{ io::{BufRead, BufReader, Error, Read}, process::{exit, ChildStderr, Command, Stdio}, sync::atomic::Ordering, - sync::{Arc, Mutex}, thread, }; use crossbeam_channel::Sender; use simplelog::*; -use ffplayout_lib::filter::filter_chains; use ffplayout_lib::utils::{ - format_log_line, test_tcp_port, Ingest, Media, PlayoutConfig, ProcessControl, + controller::ProcessUnit::*, format_log_line, test_tcp_port, Media, PlayoutConfig, + ProcessControl, }; use ffplayout_lib::vec_strings; @@ -85,11 +84,15 @@ pub fn ingest_server( let mut server_cmd = vec_strings!["-hide_banner", "-nostats", "-v", "level+info"]; let stream_input = config.ingest.input_cmd.clone().unwrap(); let mut dummy_media = Media::new(0, "Live Stream", false); - dummy_media.is_live = Some(true); - let mut filters = filter_chains(&config, &mut dummy_media, &Arc::new(Mutex::new(vec![]))); + dummy_media.unit = Ingest; + dummy_media.add_filter(&config, &None); server_cmd.append(&mut stream_input.clone()); - server_cmd.append(&mut filters); + + if let Some(mut filter) = dummy_media.filter { + server_cmd.append(&mut filter.cmd); + } + server_cmd.append(&mut config.processing.settings.unwrap()); let mut is_running; diff --git a/ffplayout-engine/src/input/playlist.rs b/ffplayout-engine/src/input/playlist.rs index cdd5cd8a..94f8f99e 100644 --- a/ffplayout-engine/src/input/playlist.rs +++ b/ffplayout-engine/src/input/playlist.rs @@ -457,7 +457,7 @@ fn timed_source( pub fn gen_source( config: &PlayoutConfig, mut node: Media, - filter_chain: &Arc>>, + filter_chain: &Option>>>, ) -> Media { let duration = node.out - node.seek; @@ -534,7 +534,7 @@ pub fn gen_source( fn handle_list_init( config: &PlayoutConfig, mut node: Media, - filter_chain: &Arc>>, + filter_chain: &Option>>>, ) -> Media { debug!("Playlist init"); let (_, total_delta) = get_delta(config, &node.begin.unwrap()); @@ -555,7 +555,7 @@ fn handle_list_end( config: &PlayoutConfig, mut node: Media, total_delta: f64, - filter_chain: &Arc>>, + filter_chain: &Option>>>, ) -> Media { debug!("Playlist end"); diff --git a/ffplayout-engine/src/output/desktop.rs b/ffplayout-engine/src/output/desktop.rs index 1bf1f772..80180292 100644 --- a/ffplayout-engine/src/output/desktop.rs +++ b/ffplayout-engine/src/output/desktop.rs @@ -1,7 +1,4 @@ -use std::{ - process::{self, Command, Stdio}, - sync::{Arc, Mutex}, -}; +use std::process::{self, Command, Stdio}; use simplelog::*; @@ -25,9 +22,7 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child { ); let mut filter: String = "null,".to_string(); - filter.push_str( - v_drawtext::filter_node(config, None, &Arc::new(Mutex::new(vec![]))).as_str(), - ); + filter.push_str(v_drawtext::filter_node(config, None, &None).as_str()); enc_filter = vec!["-vf".to_string(), filter]; } } diff --git a/ffplayout-engine/src/output/hls.rs b/ffplayout-engine/src/output/hls.rs index 1d1e5f9d..fead9b6e 100644 --- a/ffplayout-engine/src/output/hls.rs +++ b/ffplayout-engine/src/output/hls.rs @@ -29,12 +29,13 @@ use simplelog::*; use crate::input::{ingest::log_line, source_generator}; use crate::utils::prepare_output_cmd; -use ffplayout_lib::filter::filter_chains; -use ffplayout_lib::utils::{ - sec_to_time, stderr_reader, test_tcp_port, Encoder, Ingest, Media, PlayerControl, - PlayoutConfig, PlayoutStatus, ProcessControl, +use ffplayout_lib::{ + utils::{ + controller::ProcessUnit::*, sec_to_time, stderr_reader, test_tcp_port, Media, + PlayerControl, PlayoutConfig, PlayoutStatus, ProcessControl, + }, + vec_strings, }; -use ffplayout_lib::vec_strings; /// Ingest Server for HLS fn ingest_to_hls_server( @@ -49,7 +50,7 @@ fn ingest_to_hls_server( let stream_input = config.ingest.input_cmd.clone().unwrap(); server_prefix.append(&mut stream_input.clone()); let mut dummy_media = Media::new(0, "Live Stream", false); - dummy_media.is_live = Some(true); + dummy_media.unit = Ingest; let mut is_running; @@ -63,8 +64,8 @@ fn ingest_to_hls_server( }; loop { - let filters = filter_chains(&config, &mut dummy_media, &playout_stat.chain); - let server_cmd = prepare_output_cmd(server_prefix.clone(), filters, &config); + dummy_media.add_filter(&config, &playout_stat.chain); + let server_cmd = prepare_output_cmd(server_prefix.clone(), &dummy_media.filter, &config); debug!( "Server CMD: \"ffmpeg {}\"", @@ -179,8 +180,7 @@ pub fn write_hls( let mut enc_prefix = vec_strings!["-hide_banner", "-nostats", "-v", &ff_log_format]; enc_prefix.append(&mut cmd); - let enc_filter = node.filter.unwrap(); - let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, config); + let enc_cmd = prepare_output_cmd(enc_prefix, &node.filter, config); debug!( "HLS writer CMD: \"ffmpeg {}\"", diff --git a/ffplayout-engine/src/output/mod.rs b/ffplayout-engine/src/output/mod.rs index e4c81714..08783a38 100644 --- a/ffplayout-engine/src/output/mod.rs +++ b/ffplayout-engine/src/output/mod.rs @@ -99,12 +99,11 @@ pub fn player( node.audio ); - let mut filter = node.filter.unwrap(); let mut dec_cmd = vec_strings!["-hide_banner", "-nostats", "-v", &ff_log_format]; dec_cmd.append(&mut cmd); - if filter.len() > 1 { - dec_cmd.append(&mut filter); + if let Some(mut filter) = node.filter { + dec_cmd.append(&mut filter.cmd); } dec_cmd.append(&mut config.processing.clone().settings.unwrap()); diff --git a/ffplayout-engine/src/output/null.rs b/ffplayout-engine/src/output/null.rs index f98f21ee..9e4f6a0e 100644 --- a/ffplayout-engine/src/output/null.rs +++ b/ffplayout-engine/src/output/null.rs @@ -1,47 +1,32 @@ -use std::{ - process::{self, Command, Stdio}, - sync::{Arc, Mutex}, -}; +use std::process::{self, Command, Stdio}; use simplelog::*; -use ffplayout_lib::{filter::v_drawtext, utils::PlayoutConfig, vec_strings}; +use crate::utils::prepare_output_cmd; +use ffplayout_lib::{ + utils::{Media, PlayoutConfig, ProcessUnit::*}, + vec_strings, +}; /// Desktop Output /// /// Instead of streaming, we run a ffplay instance and play on desktop. pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child { - let mut enc_filter: Vec = vec![]; + let mut media = Media::new(0, "", false); + media.unit = Encoder; + media.add_filter(config, &None); - let mut enc_cmd = vec_strings![ + let enc_prefix = vec_strings![ "-hide_banner", "-nostats", "-v", log_format, "-re", "-i", - "pipe:0", - "-f", - "null", - "-" + "pipe:0" ]; - if config.text.add_text && !config.text.text_from_filename { - if let Some(socket) = config.text.zmq_stream_socket.clone() { - debug!( - "Using drawtext filter, listening on address: {}", - socket - ); - - let mut filter: String = "null,".to_string(); - filter.push_str( - v_drawtext::filter_node(config, None, &Arc::new(Mutex::new(vec![]))).as_str(), - ); - enc_filter = vec!["-vf".to_string(), filter]; - } - } - - enc_cmd.splice(7..7, enc_filter); + let enc_cmd = prepare_output_cmd(enc_prefix, &media.filter, config); debug!( "Encoder CMD: \"ffmpeg {}\"", diff --git a/ffplayout-engine/src/output/stream.rs b/ffplayout-engine/src/output/stream.rs index c3ef86eb..29769c67 100644 --- a/ffplayout-engine/src/output/stream.rs +++ b/ffplayout-engine/src/output/stream.rs @@ -1,22 +1,20 @@ -use std::{ - process::{self, Command, Stdio}, - sync::{Arc, Mutex}, -}; +use std::process::{self, Command, Stdio}; use simplelog::*; use crate::utils::prepare_output_cmd; -use ffplayout_lib::filter::v_drawtext; -use ffplayout_lib::utils::PlayoutConfig; -use ffplayout_lib::vec_strings; +use ffplayout_lib::{ + utils::{Media, PlayoutConfig, ProcessUnit::*}, + vec_strings, +}; /// Streaming Output /// /// Prepare the ffmpeg command for streaming output pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child { - let mut enc_cmd = vec![]; - let mut enc_filter = vec![]; - let mut output_cmd = config.out.output_cmd.as_ref().unwrap().clone(); + let mut media = Media::new(0, "", false); + media.unit = Encoder; + media.add_filter(config, &None); let enc_prefix = vec_strings![ "-hide_banner", @@ -28,26 +26,7 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child { "pipe:0" ]; - if config.text.add_text && !config.text.text_from_filename { - if let Some(socket) = config.text.zmq_stream_socket.clone() { - debug!( - "Using drawtext filter, listening on address: {}", - socket - ); - - let mut filter = "[0:v]null,".to_string(); - - filter.push_str( - v_drawtext::filter_node(config, None, &Arc::new(Mutex::new(vec![]))).as_str(), - ); - - enc_filter = vec!["-filter_complex".to_string(), filter]; - } - } - - enc_cmd.append(&mut output_cmd); - - let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, config); + let enc_cmd = prepare_output_cmd(enc_prefix, &media.filter, config); debug!( "Encoder CMD: \"ffmpeg {}\"", diff --git a/ffplayout-engine/src/rpc/mod.rs b/ffplayout-engine/src/rpc/mod.rs index 175e0d50..f92c6782 100644 --- a/ffplayout-engine/src/rpc/mod.rs +++ b/ffplayout-engine/src/rpc/mod.rs @@ -87,8 +87,9 @@ pub fn json_rpc_server( // TODO: in Rust 1.65 use let_chains instead if !filter.is_empty() && config.text.zmq_stream_socket.is_some() { - let mut clips_filter = playout_stat.chain.lock().unwrap(); - *clips_filter = vec![filter.clone()]; + if let Some(clips_filter) = playout_stat.chain.clone() { + *clips_filter.lock().unwrap() = vec![filter.clone()]; + } if config.out.mode == HLS { if proc.server_is_running.load(Ordering::SeqCst) { diff --git a/ffplayout-engine/src/utils/mod.rs b/ffplayout-engine/src/utils/mod.rs index e8c0a11f..f0b72bf5 100644 --- a/ffplayout-engine/src/utils/mod.rs +++ b/ffplayout-engine/src/utils/mod.rs @@ -9,8 +9,8 @@ pub mod arg_parse; pub use arg_parse::Args; use ffplayout_lib::{ + filter::Filters, utils::{time_to_sec, OutputMode::*, PlayoutConfig, ProcessMode::*}, - vec_strings, }; /// Read command line arguments, and override the config with them. @@ -92,120 +92,67 @@ pub fn get_config(args: Args) -> PlayoutConfig { /// seek for multiple outputs and add mapping for it pub fn prepare_output_cmd( mut cmd: Vec, - mut filter: Vec, + filters: &Option, config: &PlayoutConfig, ) -> Vec { let mut output_params = config.out.clone().output_cmd.unwrap(); - let mut new_params = vec![]; let params_len = output_params.len(); - let mut output_a_map = "[a_out1]".to_string(); - let mut output_v_map = "[v_out1]".to_string(); - let mut out_count = 1; + let mut new_params = vec![]; let mut output_filter = String::new(); - let mut next_is_filter = false; - let re_audio_map = Regex::new(r"\[0:a:(?P[0-9]+)\]").unwrap(); + let re_map = Regex::new(r"(\[?[0-9]:[av](:[0-9]+)?\]?|-map|\[[a-z_0-9]+\])").unwrap(); - // Loop over output parameters - // - // Check if it contains a filtergraph, count its outputs and set correct mapping values. - for (i, p) in output_params.iter().enumerate() { - let mut param = p.clone(); - - param = param.replace("[0:v]", "[vout0]"); - param = param.replace("[0:a]", "[aout0]"); - param = re_audio_map.replace_all(¶m, "[aout$num]").to_string(); - - // Skip filter command, to concat existing filters with new ones. - if param != "-filter_complex" { - if next_is_filter { - output_filter = param.clone(); - next_is_filter = false; - } else { - new_params.push(param.clone()); - } - } else { - next_is_filter = true; - } - - // Check if parameter is a output - if i > 0 - && !param.starts_with('-') - && !output_params[i - 1].starts_with('-') - && i < params_len - 1 - { - out_count += 1; - let mut a_map = "0:a".to_string(); - let v_map = format!("[v_out{out_count}]"); - output_v_map.push_str(&v_map); - - if config.out.mode == HLS { - a_map = format!("[a_out{out_count}]"); - } - - output_a_map.push_str(&a_map); - - if !output_params.contains(&"-map".to_string()) { - let mut map = vec_strings!["-map", v_map, "-map", a_map]; - new_params.append(&mut map); - } - } - } - - if !filter.is_empty() { - output_params = new_params; - - // Process A/V mapping + if let Some(mut filter) = filters.clone() { + // Loop over output parameters // - // Check if there is multiple outputs, and/or multiple audio tracks - // and add the correct mapping for it. - if out_count > 1 && config.processing.audio_tracks == 1 && config.out.mode == HLS { - filter[1].push_str(&format!(";[vout0]split={out_count}{output_v_map}")); - filter[1].push_str(&format!(";[aout0]asplit={out_count}{output_a_map}")); - filter.drain(2..); - cmd.append(&mut filter); - cmd.append(&mut vec_strings!["-map", "[v_out1]", "-map", "[a_out1]"]); - } else if !output_filter.is_empty() && config.out.mode == HLS { - filter[1].push_str(&format!(";{output_filter}")); - filter.drain(2..); - cmd.append(&mut filter); - } else if out_count == 1 - && config.processing.audio_tracks == 1 - && config.out.mode == HLS - && output_params[0].contains("split") - { - let out_filter = output_params.remove(0); - filter[1].push_str(&format!(";{out_filter}")); - filter.drain(2..); - cmd.append(&mut filter); - } else if out_count > 1 && config.processing.audio_tracks == 1 && config.out.mode == Stream - { - filter[1].push_str(&format!(",split={out_count}{output_v_map}")); - cmd.append(&mut filter); - cmd.append(&mut vec_strings!["-map", "[v_out1]", "-map", "0:a"]); - } else if config.processing.audio_tracks > 1 && config.out.mode == Stream { - filter[1].push_str("[v_out1]"); - cmd.append(&mut filter); - - output_params = output_params - .iter() - .map(|p| p.replace("0:v", "[v_out1]")) - .collect(); - - if out_count == 1 { - cmd.append(&mut vec_strings!["-map", "[v_out1]"]); - - for i in 0..config.processing.audio_tracks { - cmd.append(&mut vec_strings!["-map", format!("0:a:{i}")]); + // Check if it contains a filtergraph, count its outputs and set correct mapping values. + for (i, param) in output_params.iter().enumerate() { + // Skip filter command, to concat existing filters with new ones. + if param != "-filter_complex" { + if i > 0 && output_params[i - 1] == "-filter_complex" { + output_filter = param.clone(); + } else if !output_filter.contains("split") { + if !re_map.is_match(param) { + // Skip mapping parameters, when no split filter is set + new_params.push(param.clone()); + } + } else { + new_params.push(param.clone()); } } - } else { - cmd.append(&mut filter); - } - } else if out_count == 1 && config.processing.audio_tracks > 1 && config.out.mode == Stream { - cmd.append(&mut vec_strings!["-map", "0:v"]); - for i in 0..config.processing.audio_tracks { - cmd.append(&mut vec_strings!["-map", format!("0:a:{i}")]); + // Check if parameter is a output + if i > 0 + && !param.starts_with('-') + && !output_params[i - 1].starts_with('-') + && i < params_len - 1 + { + // add mapping to following outputs + new_params.append(&mut filter.output_map.clone()); + } + } + if filter.cmd.contains(&"-filter_complex".to_string()) { + output_params = new_params; + + // Process A/V mapping + // + // when filter in output_param are found and we are in HLS mode, fix the mapping + if !output_filter.is_empty() && config.out.mode == HLS { + let re = Regex::new(r"0:a:(?P[0-9]+)").unwrap(); + output_filter = re + .replace_all(&output_filter, "aout${num}") + .to_string() + .replace("[0:a]", &filter.audio_map[0]) + .replace("[0:v]", &filter.video_map[0]) + .replace("[0:v:0]", &filter.video_map[0]); + + filter.cmd[1].push_str(&format!(";{output_filter}")); + filter.cmd.drain(2..); + cmd.append(&mut filter.cmd); + } else { + cmd.append(&mut filter.cmd); + } + } else { + cmd.append(&mut filter.cmd); } } diff --git a/lib/src/filter/mod.rs b/lib/src/filter/mod.rs index 32cf101f..c29b8839 100644 --- a/lib/src/filter/mod.rs +++ b/lib/src/filter/mod.rs @@ -13,11 +13,12 @@ pub mod v_drawtext; // get_delta use self::custom_filter::custom_filter; use crate::utils::{ - fps_calc, get_delta, is_close, Media, MediaProbe, OutputMode::*, PlayoutConfig, + controller::ProcessUnit::*, fps_calc, get_delta, is_close, Media, MediaProbe, OutputMode::*, + PlayoutConfig, }; -#[derive(Clone, Debug, Copy, PartialEq)] -enum FilterType { +#[derive(Clone, Debug, Copy, Eq, PartialEq)] +pub enum FilterType { Audio, Video, } @@ -34,22 +35,23 @@ impl fmt::Display for FilterType { use FilterType::*; #[derive(Debug, Clone)] -struct Filters { +pub struct Filters { audio_chain: String, video_chain: String, - final_chain: String, - audio_map: Vec, - video_map: Vec, - output_map: Vec, + pub final_chain: String, + pub audio_map: Vec, + pub video_map: Vec, + pub output_map: Vec, + audio_track_count: i32, audio_position: i32, video_position: i32, audio_last: i32, video_last: i32, - cmd: Vec, + pub cmd: Vec, } impl Filters { - fn new(position: i32) -> Self { + pub fn new(audio_track_count: i32, audio_position: i32) -> Self { Self { audio_chain: String::new(), video_chain: String::new(), @@ -57,15 +59,16 @@ impl Filters { audio_map: vec![], video_map: vec![], output_map: vec![], - audio_position: position, - video_position: position, + audio_track_count, + audio_position, + video_position: 0, audio_last: -1, video_last: -1, cmd: vec![], } } - fn add_filter(&mut self, filter: &str, track_nr: i32, filter_type: FilterType) { + pub fn add_filter(&mut self, filter: &str, track_nr: i32, filter_type: FilterType) { let (map, chain, position, last) = match filter_type { Audio => ( &mut self.audio_map, @@ -114,20 +117,47 @@ impl Filters { fn close_chains(&mut self) { // add final output selector - self.audio_chain - .push_str(&format!("[aout{}]", self.audio_last)); - self.video_chain - .push_str(&format!("[vout{}]", self.video_last)); + if self.video_last >= 0 { + self.video_chain + .push_str(&format!("[vout{}]", self.video_last)); + } else { + self.output_map + .append(&mut vec!["-map".to_string(), "0:v".to_string()]); + } + + if self.audio_last >= 0 { + self.audio_chain + .push_str(&format!("[aout{}]", self.audio_last)); + } else { + for i in 0..self.audio_track_count { + self.output_map.append(&mut vec![ + "-map".to_string(), + format!("{}:a:{i}", self.audio_position), + ]); + } + } } fn build_final_chain(&mut self) { self.final_chain.push_str(&self.video_chain); - self.final_chain.push(';'); - self.final_chain.push_str(&self.audio_chain); - self.cmd.push("-filter_complex".to_string()); - self.cmd.push(self.final_chain.clone()); - self.cmd.append(&mut self.output_map); + if !self.audio_chain.is_empty() { + self.final_chain.push(';'); + self.final_chain.push_str(&self.audio_chain); + } + + if !self.final_chain.is_empty() { + self.cmd.push("-filter_complex".to_string()); + self.cmd.push(self.final_chain.clone()); + } + + self.cmd.append(&mut self.output_map.clone()); + } +} + +impl Default for Filters { + fn default() -> Self { + Self::new(1, 0) } } @@ -220,7 +250,7 @@ fn fade(node: &mut Media, chain: &mut Filters, nr: i32, filter_type: FilterType) t = "a" } - if node.seek > 0.0 || node.is_live == Some(true) { + if node.seek > 0.0 || node.unit == Ingest { chain.add_filter(&format!("{t}fade=in:st=0:d=0.5"), nr, filter_type) } @@ -283,9 +313,11 @@ fn add_text( node: &mut Media, chain: &mut Filters, config: &PlayoutConfig, - filter_chain: &Arc>>, + filter_chain: &Option>>>, ) { - if config.text.add_text && (config.text.text_from_filename || config.out.mode == HLS) { + if config.text.add_text + && (config.text.text_from_filename || config.out.mode == HLS || node.unit == Encoder) + { let filter = v_drawtext::filter_node(config, Some(node), filter_chain); chain.add_filter(&filter, 0, Video); @@ -325,8 +357,7 @@ fn extend_audio(node: &mut Media, chain: &mut Filters, nr: i32) { /// Add single pass loudnorm filter to audio line. fn add_loudnorm(node: &Media, chain: &mut Filters, config: &PlayoutConfig, nr: i32) { - if config.processing.add_loudnorm - || (node.is_live.unwrap_or_default() && config.processing.loudnorm_ingest) + if config.processing.add_loudnorm || (node.unit == Ingest && config.processing.loudnorm_ingest) { let loud_filter = a_loudnorm::filter_node(config); chain.add_filter(&loud_filter, nr, Audio); @@ -383,9 +414,18 @@ fn custom(filter: &str, chain: &mut Filters, nr: i32, filter_type: FilterType) { pub fn filter_chains( config: &PlayoutConfig, node: &mut Media, - filter_chain: &Arc>>, -) -> Vec { - let mut filters = Filters::new(0); + filter_chain: &Option>>>, +) -> Filters { + let mut filters = Filters::new(config.processing.audio_tracks, 0); + + if node.unit == Encoder { + add_text(node, &mut filters, config, filter_chain); + + filters.close_chains(); + filters.build_final_chain(); + + return filters; + } if let Some(probe) = node.probe.as_ref() { if Path::new(&node.audio).is_file() { @@ -434,7 +474,7 @@ pub fn filter_chains( || Path::new(&node.audio).is_file() { extend_audio(node, &mut filters, i); - } else if !node.is_live.unwrap_or(false) { + } else if node.unit == Decoder { warn!( "Missing audio track (id {i}) from {}", node.source @@ -456,5 +496,5 @@ pub fn filter_chains( filters.close_chains(); filters.build_final_chain(); - filters.cmd + filters } diff --git a/lib/src/filter/v_drawtext.rs b/lib/src/filter/v_drawtext.rs index 0b3a3eea..7700cec1 100644 --- a/lib/src/filter/v_drawtext.rs +++ b/lib/src/filter/v_drawtext.rs @@ -5,57 +5,53 @@ use std::{ use regex::Regex; -use crate::utils::{Media, PlayoutConfig}; +use crate::utils::{controller::ProcessUnit::*, Media, PlayoutConfig}; pub fn filter_node( config: &PlayoutConfig, node: Option<&Media>, - filter_chain: &Arc>>, + filter_chain: &Option>>>, ) -> String { let mut filter = String::new(); let mut font = String::new(); - if config.text.add_text { - if Path::new(&config.text.fontfile).is_file() { - font = format!(":fontfile='{}'", config.text.fontfile) - } + if Path::new(&config.text.fontfile).is_file() { + font = format!(":fontfile='{}'", config.text.fontfile) + } - let zmq_socket = match node.and_then(|n| n.is_live) { - Some(true) => config.text.zmq_server_socket.clone(), - Some(false) => config.text.zmq_stream_socket.clone(), - None => config.text.zmq_stream_socket.clone(), + let zmq_socket = match node.map(|n| n.unit) { + Some(Ingest) => config.text.zmq_server_socket.clone(), + _ => config.text.zmq_stream_socket.clone(), + }; + + // TODO: in Rust 1.65 use let_chains instead + if config.text.text_from_filename && node.is_some() { + let source = node.unwrap_or(&Media::new(0, "", false)).source.clone(); + let regex: Regex = Regex::new(&config.text.regex).unwrap(); + + let text: String = match regex.captures(&source) { + Some(t) => t[1].to_string(), + None => source, }; - // TODO: in Rust 1.65 use let_chains instead - if config.text.text_from_filename && node.is_some() { - let source = node.unwrap_or(&Media::new(0, "", false)).source.clone(); - let regex: Regex = Regex::new(&config.text.regex).unwrap(); + let escape = text + .replace('\'', "'\\\\\\''") + .replace('%', "\\\\\\%") + .replace(':', "\\:"); + filter = format!("drawtext=text='{escape}':{}{font}", config.text.style) + } else if let Some(socket) = zmq_socket { + let mut filter_cmd = format!("text=''{font}"); - let text: String = match regex.captures(&source) { - Some(t) => t[1].to_string(), - None => source, - }; - - let escape = text - .replace('\'', "'\\\\\\''") - .replace('%', "\\\\\\%") - .replace(':', "\\:"); - filter = format!("drawtext=text='{escape}':{}{font}", config.text.style) - } else if let Some(socket) = zmq_socket { - let chain = filter_chain.lock().unwrap(); - let mut filter_cmd = format!("text=''{font}"); - - if !chain.is_empty() { - if let Some(link) = chain.iter().find(|&l| l.contains("text")) { - filter_cmd = link.to_string(); - } + if let Some(chain) = filter_chain { + if let Some(link) = chain.lock().unwrap().iter().find(|&l| l.contains("text")) { + filter_cmd = link.to_string(); } - - filter = format!( - "zmq=b=tcp\\\\://'{}',drawtext@dyntext={filter_cmd}", - socket.replace(':', "\\:") - ) } + + filter = format!( + "zmq=b=tcp\\\\://'{}',drawtext@dyntext={filter_cmd}", + socket.replace(':', "\\:") + ) } filter diff --git a/lib/src/utils/config.rs b/lib/src/utils/config.rs index 755a34e4..20ff567b 100644 --- a/lib/src/utils/config.rs +++ b/lib/src/utils/config.rs @@ -9,8 +9,8 @@ use std::{ use serde::{Deserialize, Serialize}; use shlex::split; -use crate::utils::{free_tcp_socket, home_dir, time_to_sec}; -use crate::vec_strings; +use super::vec_strings; +use crate::utils::{free_tcp_socket, home_dir, time_to_sec, OutputMode::*}; pub const DUMMY_LEN: f64 = 60.0; pub const IMAGE_FORMAT: [&str; 21] = [ @@ -43,7 +43,7 @@ impl FromStr for OutputMode { "hls" => Ok(Self::HLS), "null" => Ok(Self::Null), "stream" => Ok(Self::Stream), - _ => Err(String::new()), + _ => Err("Use 'desktop', 'hls', 'null' or 'stream'".to_string()), } } } @@ -71,7 +71,7 @@ impl FromStr for ProcessMode { match input { "folder" => Ok(Self::Folder), "playlist" => Ok(Self::Playlist), - _ => Err(String::new()), + _ => Err("Use 'folder' or 'playlist'".to_string()), } } } @@ -267,14 +267,6 @@ impl PlayoutConfig { .join(".ffp_status") .display() .to_string(); - let bitrate = format!( - "{}k", - config.processing.width * config.processing.height / 10 - ); - let buf_size = format!( - "{}k", - (config.processing.width * config.processing.height / 10) / 2 - ); config.playlist.start_sec = Some(time_to_sec(&config.playlist.day_start)); @@ -297,19 +289,13 @@ impl PlayoutConfig { "-pix_fmt", "yuv420p", "-r", - &config.processing.fps.to_string(), + &config.processing.fps, "-c:v", "mpeg2video", "-g", "1", - "-b:v", - &bitrate, - "-minrate", - &bitrate, - "-maxrate", - &bitrate, - "-bufsize", - &buf_size + "-qscale:v", + "2" ]; settings.append(&mut pre_audio_codec(config.processing.add_loudnorm)); @@ -320,7 +306,12 @@ impl PlayoutConfig { config.processing.settings = Some(settings); config.ingest.input_cmd = split(config.ingest.input_param.as_str()); - config.out.output_cmd = split(config.out.output_param.as_str()); + + if config.out.mode == Null { + config.out.output_cmd = Some(vec_strings!["-f", "null", "-"]); + } else { + config.out.output_cmd = split(config.out.output_param.as_str()); + } // when text overlay without text_from_filename is on, turn also the RPC server on, // to get text messages from it diff --git a/lib/src/utils/controller.rs b/lib/src/utils/controller.rs index 2b20e6ce..19315f10 100644 --- a/lib/src/utils/controller.rs +++ b/lib/src/utils/controller.rs @@ -8,11 +8,13 @@ use std::{ }; use jsonrpc_http_server::CloseHandle; +use serde::{Deserialize, Serialize}; use simplelog::*; use crate::utils::Media; /// Defined process units. +#[derive(Clone, Debug, Copy, Eq, Serialize, Deserialize, PartialEq)] pub enum ProcessUnit { Decoder, Encoder, @@ -29,6 +31,12 @@ impl fmt::Display for ProcessUnit { } } +impl Default for ProcessUnit { + fn default() -> Self { + ProcessUnit::Decoder + } +} + use ProcessUnit::*; /// Process Controller @@ -182,7 +190,7 @@ impl Default for PlayerControl { /// Global playout control, for move forward/backward clip, or resetting playlist/state. #[derive(Clone, Debug)] pub struct PlayoutStatus { - pub chain: Arc>>, + pub chain: Option>>>, pub current_date: Arc>, pub date: Arc>, pub list_init: Arc, @@ -192,7 +200,7 @@ pub struct PlayoutStatus { impl PlayoutStatus { pub fn new() -> Self { Self { - chain: Arc::new(Mutex::new(vec![])), + chain: None, current_date: Arc::new(Mutex::new(String::new())), date: Arc::new(Mutex::new(String::new())), list_init: Arc::new(AtomicBool::new(true)), diff --git a/lib/src/utils/folder.rs b/lib/src/utils/folder.rs index 20006a73..a5c8b58d 100644 --- a/lib/src/utils/folder.rs +++ b/lib/src/utils/folder.rs @@ -19,7 +19,7 @@ use crate::utils::{get_sec, include_file, Media, PlayoutConfig}; #[derive(Debug, Clone)] pub struct FolderSource { config: PlayoutConfig, - filter_chain: Arc>>, + filter_chain: Option>>>, pub nodes: Arc>>, current_node: Media, index: Arc, @@ -28,7 +28,7 @@ pub struct FolderSource { impl FolderSource { pub fn new( config: &PlayoutConfig, - filter_chain: Arc>>, + filter_chain: Option>>>, current_list: Arc>>, global_index: Arc, ) -> Self { diff --git a/lib/src/utils/generator.rs b/lib/src/utils/generator.rs index 7aa3dbca..7709b365 100644 --- a/lib/src/utils/generator.rs +++ b/lib/src/utils/generator.rs @@ -93,7 +93,7 @@ pub fn generate_playlist( date_range = get_date_range(&date_range) } - let media_list = FolderSource::new(config, Arc::new(Mutex::new(vec![])), current_list, index); + let media_list = FolderSource::new(config, None, current_list, index); let list_length = media_list.nodes.lock().unwrap().len(); for date in date_range { diff --git a/lib/src/utils/json_serializer.rs b/lib/src/utils/json_serializer.rs index 05ccacf2..f70f442c 100644 --- a/lib/src/utils/json_serializer.rs +++ b/lib/src/utils/json_serializer.rs @@ -9,8 +9,8 @@ use std::{ use simplelog::*; use crate::utils::{ - get_date, is_remote, modified_time, time_from_header, validate_playlist, Media, PlayoutConfig, - DUMMY_LEN, + controller::ProcessUnit::*, get_date, is_remote, modified_time, time_from_header, + validate_playlist, Media, PlayoutConfig, DUMMY_LEN, }; /// This is our main playlist object, it holds all necessary information for the current day. @@ -76,7 +76,7 @@ fn set_defaults( item.last_ad = Some(false); item.next_ad = Some(false); item.process = Some(true); - item.filter = Some(vec![]); + item.filter = None; start_sec += item.out - item.seek; } @@ -112,10 +112,10 @@ fn loop_playlist( cmd: item.cmd.clone(), probe: item.probe.clone(), process: Some(true), - is_live: Some(false), + unit: Decoder, last_ad: Some(false), next_ad: Some(false), - filter: Some(vec![]), + filter: None, custom_filter: String::new(), }; diff --git a/lib/src/utils/json_validate.rs b/lib/src/utils/json_validate.rs index 2c6185bd..ab975cf4 100644 --- a/lib/src/utils/json_validate.rs +++ b/lib/src/utils/json_validate.rs @@ -3,7 +3,7 @@ use std::{ process::{Command, Stdio}, sync::{ atomic::{AtomicBool, Ordering}, - Arc, Mutex, + Arc, }, }; @@ -53,16 +53,16 @@ fn check_media( node.cmd = Some(seek_and_length(&node)); } - node.add_filter(config, &Arc::new(Mutex::new(vec![]))); + node.add_filter(config, &None); let mut filter = node.filter.unwrap_or_default(); - if filter.len() > 1 { - filter[1] = filter[1].replace("realtime=speed=1", "null") + if filter.cmd.len() > 1 { + filter.cmd[1] = filter.cmd[1].replace("realtime=speed=1", "null") } enc_cmd.append(&mut node.cmd.unwrap_or_default()); - enc_cmd.append(&mut filter); + enc_cmd.append(&mut filter.cmd); enc_cmd.append(&mut vec_strings!["-t", "0.1", "-f", "null", "-"]); let mut enc_proc = match Command::new("ffmpeg") diff --git a/lib/src/utils/mod.rs b/lib/src/utils/mod.rs index 1a94a110..45d9d629 100644 --- a/lib/src/utils/mod.rs +++ b/lib/src/utils/mod.rs @@ -41,13 +41,19 @@ pub use config::{ ProcessMode::{self, *}, DUMMY_LEN, FFMPEG_IGNORE_ERRORS, IMAGE_FORMAT, }; -pub use controller::{PlayerControl, PlayoutStatus, ProcessControl, ProcessUnit::*}; +pub use controller::{ + PlayerControl, PlayoutStatus, ProcessControl, + ProcessUnit::{self, *}, +}; pub use generator::generate_playlist; pub use json_serializer::{read_json, JsonPlaylist}; pub use json_validate::validate_playlist; pub use logging::{init_logging, send_mail}; -use crate::{filter::filter_chains, vec_strings}; +use crate::{ + filter::{filter_chains, Filters}, + vec_strings, +}; /// Video clip struct to hold some important states and comments for current media. #[derive(Debug, Serialize, Deserialize, Clone)] @@ -82,7 +88,7 @@ pub struct Media { pub cmd: Option>, #[serde(skip_serializing, skip_deserializing)] - pub filter: Option>, + pub filter: Option, #[serde(default, skip_serializing_if = "is_empty_string")] pub custom_filter: String, @@ -99,8 +105,8 @@ pub struct Media { #[serde(skip_serializing, skip_deserializing)] pub process: Option, - #[serde(skip_serializing, skip_deserializing)] - pub is_live: Option, + #[serde(default, skip_serializing)] + pub unit: ProcessUnit, } impl Media { @@ -130,13 +136,13 @@ impl Media { source: src.to_string(), audio: String::new(), cmd: Some(vec_strings!["-i", src]), - filter: Some(vec![]), + filter: None, custom_filter: String::new(), probe, last_ad: Some(false), next_ad: Some(false), process: Some(true), - is_live: Some(false), + unit: Decoder, } } @@ -160,7 +166,11 @@ impl Media { } } - pub fn add_filter(&mut self, config: &PlayoutConfig, filter_chain: &Arc>>) { + pub fn add_filter( + &mut self, + config: &PlayoutConfig, + filter_chain: &Option>>>, + ) { let mut node = self.clone(); self.filter = Some(filter_chains(config, &mut node, filter_chain)) } diff --git a/tests/assets/playlist_full.json b/tests/assets/playlist_full.json old mode 100755 new mode 100644 diff --git a/tests/src/engine_cmd.rs b/tests/src/engine_cmd.rs index 5bbda21b..8cde7d66 100644 --- a/tests/src/engine_cmd.rs +++ b/tests/src/engine_cmd.rs @@ -1,12 +1,8 @@ -use std::{ - fs, - sync::{Arc, Mutex}, -}; +use std::fs; use ffplayout::{input::playlist::gen_source, utils::prepare_output_cmd}; use ffplayout_lib::{ - filter::v_drawtext, - utils::{Media, OutputMode::*, PlayoutConfig}, + utils::{Media, OutputMode::*, PlayoutConfig, ProcessUnit::*}, vec_strings, }; @@ -19,9 +15,9 @@ fn video_audio_input() { config.processing.logo = logo_path.to_string_lossy().to_string(); let media_obj = Media::new(0, "./assets/with_audio.mp4", true); - let media = gen_source(&config, media_obj, &Arc::new(Mutex::new(vec![]))); + let media = gen_source(&config, media_obj, &None); - let test_filter_cmd = Some( + let test_filter_cmd = vec_strings![ "-filter_complex", format!("[0:v:0]scale=1024:576,null[v];movie={}:loop=0,setpts=N/(FRAME_RATE*TB),format=rgba,colorchannelmixer=aa=0.7[l];[v][l]overlay=W-w-12:12:shortest=1[vout0];[0:a:0]anull[aout0]", config.processing.logo), @@ -29,14 +25,13 @@ fn video_audio_input() { "[vout0]", "-map", "[aout0]" - ], - ); + ]; assert_eq!( media.cmd, Some(vec_strings!["-i", "./assets/with_audio.mp4"]) ); - assert_eq!(media.filter, test_filter_cmd); + assert_eq!(media.filter.unwrap().cmd, test_filter_cmd); } #[test] @@ -47,9 +42,9 @@ fn dual_audio_aevalsrc_input() { config.processing.add_logo = false; let media_obj = Media::new(0, "./assets/with_audio.mp4", true); - let media = gen_source(&config, media_obj, &Arc::new(Mutex::new(vec![]))); + let media = gen_source(&config, media_obj, &None); - let test_filter_cmd = Some( + let test_filter_cmd = vec_strings![ "-filter_complex", "[0:v:0]scale=1024:576[vout0];[0:a:0]anull[aout0];aevalsrc=0:channel_layout=stereo:duration=30:sample_rate=48000,anull[aout1]", @@ -59,14 +54,13 @@ fn dual_audio_aevalsrc_input() { "[aout0]", "-map", "[aout1]" - ], - ); + ]; assert_eq!( media.cmd, Some(vec_strings!["-i", "./assets/with_audio.mp4"]) ); - assert_eq!(media.filter, test_filter_cmd); + assert_eq!(media.filter.unwrap().cmd, test_filter_cmd); } #[test] @@ -77,9 +71,9 @@ fn dual_audio_input() { config.processing.add_logo = false; let media_obj = Media::new(0, "./assets/dual_audio.mp4", true); - let media = gen_source(&config, media_obj, &Arc::new(Mutex::new(vec![]))); + let media = gen_source(&config, media_obj, &None); - let test_filter_cmd = Some(vec_strings![ + let test_filter_cmd = vec_strings![ "-filter_complex", "[0:v:0]scale=1024:576[vout0];[0:a:0]anull[aout0];[0:a:1]anull[aout1]", "-map", @@ -88,13 +82,13 @@ fn dual_audio_input() { "[aout0]", "-map", "[aout1]" - ]); + ]; assert_eq!( media.cmd, Some(vec_strings!["-i", "./assets/dual_audio.mp4"]) ); - assert_eq!(media.filter, test_filter_cmd); + assert_eq!(media.filter.unwrap().cmd, test_filter_cmd); } #[test] @@ -106,16 +100,16 @@ fn video_separate_audio_input() { let mut media_obj = Media::new(0, "./assets/no_audio.mp4", true); media_obj.audio = "./assets/audio.mp3".to_string(); - let media = gen_source(&config, media_obj, &Arc::new(Mutex::new(vec![]))); + let media = gen_source(&config, media_obj, &None); - let test_filter_cmd = Some(vec_strings![ + let test_filter_cmd = vec_strings![ "-filter_complex", "[0:v:0]scale=1024:576[vout0];[1:a:0]anull[aout0]", "-map", "[vout0]", "-map", "[aout0]" - ]); + ]; assert_eq!( media.cmd, @@ -128,7 +122,7 @@ fn video_separate_audio_input() { "30" ]) ); - assert_eq!(media.filter, test_filter_cmd); + assert_eq!(media.filter.unwrap().cmd, test_filter_cmd); } #[test] @@ -153,7 +147,6 @@ fn video_audio_stream() { ]); let mut enc_cmd = vec![]; - let enc_filter = vec![]; let mut output_cmd = config.out.output_cmd.as_ref().unwrap().clone(); let enc_prefix = vec_strings![ @@ -168,7 +161,7 @@ fn video_audio_stream() { enc_cmd.append(&mut output_cmd); - let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config); + let enc_cmd = prepare_output_cmd(enc_prefix, &None, &config); let test_cmd = vec_strings![ "-hide_banner", @@ -202,6 +195,7 @@ fn video_dual_audio_stream() { config.out.mode = Stream; config.processing.add_logo = false; config.processing.audio_tracks = 2; + config.text.add_text = false; config.out.output_cmd = Some(vec_strings![ "-c:v", "libx264", @@ -218,8 +212,12 @@ fn video_dual_audio_stream() { "srt://127.0.0.1:40051" ]); + let mut media = Media::new(0, "", false); + media.unit = Encoder; + media.add_filter(&config, &None); + let mut enc_cmd = vec![]; - let enc_filter = vec![]; + let mut output_cmd = config.out.output_cmd.as_ref().unwrap().clone(); let enc_prefix = vec_strings![ @@ -234,7 +232,7 @@ fn video_dual_audio_stream() { enc_cmd.append(&mut output_cmd); - let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config); + let enc_cmd = prepare_output_cmd(enc_prefix, &media.filter, &config); let test_cmd = vec_strings![ "-hide_banner", @@ -308,17 +306,16 @@ fn video_dual_audio_filter_stream() { .clone() .unwrap() .replace(':', "\\:"); - let mut filter = "[0:v]null,".to_string(); - filter.push_str(v_drawtext::filter_node(&config, None, &Arc::new(Mutex::new(vec![]))).as_str()); - - let enc_filter = vec!["-filter_complex".to_string(), filter]; + let mut media = Media::new(0, "", false); + media.unit = Encoder; + media.add_filter(&config, &None); let mut output_cmd = config.out.output_cmd.as_ref().unwrap().clone(); enc_cmd.append(&mut output_cmd); - let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config); + let enc_cmd = prepare_output_cmd(enc_prefix, &media.filter, &config); let test_cmd = vec_strings![ "-hide_banner", @@ -329,9 +326,9 @@ fn video_dual_audio_filter_stream() { "-i", "pipe:0", "-filter_complex", - format!("[0:v]null,zmq=b=tcp\\\\://'{socket}',drawtext@dyntext=text=''[v_out1]"), + format!("[0:v:0]zmq=b=tcp\\\\://'{socket}',drawtext@dyntext=text=''[vout0]"), "-map", - "[v_out1]", + "[vout0]", "-map", "0:a:0", "-map", @@ -391,7 +388,6 @@ fn video_audio_multi_stream() { ]); let mut enc_cmd = vec![]; - let enc_filter = vec![]; let mut output_cmd = config.out.output_cmd.as_ref().unwrap().clone(); let enc_prefix = vec_strings![ @@ -406,7 +402,7 @@ fn video_audio_multi_stream() { enc_cmd.append(&mut output_cmd); - let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config); + let enc_cmd = prepare_output_cmd(enc_prefix, &None, &config); let test_cmd = vec_strings![ "-hide_banner", @@ -499,7 +495,6 @@ fn video_dual_audio_multi_stream() { ]); let mut enc_cmd = vec![]; - let enc_filter = vec![]; let mut output_cmd = config.out.output_cmd.as_ref().unwrap().clone(); let enc_prefix = vec_strings![ @@ -514,7 +509,7 @@ fn video_dual_audio_multi_stream() { enc_cmd.append(&mut output_cmd); - let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config); + let enc_cmd = prepare_output_cmd(enc_prefix, &None, &config); let test_cmd = vec_strings![ "-hide_banner", @@ -636,17 +631,16 @@ fn video_dual_audio_multi_filter_stream() { .clone() .unwrap() .replace(':', "\\:"); - let mut filter = "[0:v]null,".to_string(); - filter.push_str(v_drawtext::filter_node(&config, None, &Arc::new(Mutex::new(vec![]))).as_str()); - - let enc_filter = vec!["-filter_complex".to_string(), filter]; + let mut media = Media::new(0, "", false); + media.unit = Encoder; + media.add_filter(&config, &None); let mut output_cmd = config.out.output_cmd.as_ref().unwrap().clone(); enc_cmd.append(&mut output_cmd); - let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config); + let enc_cmd = prepare_output_cmd(enc_prefix, &media.filter, &config); let test_cmd = vec_strings![ "-hide_banner", @@ -657,9 +651,9 @@ fn video_dual_audio_multi_filter_stream() { "-i", "pipe:0", "-filter_complex", - format!("[0:v]null,zmq=b=tcp\\\\://'{socket}',drawtext@dyntext=text=''[v_out1]"), + format!("[0:v:0]zmq=b=tcp\\\\://'{socket}',drawtext@dyntext=text=''[vout0]"), "-map", - "[v_out1]", + "[vout0]", "-map", "0:a:0", "-map", @@ -678,7 +672,7 @@ fn video_dual_audio_multi_filter_stream() { "mpegts", "srt://127.0.0.1:40051", "-map", - "[v_out1]", + "[vout0]", "-map", "0:a:0", "-map", @@ -736,8 +730,7 @@ fn video_audio_hls() { ]); let media_obj = Media::new(0, "./assets/with_audio.mp4", true); - let media = gen_source(&config, media_obj, &Arc::new(Mutex::new(vec![]))); - let enc_filter = media.filter.unwrap(); + let media = gen_source(&config, media_obj, &None); let enc_prefix = vec_strings![ "-hide_banner", @@ -749,7 +742,7 @@ fn video_audio_hls() { "./assets/with_audio.mp4" ]; - let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config); + let enc_cmd = prepare_output_cmd(enc_prefix, &media.filter, &config); let test_cmd = vec_strings![ "-hide_banner", @@ -823,8 +816,7 @@ fn video_multi_audio_hls() { ]); let media_obj = Media::new(0, "./assets/dual_audio.mp4", true); - let media = gen_source(&config, media_obj, &Arc::new(Mutex::new(vec![]))); - let enc_filter = media.filter.unwrap(); + let media = gen_source(&config, media_obj, &None); let enc_prefix = vec_strings![ "-hide_banner", @@ -836,7 +828,7 @@ fn video_multi_audio_hls() { "./assets/dual_audio.mp4" ]; - let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config); + let enc_cmd = prepare_output_cmd(enc_prefix, &media.filter, &config); let test_cmd = vec_strings![ "-hide_banner", @@ -927,8 +919,7 @@ fn multi_video_audio_hls() { ]); let media_obj = Media::new(0, "./assets/with_audio.mp4", true); - let media = gen_source(&config, media_obj, &Arc::new(Mutex::new(vec![]))); - let enc_filter = media.filter.unwrap(); + let media = gen_source(&config, media_obj, &None); let enc_prefix = vec_strings![ "-hide_banner", @@ -940,7 +931,7 @@ fn multi_video_audio_hls() { "./assets/with_audio.mp4" ]; - let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config); + let enc_cmd = prepare_output_cmd(enc_prefix, &media.filter, &config); let test_cmd = vec_strings![ "-hide_banner", @@ -1044,8 +1035,7 @@ fn multi_video_multi_audio_hls() { ]); let media_obj = Media::new(0, "./assets/dual_audio.mp4", true); - let media = gen_source(&config, media_obj, &Arc::new(Mutex::new(vec![]))); - let enc_filter = media.filter.unwrap(); + let media = gen_source(&config, media_obj, &None); let enc_prefix = vec_strings![ "-hide_banner", @@ -1057,7 +1047,7 @@ fn multi_video_multi_audio_hls() { "./assets/dual_audio.mp4" ]; - let enc_cmd = prepare_output_cmd(enc_prefix, enc_filter, &config); + let enc_cmd = prepare_output_cmd(enc_prefix, &media.filter, &config); let test_cmd = vec_strings![ "-hide_banner",