simplify prepare_output_cmd, use filters struct for stream encoder (#209)

This commit is contained in:
jb-alvarado 2022-10-17 19:30:10 +02:00 committed by GitHub
parent 5e923d4e06
commit 23aedf3600
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 325 additions and 381 deletions

28
Cargo.lock generated
View File

@ -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",
]

View File

@ -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

View File

@ -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;

View File

@ -457,7 +457,7 @@ fn timed_source(
pub fn gen_source(
config: &PlayoutConfig,
mut node: Media,
filter_chain: &Arc<Mutex<Vec<String>>>,
filter_chain: &Option<Arc<Mutex<Vec<String>>>>,
) -> 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<Mutex<Vec<String>>>,
filter_chain: &Option<Arc<Mutex<Vec<String>>>>,
) -> 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<Mutex<Vec<String>>>,
filter_chain: &Option<Arc<Mutex<Vec<String>>>>,
) -> Media {
debug!("Playlist end");

View File

@ -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];
}
}

View File

@ -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: <bright-blue>\"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: <bright-blue>\"ffmpeg {}\"</>",

View File

@ -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());

View File

@ -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<String> = 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: <yellow>{}</>",
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: <bright-blue>\"ffmpeg {}\"</>",

View File

@ -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: <yellow>{}</>",
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: <bright-blue>\"ffmpeg {}\"</>",

View File

@ -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) {

View File

@ -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<String>,
mut filter: Vec<String>,
filters: &Option<Filters>,
config: &PlayoutConfig,
) -> Vec<String> {
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<num>[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(&param, "[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<num>[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);
}
}

View File

@ -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<String>,
video_map: Vec<String>,
output_map: Vec<String>,
pub final_chain: String,
pub audio_map: Vec<String>,
pub video_map: Vec<String>,
pub output_map: Vec<String>,
audio_track_count: i32,
audio_position: i32,
video_position: i32,
audio_last: i32,
video_last: i32,
cmd: Vec<String>,
pub cmd: Vec<String>,
}
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<Mutex<Vec<String>>>,
filter_chain: &Option<Arc<Mutex<Vec<String>>>>,
) {
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<Mutex<Vec<String>>>,
) -> Vec<String> {
let mut filters = Filters::new(0);
filter_chain: &Option<Arc<Mutex<Vec<String>>>>,
) -> 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 <b><magenta>{}</></b>",
node.source
@ -456,5 +496,5 @@ pub fn filter_chains(
filters.close_chains();
filters.build_final_chain();
filters.cmd
filters
}

View File

@ -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<Mutex<Vec<String>>>,
filter_chain: &Option<Arc<Mutex<Vec<String>>>>,
) -> 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

View File

@ -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

View File

@ -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<Mutex<Vec<String>>>,
pub chain: Option<Arc<Mutex<Vec<String>>>>,
pub current_date: Arc<Mutex<String>>,
pub date: Arc<Mutex<String>>,
pub list_init: Arc<AtomicBool>,
@ -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)),

View File

@ -19,7 +19,7 @@ use crate::utils::{get_sec, include_file, Media, PlayoutConfig};
#[derive(Debug, Clone)]
pub struct FolderSource {
config: PlayoutConfig,
filter_chain: Arc<Mutex<Vec<String>>>,
filter_chain: Option<Arc<Mutex<Vec<String>>>>,
pub nodes: Arc<Mutex<Vec<Media>>>,
current_node: Media,
index: Arc<AtomicUsize>,
@ -28,7 +28,7 @@ pub struct FolderSource {
impl FolderSource {
pub fn new(
config: &PlayoutConfig,
filter_chain: Arc<Mutex<Vec<String>>>,
filter_chain: Option<Arc<Mutex<Vec<String>>>>,
current_list: Arc<Mutex<Vec<Media>>>,
global_index: Arc<AtomicUsize>,
) -> Self {

View File

@ -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 {

View File

@ -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(),
};

View File

@ -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")

View File

@ -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<Vec<String>>,
#[serde(skip_serializing, skip_deserializing)]
pub filter: Option<Vec<String>>,
pub filter: Option<Filters>,
#[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<bool>,
#[serde(skip_serializing, skip_deserializing)]
pub is_live: Option<bool>,
#[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<Mutex<Vec<String>>>) {
pub fn add_filter(
&mut self,
config: &PlayoutConfig,
filter_chain: &Option<Arc<Mutex<Vec<String>>>>,
) {
let mut node = self.clone();
self.filter = Some(filter_chains(config, &mut node, filter_chain))
}

0
tests/assets/playlist_full.json Executable file → Normal file
View File

View File

@ -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",