ignore advanced config in tests, support advanced config path as argument, support advanced config on null output
This commit is contained in:
parent
5855eed691
commit
12086904cd
@ -65,7 +65,7 @@ async fn validator(
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
let mut config = PlayoutConfig::new(None, None);
|
||||
config.mail.recipient = String::new();
|
||||
config.logging.log_to_file = false;
|
||||
config.logging.timestamp = false;
|
||||
|
@ -31,9 +31,10 @@ pub async fn create_channel(
|
||||
Err(_) => rand::thread_rng().gen_range(71..99),
|
||||
};
|
||||
|
||||
let mut config = PlayoutConfig::new(Some(PathBuf::from(
|
||||
"/usr/share/ffplayout/ffplayout.yml.orig",
|
||||
)));
|
||||
let mut config = PlayoutConfig::new(
|
||||
Some(PathBuf::from("/usr/share/ffplayout/ffplayout.yml.orig")),
|
||||
None,
|
||||
);
|
||||
|
||||
config.general.stat_file = format!(".ffp_{channel_name}",);
|
||||
config.logging.path = config.logging.path.join(&channel_name);
|
||||
|
@ -14,7 +14,7 @@ use ffplayout_lib::{
|
||||
controller::ProcessUnit::*, test_tcp_port, Media, PlayoutConfig, ProcessControl,
|
||||
FFMPEG_IGNORE_ERRORS, FFMPEG_UNRECOVERABLE_ERRORS,
|
||||
},
|
||||
vec_strings, ADVANCED_CONFIG,
|
||||
vec_strings,
|
||||
};
|
||||
|
||||
fn server_monitor(
|
||||
@ -64,7 +64,11 @@ pub fn ingest_server(
|
||||
dummy_media.unit = Ingest;
|
||||
dummy_media.add_filter(&config, &None);
|
||||
|
||||
if let Some(ingest_input_cmd) = &ADVANCED_CONFIG.ingest.input_cmd {
|
||||
if let Some(ingest_input_cmd) = config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.ingest.input_cmd.clone())
|
||||
{
|
||||
server_cmd.append(&mut ingest_input_cmd.clone());
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@ use std::process::{self, Command, Stdio};
|
||||
|
||||
use simplelog::*;
|
||||
|
||||
use ffplayout_lib::{filter::v_drawtext, utils::PlayoutConfig, vec_strings, ADVANCED_CONFIG};
|
||||
use ffplayout_lib::{filter::v_drawtext, utils::PlayoutConfig, vec_strings};
|
||||
|
||||
/// Desktop Output
|
||||
///
|
||||
@ -12,7 +12,11 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child {
|
||||
|
||||
let mut enc_cmd = vec_strings!["-hide_banner", "-nostats", "-v", log_format];
|
||||
|
||||
if let Some(encoder_input_cmd) = &ADVANCED_CONFIG.encoder.input_cmd {
|
||||
if let Some(encoder_input_cmd) = config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.encoder.input_cmd.clone())
|
||||
{
|
||||
enc_cmd.append(&mut encoder_input_cmd.clone());
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ use ffplayout_lib::{
|
||||
controller::ProcessUnit::*, get_delta, sec_to_time, stderr_reader, test_tcp_port, Media,
|
||||
PlayerControl, PlayoutConfig, PlayoutStatus, ProcessControl,
|
||||
},
|
||||
vec_strings, ADVANCED_CONFIG,
|
||||
vec_strings,
|
||||
};
|
||||
|
||||
/// Ingest Server for HLS
|
||||
@ -50,7 +50,11 @@ fn ingest_to_hls_server(
|
||||
let mut dummy_media = Media::new(0, "Live Stream", false);
|
||||
dummy_media.unit = Ingest;
|
||||
|
||||
if let Some(ingest_input_cmd) = &ADVANCED_CONFIG.ingest.input_cmd {
|
||||
if let Some(ingest_input_cmd) = config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.ingest.input_cmd.clone())
|
||||
{
|
||||
server_prefix.append(&mut ingest_input_cmd.clone());
|
||||
}
|
||||
|
||||
@ -203,7 +207,11 @@ pub fn write_hls(
|
||||
|
||||
let mut enc_prefix = vec_strings!["-hide_banner", "-nostats", "-v", &ff_log_format];
|
||||
|
||||
if let Some(encoder_input_cmd) = &ADVANCED_CONFIG.encoder.input_cmd {
|
||||
if let Some(encoder_input_cmd) = config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.encoder.input_cmd.clone())
|
||||
{
|
||||
enc_prefix.append(&mut encoder_input_cmd.clone());
|
||||
}
|
||||
|
||||
|
@ -19,14 +19,11 @@ pub use hls::write_hls;
|
||||
use crate::input::{ingest_server, source_generator};
|
||||
use crate::utils::task_runner;
|
||||
|
||||
use ffplayout_lib::vec_strings;
|
||||
use ffplayout_lib::{
|
||||
utils::{
|
||||
sec_to_time, stderr_reader, OutputMode::*, PlayerControl, PlayoutConfig, PlayoutStatus,
|
||||
ProcessControl, ProcessUnit::*,
|
||||
},
|
||||
ADVANCED_CONFIG,
|
||||
use ffplayout_lib::utils::{
|
||||
sec_to_time, stderr_reader, OutputMode::*, PlayerControl, PlayoutConfig, PlayoutStatus,
|
||||
ProcessControl, ProcessUnit::*,
|
||||
};
|
||||
use ffplayout_lib::vec_strings;
|
||||
|
||||
/// Player
|
||||
///
|
||||
@ -147,7 +144,11 @@ pub fn player(
|
||||
|
||||
let mut dec_cmd = vec_strings!["-hide_banner", "-nostats", "-v", &ff_log_format];
|
||||
|
||||
if let Some(decoder_input_cmd) = &ADVANCED_CONFIG.decoder.input_cmd {
|
||||
if let Some(decoder_input_cmd) = config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.input_cmd.clone())
|
||||
{
|
||||
dec_cmd.append(&mut decoder_input_cmd.clone());
|
||||
}
|
||||
|
||||
|
@ -16,15 +16,17 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child {
|
||||
media.unit = Encoder;
|
||||
media.add_filter(config, &None);
|
||||
|
||||
let enc_prefix = vec_strings![
|
||||
"-hide_banner",
|
||||
"-nostats",
|
||||
"-v",
|
||||
log_format,
|
||||
"-re",
|
||||
"-i",
|
||||
"pipe:0"
|
||||
];
|
||||
let mut enc_prefix = vec_strings!["-hide_banner", "-nostats", "-v", log_format];
|
||||
|
||||
if let Some(input_cmd) = config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.encoder.input_cmd.clone())
|
||||
{
|
||||
enc_prefix.append(&mut input_cmd.clone());
|
||||
}
|
||||
|
||||
enc_prefix.append(&mut vec_strings!["-re", "-i", "pipe:0"]);
|
||||
|
||||
let enc_cmd = prepare_output_cmd(config, enc_prefix, &media.filter);
|
||||
|
||||
|
@ -5,7 +5,7 @@ use simplelog::*;
|
||||
use crate::utils::prepare_output_cmd;
|
||||
use ffplayout_lib::{
|
||||
utils::{Media, PlayoutConfig, ProcessUnit::*},
|
||||
vec_strings, ADVANCED_CONFIG,
|
||||
vec_strings,
|
||||
};
|
||||
|
||||
/// Streaming Output
|
||||
@ -18,7 +18,11 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child {
|
||||
|
||||
let mut enc_prefix = vec_strings!["-hide_banner", "-nostats", "-v", log_format];
|
||||
|
||||
if let Some(input_cmd) = &ADVANCED_CONFIG.encoder.input_cmd {
|
||||
if let Some(input_cmd) = config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.encoder.input_cmd.clone())
|
||||
{
|
||||
enc_prefix.append(&mut input_cmd.clone());
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,9 @@ use ffplayout_lib::utils::{OutputMode, ProcessMode};
|
||||
\n ffplayout (ARGS) [OPTIONS]\n\n Pass channel name only in multi channel environment!",
|
||||
long_about = None)]
|
||||
pub struct Args {
|
||||
#[clap(long, help = "File path to advanced.yml")]
|
||||
pub advanced_config: Option<PathBuf>,
|
||||
|
||||
#[clap(index = 1, value_parser, help = "Channel name")]
|
||||
pub channel: Option<String>,
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
use std::{
|
||||
env,
|
||||
fs::File,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
@ -37,7 +38,19 @@ pub fn get_config(args: Args) -> Result<PlayoutConfig, ProcError> {
|
||||
None => args.config,
|
||||
};
|
||||
|
||||
let mut config = PlayoutConfig::new(cfg_path);
|
||||
let mut adv_config_path = PathBuf::from("/etc/ffplayout/advanced.yml");
|
||||
|
||||
if let Some(adv_path) = args.advanced_config {
|
||||
adv_config_path = adv_path;
|
||||
} else if !adv_config_path.is_file() {
|
||||
if Path::new("./assets/advanced.yml").is_file() {
|
||||
adv_config_path = PathBuf::from("./assets/advanced.yml")
|
||||
} else if let Some(p) = env::current_exe().ok().as_ref().and_then(|op| op.parent()) {
|
||||
adv_config_path = p.join("advanced.yml")
|
||||
};
|
||||
}
|
||||
|
||||
let mut config = PlayoutConfig::new(cfg_path, Some(adv_config_path));
|
||||
|
||||
if let Some(gen) = args.generate {
|
||||
config.general.generate = Some(gen);
|
||||
|
@ -14,7 +14,6 @@ use crate::utils::{
|
||||
controller::ProcessUnit::*, custom_format, fps_calc, is_close, Media, OutputMode::*,
|
||||
PlayoutConfig,
|
||||
};
|
||||
use crate::ADVANCED_CONFIG;
|
||||
|
||||
use super::vec_strings;
|
||||
|
||||
@ -179,15 +178,19 @@ impl Filters {
|
||||
|
||||
impl Default for Filters {
|
||||
fn default() -> Self {
|
||||
Self::new(PlayoutConfig::new(None), 0)
|
||||
Self::new(PlayoutConfig::new(None, None), 0)
|
||||
}
|
||||
}
|
||||
|
||||
fn deinterlace(field_order: &Option<String>, chain: &mut Filters) {
|
||||
fn deinterlace(field_order: &Option<String>, chain: &mut Filters, config: &PlayoutConfig) {
|
||||
if let Some(order) = field_order {
|
||||
if order != "progressive" {
|
||||
let deinterlace = match &ADVANCED_CONFIG.decoder.filters.deinterlace {
|
||||
Some(deinterlace) => deinterlace.clone(),
|
||||
let deinterlace = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.deinterlace.clone())
|
||||
{
|
||||
Some(deinterlace) => deinterlace,
|
||||
None => "yadif=0:-1:0".to_string(),
|
||||
};
|
||||
|
||||
@ -202,14 +205,22 @@ fn pad(aspect: f64, chain: &mut Filters, v_stream: &ffprobe::Stream, config: &Pl
|
||||
|
||||
if let (Some(w), Some(h)) = (v_stream.width, v_stream.height) {
|
||||
if w > config.processing.width && aspect > config.processing.aspect {
|
||||
scale = match &ADVANCED_CONFIG.decoder.filters.pad_scale_w {
|
||||
scale = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.pad_scale_w.clone())
|
||||
{
|
||||
Some(pad_scale_w) => {
|
||||
custom_format(&format!("{pad_scale_w},"), &[&config.processing.width])
|
||||
}
|
||||
None => format!("scale={}:-1,", config.processing.width),
|
||||
};
|
||||
} else if h > config.processing.height && aspect < config.processing.aspect {
|
||||
scale = match &ADVANCED_CONFIG.decoder.filters.pad_scale_h {
|
||||
scale = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.pad_scale_h.clone())
|
||||
{
|
||||
Some(pad_scale_h) => {
|
||||
custom_format(&format!("{pad_scale_h},"), &[&config.processing.width])
|
||||
}
|
||||
@ -218,7 +229,11 @@ fn pad(aspect: f64, chain: &mut Filters, v_stream: &ffprobe::Stream, config: &Pl
|
||||
}
|
||||
}
|
||||
|
||||
let pad = match &ADVANCED_CONFIG.decoder.filters.pad_video {
|
||||
let pad = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.pad_video.clone())
|
||||
{
|
||||
Some(pad_video) => custom_format(
|
||||
&format!("{scale}{pad_video}"),
|
||||
&[
|
||||
@ -238,8 +253,12 @@ fn pad(aspect: f64, chain: &mut Filters, v_stream: &ffprobe::Stream, config: &Pl
|
||||
|
||||
fn fps(fps: f64, chain: &mut Filters, config: &PlayoutConfig) {
|
||||
if fps != config.processing.fps {
|
||||
let fps_filter = match &ADVANCED_CONFIG.decoder.filters.fps {
|
||||
Some(fps) => custom_format(fps, &[&config.processing.fps]),
|
||||
let fps_filter = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.fps.clone())
|
||||
{
|
||||
Some(fps) => custom_format(&fps, &[&config.processing.fps]),
|
||||
None => format!("fps={}", config.processing.fps),
|
||||
};
|
||||
|
||||
@ -257,9 +276,13 @@ fn scale(
|
||||
// width: i64, height: i64
|
||||
if let (Some(w), Some(h)) = (width, height) {
|
||||
if w != config.processing.width || h != config.processing.height {
|
||||
let scale = match &ADVANCED_CONFIG.decoder.filters.scale {
|
||||
let scale = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.scale.clone())
|
||||
{
|
||||
Some(scale) => custom_format(
|
||||
scale,
|
||||
&scale,
|
||||
&[&config.processing.width, &config.processing.height],
|
||||
),
|
||||
None => format!(
|
||||
@ -274,17 +297,25 @@ fn scale(
|
||||
}
|
||||
|
||||
if !is_close(aspect, config.processing.aspect, 0.03) {
|
||||
let dar = match &ADVANCED_CONFIG.decoder.filters.set_dar {
|
||||
Some(set_dar) => custom_format(set_dar, &[&config.processing.aspect]),
|
||||
let dar = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.set_dar.clone())
|
||||
{
|
||||
Some(set_dar) => custom_format(&set_dar, &[&config.processing.aspect]),
|
||||
None => format!("setdar=dar={}", config.processing.aspect),
|
||||
};
|
||||
|
||||
chain.add_filter(&dar, 0, Video);
|
||||
}
|
||||
} else {
|
||||
let scale = match &ADVANCED_CONFIG.decoder.filters.scale {
|
||||
let scale = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.scale.clone())
|
||||
{
|
||||
Some(scale) => custom_format(
|
||||
scale,
|
||||
&scale,
|
||||
&[&config.processing.width, &config.processing.height],
|
||||
),
|
||||
None => format!(
|
||||
@ -294,8 +325,12 @@ fn scale(
|
||||
};
|
||||
chain.add_filter(&scale, 0, Video);
|
||||
|
||||
let dar = match &ADVANCED_CONFIG.decoder.filters.set_dar {
|
||||
Some(set_dar) => custom_format(set_dar, &[&config.processing.aspect]),
|
||||
let dar = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.set_dar.clone())
|
||||
{
|
||||
Some(set_dar) => custom_format(&set_dar, &[&config.processing.aspect]),
|
||||
None => format!("setdar=dar={}", config.processing.aspect),
|
||||
};
|
||||
|
||||
@ -303,7 +338,13 @@ fn scale(
|
||||
}
|
||||
}
|
||||
|
||||
fn fade(node: &mut Media, chain: &mut Filters, nr: i32, filter_type: FilterType) {
|
||||
fn fade(
|
||||
node: &mut Media,
|
||||
chain: &mut Filters,
|
||||
nr: i32,
|
||||
filter_type: FilterType,
|
||||
config: &PlayoutConfig,
|
||||
) {
|
||||
let mut t = "";
|
||||
let mut fade_audio = false;
|
||||
|
||||
@ -319,11 +360,19 @@ fn fade(node: &mut Media, chain: &mut Filters, nr: i32, filter_type: FilterType)
|
||||
let mut fade_in = format!("{t}fade=in:st=0:d=0.5");
|
||||
|
||||
if t == "a" {
|
||||
if let Some(fade) = &ADVANCED_CONFIG.decoder.filters.afade_in {
|
||||
fade_in = custom_format(fade, &[t]);
|
||||
if let Some(fade) = config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.afade_in.clone())
|
||||
{
|
||||
fade_in = custom_format(&fade, &[t]);
|
||||
}
|
||||
} else if let Some(fade) = &ADVANCED_CONFIG.decoder.filters.fade_in {
|
||||
fade_in = custom_format(fade, &[t]);
|
||||
} else if let Some(fade) = config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.fade_in.clone())
|
||||
{
|
||||
fade_in = custom_format(&fade, &[t]);
|
||||
};
|
||||
|
||||
chain.add_filter(&fade_in, nr, filter_type);
|
||||
@ -333,11 +382,20 @@ fn fade(node: &mut Media, chain: &mut Filters, nr: i32, filter_type: FilterType)
|
||||
let mut fade_out = format!("{t}fade=out:st={}:d=1.0", (node.out - node.seek - 1.0));
|
||||
|
||||
if t == "a" {
|
||||
if let Some(fade) = &ADVANCED_CONFIG.decoder.filters.afade_out {
|
||||
fade_out = custom_format(fade, &[t]);
|
||||
if let Some(fade) = config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.afade_out.clone())
|
||||
{
|
||||
fade_out = custom_format(&fade, &[node.out - node.seek - 1.0]);
|
||||
}
|
||||
} else if let Some(fade) = &ADVANCED_CONFIG.decoder.filters.fade_out {
|
||||
fade_out = custom_format(fade, &[t]);
|
||||
} else if let Some(fade) = config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.fade_out.clone())
|
||||
.clone()
|
||||
{
|
||||
fade_out = custom_format(&fade, &[node.out - node.seek - 1.0]);
|
||||
};
|
||||
|
||||
chain.add_filter(&fade_out, nr, filter_type);
|
||||
@ -360,7 +418,11 @@ fn overlay(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) {
|
||||
);
|
||||
|
||||
if node.last_ad {
|
||||
match &ADVANCED_CONFIG.decoder.filters.overlay_logo_fade_in {
|
||||
match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.overlay_logo_fade_in.clone())
|
||||
{
|
||||
Some(fade_in) => logo_chain.push_str(&format!(",{fade_in}")),
|
||||
None => logo_chain.push_str(",fade=in:st=0:d=1.0:alpha=1"),
|
||||
};
|
||||
@ -369,7 +431,11 @@ fn overlay(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) {
|
||||
if node.next_ad {
|
||||
let length = node.out - node.seek - 1.0;
|
||||
|
||||
match &ADVANCED_CONFIG.decoder.filters.overlay_logo_fade_out {
|
||||
match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.overlay_logo_fade_out.clone())
|
||||
{
|
||||
Some(fade_out) => {
|
||||
logo_chain.push_str(&custom_format(&format!(",{fade_out}"), &[length]))
|
||||
}
|
||||
@ -378,7 +444,11 @@ fn overlay(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) {
|
||||
}
|
||||
|
||||
if !config.processing.logo_scale.is_empty() {
|
||||
match &ADVANCED_CONFIG.decoder.filters.overlay_logo_scale {
|
||||
match &config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.overlay_logo_scale.clone())
|
||||
{
|
||||
Some(logo_scale) => logo_chain.push_str(&custom_format(
|
||||
&format!(",{logo_scale}"),
|
||||
&[&config.processing.logo_scale],
|
||||
@ -387,13 +457,20 @@ fn overlay(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
match &ADVANCED_CONFIG.decoder.filters.overlay_logo {
|
||||
match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.overlay_logo.clone())
|
||||
{
|
||||
Some(overlay) => {
|
||||
if !overlay.starts_with(',') {
|
||||
logo_chain.push(',');
|
||||
}
|
||||
|
||||
logo_chain.push_str(&custom_format(overlay, &[&config.processing.logo_position]))
|
||||
logo_chain.push_str(&custom_format(
|
||||
&overlay,
|
||||
&[&config.processing.logo_position],
|
||||
))
|
||||
}
|
||||
None => logo_chain.push_str(&format!(
|
||||
"[l];[v][l]overlay={}:shortest=1",
|
||||
@ -405,7 +482,7 @@ fn overlay(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) {
|
||||
}
|
||||
}
|
||||
|
||||
fn extend_video(node: &mut Media, chain: &mut Filters) {
|
||||
fn extend_video(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) {
|
||||
if let Some(video_duration) = node
|
||||
.probe
|
||||
.as_ref()
|
||||
@ -416,8 +493,12 @@ fn extend_video(node: &mut Media, chain: &mut Filters) {
|
||||
if node.out - node.seek > video_duration - node.seek + 0.1 && node.duration >= node.out {
|
||||
let duration = (node.out - node.seek) - (video_duration - node.seek);
|
||||
|
||||
let tpad = match &ADVANCED_CONFIG.decoder.filters.tpad {
|
||||
Some(pad) => custom_format(pad, &[duration]),
|
||||
let tpad = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.tpad.clone())
|
||||
{
|
||||
Some(pad) => custom_format(&pad, &[duration]),
|
||||
None => format!("tpad=stop_mode=add:stop_duration={duration}"),
|
||||
};
|
||||
|
||||
@ -442,9 +523,13 @@ fn add_text(
|
||||
}
|
||||
}
|
||||
|
||||
fn add_audio(node: &Media, chain: &mut Filters, nr: i32) {
|
||||
let audio = match &ADVANCED_CONFIG.decoder.filters.aevalsrc {
|
||||
Some(aevalsrc) => custom_format(aevalsrc, &[node.out - node.seek]),
|
||||
fn add_audio(node: &Media, chain: &mut Filters, nr: i32, config: &PlayoutConfig) {
|
||||
let audio = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.aevalsrc.clone())
|
||||
{
|
||||
Some(aevalsrc) => custom_format(&aevalsrc, &[node.out - node.seek]),
|
||||
None => format!(
|
||||
"aevalsrc=0:channel_layout=stereo:duration={}:sample_rate=48000",
|
||||
node.out - node.seek
|
||||
@ -454,7 +539,7 @@ fn add_audio(node: &Media, chain: &mut Filters, nr: i32) {
|
||||
chain.add_filter(&audio, nr, Audio);
|
||||
}
|
||||
|
||||
fn extend_audio(node: &mut Media, chain: &mut Filters, nr: i32) {
|
||||
fn extend_audio(node: &mut Media, chain: &mut Filters, nr: i32, config: &PlayoutConfig) {
|
||||
if !Path::new(&node.audio).is_file() {
|
||||
if let Some(audio_duration) = node
|
||||
.probe
|
||||
@ -465,8 +550,12 @@ fn extend_audio(node: &mut Media, chain: &mut Filters, nr: i32) {
|
||||
{
|
||||
if node.out - node.seek > audio_duration - node.seek + 0.1 && node.duration >= node.out
|
||||
{
|
||||
let apad = match &ADVANCED_CONFIG.decoder.filters.apad {
|
||||
Some(apad) => custom_format(apad, &[node.out - node.seek]),
|
||||
let apad = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.apad.clone())
|
||||
{
|
||||
Some(apad) => custom_format(&apad, &[node.out - node.seek]),
|
||||
None => format!("apad=whole_dur={}", node.out - node.seek),
|
||||
};
|
||||
|
||||
@ -478,8 +567,12 @@ fn extend_audio(node: &mut Media, chain: &mut Filters, nr: i32) {
|
||||
|
||||
fn audio_volume(chain: &mut Filters, config: &PlayoutConfig, nr: i32) {
|
||||
if config.processing.volume != 1.0 {
|
||||
let volume = match &ADVANCED_CONFIG.decoder.filters.volume {
|
||||
Some(volume) => custom_format(volume, &[config.processing.volume]),
|
||||
let volume = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.volume.clone())
|
||||
{
|
||||
Some(volume) => custom_format(&volume, &[config.processing.volume]),
|
||||
None => format!("volume={}", config.processing.volume),
|
||||
};
|
||||
|
||||
@ -500,7 +593,13 @@ fn aspect_calc(aspect_string: &Option<String>, config: &PlayoutConfig) -> f64 {
|
||||
source_aspect
|
||||
}
|
||||
|
||||
pub fn split_filter(chain: &mut Filters, count: usize, nr: i32, filter_type: FilterType) {
|
||||
pub fn split_filter(
|
||||
chain: &mut Filters,
|
||||
count: usize,
|
||||
nr: i32,
|
||||
filter_type: FilterType,
|
||||
config: &PlayoutConfig,
|
||||
) {
|
||||
if count > 1 {
|
||||
let out_link = match filter_type {
|
||||
Audio => &mut chain.audio_out_link,
|
||||
@ -514,8 +613,12 @@ pub fn split_filter(chain: &mut Filters, count: usize, nr: i32, filter_type: Fil
|
||||
}
|
||||
}
|
||||
|
||||
let split = match &ADVANCED_CONFIG.decoder.filters.split {
|
||||
Some(split) => custom_format(split, &[count.to_string(), out_link.join("")]),
|
||||
let split = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.split.clone())
|
||||
{
|
||||
Some(split) => custom_format(&split, &[count.to_string(), out_link.join("")]),
|
||||
None => format!("split={count}{}", out_link.join("")),
|
||||
};
|
||||
|
||||
@ -582,7 +685,7 @@ pub fn filter_chains(
|
||||
if let Some(f) = config.out.output_filter.clone() {
|
||||
process_output_filters(config, &mut filters, &f)
|
||||
} else if config.out.output_count > 1 && !config.processing.audio_only {
|
||||
split_filter(&mut filters, config.out.output_count, 0, Video);
|
||||
split_filter(&mut filters, config.out.output_count, 0, Video, config);
|
||||
}
|
||||
|
||||
return filters;
|
||||
@ -598,7 +701,7 @@ pub fn filter_chains(
|
||||
let aspect = aspect_calc(&v_stream.display_aspect_ratio, config);
|
||||
let frame_per_sec = fps_calc(&v_stream.r_frame_rate, 1.0);
|
||||
|
||||
deinterlace(&v_stream.field_order, &mut filters);
|
||||
deinterlace(&v_stream.field_order, &mut filters, config);
|
||||
pad(aspect, &mut filters, v_stream, config);
|
||||
fps(frame_per_sec, &mut filters, config);
|
||||
scale(
|
||||
@ -610,14 +713,14 @@ pub fn filter_chains(
|
||||
);
|
||||
}
|
||||
|
||||
extend_video(node, &mut filters);
|
||||
extend_video(node, &mut filters, config);
|
||||
} else {
|
||||
fps(0.0, &mut filters, config);
|
||||
scale(None, None, 1.0, &mut filters, config);
|
||||
}
|
||||
|
||||
add_text(node, &mut filters, config, filter_chain);
|
||||
fade(node, &mut filters, 0, Video);
|
||||
fade(node, &mut filters, 0, Video, config);
|
||||
overlay(node, &mut filters, config);
|
||||
}
|
||||
|
||||
@ -653,7 +756,7 @@ pub fn filter_chains(
|
||||
.is_some()
|
||||
|| Path::new(&node.audio).is_file()
|
||||
{
|
||||
extend_audio(node, &mut filters, i);
|
||||
extend_audio(node, &mut filters, i, config);
|
||||
} else if node.unit == Decoder {
|
||||
if !node.source.contains("color=c=") {
|
||||
warn!(
|
||||
@ -662,14 +765,14 @@ pub fn filter_chains(
|
||||
);
|
||||
}
|
||||
|
||||
add_audio(node, &mut filters, i);
|
||||
add_audio(node, &mut filters, i, config);
|
||||
}
|
||||
|
||||
// add at least anull filter, for correct filter construction,
|
||||
// is important for split filter in HLS mode
|
||||
filters.add_filter("anull", i, Audio);
|
||||
|
||||
fade(node, &mut filters, i, Audio);
|
||||
fade(node, &mut filters, i, Audio, config);
|
||||
audio_volume(&mut filters, config, i);
|
||||
|
||||
custom(&proc_af, &mut filters, i, Audio);
|
||||
|
@ -7,7 +7,6 @@ use std::{
|
||||
use regex::Regex;
|
||||
|
||||
use crate::utils::{controller::ProcessUnit::*, custom_format, Media, PlayoutConfig};
|
||||
use crate::ADVANCED_CONFIG;
|
||||
|
||||
pub fn filter_node(
|
||||
config: &PlayoutConfig,
|
||||
@ -45,7 +44,11 @@ pub fn filter_node(
|
||||
.replace('%', "\\\\\\%")
|
||||
.replace(':', "\\:");
|
||||
|
||||
filter = match &ADVANCED_CONFIG.decoder.filters.drawtext_from_file {
|
||||
filter = match &config
|
||||
.advanced
|
||||
.clone()
|
||||
.and_then(|a| a.decoder.filters.drawtext_from_file)
|
||||
{
|
||||
Some(drawtext) => custom_format(drawtext, &[&escaped_text, &config.text.style, &font]),
|
||||
None => format!("drawtext=text='{escaped_text}':{}{font}", config.text.style),
|
||||
};
|
||||
@ -58,8 +61,12 @@ pub fn filter_node(
|
||||
}
|
||||
}
|
||||
|
||||
filter = match &ADVANCED_CONFIG.decoder.filters.drawtext_from_zmq {
|
||||
Some(drawtext) => custom_format(drawtext, &[&socket.replace(':', "\\:"), &filter_cmd]),
|
||||
filter = match config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.filters.drawtext_from_zmq.clone())
|
||||
{
|
||||
Some(drawtext) => custom_format(&drawtext, &[&socket.replace(':', "\\:"), &filter_cmd]),
|
||||
None => format!(
|
||||
"zmq=b=tcp\\\\://'{}',drawtext@dyntext={filter_cmd}",
|
||||
socket.replace(':', "\\:")
|
||||
|
@ -1,16 +1,8 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
extern crate log;
|
||||
extern crate simplelog;
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
pub mod filter;
|
||||
pub mod macros;
|
||||
pub mod utils;
|
||||
|
||||
use utils::advanced_config::AdvancedConfig;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref ADVANCED_CONFIG: Arc<AdvancedConfig> = Arc::new(AdvancedConfig::new());
|
||||
}
|
||||
|
@ -1,8 +1,4 @@
|
||||
use std::{
|
||||
env,
|
||||
fs::File,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::{fs::File, path::PathBuf};
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shlex::split;
|
||||
@ -67,19 +63,10 @@ pub struct Filters {
|
||||
}
|
||||
|
||||
impl AdvancedConfig {
|
||||
pub fn new() -> Self {
|
||||
pub fn new(cfg_path: PathBuf) -> Self {
|
||||
let mut config: AdvancedConfig = Default::default();
|
||||
let mut config_path = PathBuf::from("/etc/ffplayout/advanced.yml");
|
||||
|
||||
if !config_path.is_file() {
|
||||
if Path::new("./assets/advanced.yml").is_file() {
|
||||
config_path = PathBuf::from("./assets/advanced.yml")
|
||||
} else if let Some(p) = env::current_exe().ok().as_ref().and_then(|op| op.parent()) {
|
||||
config_path = p.join("advanced.yml")
|
||||
};
|
||||
}
|
||||
|
||||
if let Ok(f) = File::open(&config_path) {
|
||||
if let Ok(f) = File::open(&cfg_path) {
|
||||
config = match serde_yaml::from_reader(f) {
|
||||
Ok(yaml) => yaml,
|
||||
Err(_) => AdvancedConfig::default(),
|
||||
|
@ -11,7 +11,7 @@ use log::LevelFilter;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use shlex::split;
|
||||
|
||||
use crate::ADVANCED_CONFIG;
|
||||
use crate::AdvancedConfig;
|
||||
|
||||
use super::vec_strings;
|
||||
use crate::utils::{free_tcp_socket, home_dir, time_to_sec, OutputMode::*};
|
||||
@ -145,6 +145,8 @@ pub struct Source {
|
||||
/// This we init ones, when ffplayout is starting and use them globally in the hole program.
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
pub struct PlayoutConfig {
|
||||
#[serde(default, skip_serializing, skip_deserializing)]
|
||||
pub advanced: Option<AdvancedConfig>,
|
||||
pub general: General,
|
||||
pub rpc_server: RpcServer,
|
||||
pub mail: Mail,
|
||||
@ -362,7 +364,7 @@ fn default_channels() -> u8 {
|
||||
|
||||
impl PlayoutConfig {
|
||||
/// Read config from YAML file, and set some extra config values.
|
||||
pub fn new(cfg_path: Option<PathBuf>) -> Self {
|
||||
pub fn new(cfg_path: Option<PathBuf>, advanced_path: Option<PathBuf>) -> Self {
|
||||
let mut config_path = PathBuf::from("/etc/ffplayout/ffplayout.yml");
|
||||
|
||||
if let Some(cfg) = cfg_path {
|
||||
@ -389,6 +391,11 @@ impl PlayoutConfig {
|
||||
|
||||
let mut config: PlayoutConfig =
|
||||
serde_yaml::from_reader(f).expect("Could not read config file.");
|
||||
|
||||
if let Some(adv_path) = advanced_path {
|
||||
config.advanced = Some(AdvancedConfig::new(adv_path))
|
||||
}
|
||||
|
||||
config.general.generate = None;
|
||||
|
||||
config.general.config_path = config_path.to_string_lossy().to_string();
|
||||
@ -430,12 +437,16 @@ impl PlayoutConfig {
|
||||
}
|
||||
|
||||
let mut process_cmd = vec_strings![];
|
||||
let advanced_output_cmd = config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.output_cmd.clone());
|
||||
|
||||
if config.processing.audio_only {
|
||||
process_cmd.append(&mut vec_strings!["-vn"]);
|
||||
} else if config.processing.copy_video {
|
||||
process_cmd.append(&mut vec_strings!["-c:v", "copy"]);
|
||||
} else if let Some(decoder_cmd) = &ADVANCED_CONFIG.decoder.output_cmd {
|
||||
} else if let Some(decoder_cmd) = &advanced_output_cmd {
|
||||
process_cmd.append(&mut decoder_cmd.clone());
|
||||
} else {
|
||||
let bitrate = format!(
|
||||
@ -470,7 +481,7 @@ impl PlayoutConfig {
|
||||
|
||||
if config.processing.copy_audio {
|
||||
process_cmd.append(&mut vec_strings!["-c:a", "copy"]);
|
||||
} else if ADVANCED_CONFIG.decoder.output_cmd.is_none() {
|
||||
} else if advanced_output_cmd.is_none() {
|
||||
process_cmd.append(&mut pre_audio_codec(
|
||||
&config.processing.custom_filter,
|
||||
&config.ingest.custom_filter,
|
||||
@ -530,7 +541,7 @@ impl PlayoutConfig {
|
||||
|
||||
impl Default for PlayoutConfig {
|
||||
fn default() -> Self {
|
||||
Self::new(None)
|
||||
Self::new(None, None)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,6 @@ use crate::utils::{
|
||||
JsonPlaylist, Media, OutputMode::Null, PlayerControl, PlayoutConfig, FFMPEG_IGNORE_ERRORS,
|
||||
IMAGE_FORMAT,
|
||||
};
|
||||
use crate::ADVANCED_CONFIG;
|
||||
|
||||
/// Validate a single media file.
|
||||
///
|
||||
@ -38,7 +37,11 @@ fn check_media(
|
||||
|
||||
let mut process_length = 0.1;
|
||||
|
||||
if let Some(decoder_input_cmd) = &ADVANCED_CONFIG.decoder.input_cmd {
|
||||
if let Some(decoder_input_cmd) = config
|
||||
.advanced
|
||||
.as_ref()
|
||||
.and_then(|a| a.decoder.input_cmd.clone())
|
||||
{
|
||||
dec_cmd.append(&mut decoder_input_cmd.clone());
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ fn timed_stop(sec: u64, proc_ctl: ProcessControl) {
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_gen_source() {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
let mut config = PlayoutConfig::new(None, None);
|
||||
config.general.skip_validation = true;
|
||||
config.mail.recipient = "".into();
|
||||
config.processing.mode = Playlist;
|
||||
@ -76,7 +76,7 @@ fn test_gen_source() {
|
||||
100,
|
||||
);
|
||||
|
||||
assert_eq!(valid_media.out, 1.9);
|
||||
assert_eq!(valid_media.out, 1.2);
|
||||
|
||||
let mut no_valid_source_with_probe = Media::new(0, "assets/media_mix/av_snc.mp4", true);
|
||||
no_valid_source_with_probe.duration = 30.0;
|
||||
@ -97,7 +97,7 @@ fn test_gen_source() {
|
||||
#[serial]
|
||||
#[ignore]
|
||||
fn playlist_missing() {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
let mut config = PlayoutConfig::new(None, None);
|
||||
config.general.skip_validation = true;
|
||||
config.mail.recipient = "".into();
|
||||
config.processing.mode = Playlist;
|
||||
@ -140,7 +140,7 @@ fn playlist_missing() {
|
||||
#[serial]
|
||||
#[ignore]
|
||||
fn playlist_next_missing() {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
let mut config = PlayoutConfig::new(None, None);
|
||||
config.general.skip_validation = true;
|
||||
config.mail.recipient = "".into();
|
||||
config.processing.mode = Playlist;
|
||||
@ -183,7 +183,7 @@ fn playlist_next_missing() {
|
||||
#[serial]
|
||||
#[ignore]
|
||||
fn playlist_to_short() {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
let mut config = PlayoutConfig::new(None, None);
|
||||
config.general.skip_validation = true;
|
||||
config.mail.recipient = "".into();
|
||||
config.processing.mode = Playlist;
|
||||
@ -226,7 +226,7 @@ fn playlist_to_short() {
|
||||
#[serial]
|
||||
#[ignore]
|
||||
fn playlist_init_after_list_end() {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
let mut config = PlayoutConfig::new(None, None);
|
||||
config.general.skip_validation = true;
|
||||
config.mail.recipient = "".into();
|
||||
config.processing.mode = Playlist;
|
||||
@ -269,7 +269,7 @@ fn playlist_init_after_list_end() {
|
||||
#[serial]
|
||||
#[ignore]
|
||||
fn playlist_change_at_midnight() {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
let mut config = PlayoutConfig::new(None, None);
|
||||
config.general.skip_validation = true;
|
||||
config.mail.recipient = "".into();
|
||||
config.processing.mode = Playlist;
|
||||
@ -312,7 +312,7 @@ fn playlist_change_at_midnight() {
|
||||
#[serial]
|
||||
#[ignore]
|
||||
fn playlist_change_before_midnight() {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
let mut config = PlayoutConfig::new(None, None);
|
||||
config.general.skip_validation = true;
|
||||
config.mail.recipient = "".into();
|
||||
config.processing.mode = Playlist;
|
||||
@ -355,7 +355,7 @@ fn playlist_change_before_midnight() {
|
||||
#[serial]
|
||||
#[ignore]
|
||||
fn playlist_change_at_six() {
|
||||
let mut config = PlayoutConfig::new(None);
|
||||
let mut config = PlayoutConfig::new(None, None);
|
||||
config.general.skip_validation = true;
|
||||
config.mail.recipient = "".into();
|
||||
config.processing.mode = Playlist;
|
||||
|
Loading…
Reference in New Issue
Block a user