work on advanced settings, #558

This commit is contained in:
jb-alvarado 2024-03-05 09:30:05 +01:00
parent 5b68fc4fbc
commit fb2fee92af
14 changed files with 296 additions and 69 deletions

9
Cargo.lock generated
View File

@ -1231,7 +1231,7 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "ffplayout"
version = "0.20.5"
version = "0.21.0"
dependencies = [
"chrono",
"clap",
@ -1253,7 +1253,7 @@ dependencies = [
[[package]]
name = "ffplayout-api"
version = "0.20.5"
version = "0.21.0"
dependencies = [
"actix-files",
"actix-multipart",
@ -1292,13 +1292,14 @@ dependencies = [
[[package]]
name = "ffplayout-lib"
version = "0.20.5"
version = "0.21.0"
dependencies = [
"chrono",
"crossbeam-channel",
"derive_more",
"ffprobe",
"file-rotate",
"lazy_static",
"lettre",
"lexical-sort",
"log",
@ -3462,7 +3463,7 @@ dependencies = [
[[package]]
name = "tests"
version = "0.20.5"
version = "0.21.0"
dependencies = [
"chrono",
"crossbeam-channel",

View File

@ -4,7 +4,7 @@ default-members = ["ffplayout-api", "ffplayout-engine", "tests"]
resolver = "2"
[workspace.package]
version = "0.20.5"
version = "0.21.0"
license = "GPL-3.0"
repository = "https://github.com/ffplayout/ffplayout"
authors = ["Jonathan Baecker <jonbae77@gmail.com>"]

29
assets/advanced.yml Normal file
View File

@ -0,0 +1,29 @@
help: Changing these settings is for advanced users only! There will be no support or guarantee that it will be stable after changing them.
decoder:
input_param:
# output_param get also applied to ingest instance.
output_param:
filters:
deinterlace: # yadif=0:-1:0
pad_scale_w: # scale={}:-1,
pad_scale_h: # scale=-1:{},
pad_video: # '{}pad=max(iw\\,ih*({0}/{1})):ow/({0}/{1}):(ow-iw)/2:(oh-ih)/2'
fps: # fps={}
scale: # scale={}:{}
set_dar: # setdar=dar={}
fade_in: # '{}fade=in:st=0:d=0.5'
fade_out: # '{}fade=out:st={}:d=1.0'
overlay_logo_scale: # ',scale={}'
overlay_logo: # null[v];movie={}:loop=0,setpts=N/(FRAME_RATE*TB),format=rgba,colorchannelmixer=aa={}{}[l];[v][l]{}:shortest=1
overlay_logo_fade_in: # ',fade=in:st=0:d=1.0:alpha=1'
overlay_logo_fade_out: # ',fade=out:st={}:d=1.0:alpha=1'
tpad: # tpad=stop_mode=add:stop_duration={}
drawtext_from_file: # drawtext=text='{}':{}{}
drawtext_from_zmq: # zmq=b=tcp\\\\://'{}',drawtext@dyntext={}
apad: # apad=whole_dur={}
volume: # volume={}
split: # split={}{}
encoder:
input_param:
ingest:
input_param:

View File

@ -1,26 +0,0 @@
help: Changing these settings is for advanced users only! There will be no support or guarantee that it will be stable after changing it.
decoder:
input_options:
output_options:
filters:
deinterlace: yadif=0:-1:0
pad_scale_w: scale={}:-1,
pad_scale_h: scale=-1:{},
pad_video: '{}pad=max(iw\\,ih*({0}/{1})):ow/({0}/{1}):(ow-iw)/2:(oh-ih)/2'
fps: fps={}
scale: scale={}:{}
set_dar: setdar=dar={}
fade_in: {}fade=in:st=0:d=0.5
fade_out: {}fade=out:st={}:d=1.0
overlay_logo_scale: ',scale={}'
overlay_logo: null[v];movie={}:loop=0,setpts=N/(FRAME_RATE*TB),format=rgba,colorchannelmixer=aa={}{}[l];[v][l]{}:shortest=1
overlay_logo_fade_in: ',fade=in:st=0:d=1.0:alpha=1'
overlay_logo_fade_out: ',fade=out:st={}:d=1.0:alpha=1'
tpad: tpad=stop_mode=add:stop_duration={}
drawtext_from_file: drawtext=text='{}':{}{}
drawtext_from_zmq: zmq=b=tcp\\\\://'{}',drawtext@dyntext={}
apad: apad=whole_dur={}
volume: volume={}
split: split={}{}
encoder:
input_options:

View File

@ -14,7 +14,7 @@ use ffplayout_lib::{
controller::ProcessUnit::*, test_tcp_port, Media, PlayoutConfig, ProcessControl,
FFMPEG_IGNORE_ERRORS, FFMPEG_UNRECOVERABLE_ERRORS,
},
vec_strings,
vec_strings, ADVANCED_CONFIG,
};
fn server_monitor(
@ -61,6 +61,10 @@ pub fn ingest_server(
dummy_media.unit = Ingest;
dummy_media.add_filter(&config, &None);
if let Some(ingest_input_cmd) = &ADVANCED_CONFIG.lock().unwrap().ingest.input_cmd {
server_cmd.append(&mut ingest_input_cmd.clone());
}
server_cmd.append(&mut stream_input.clone());
if let Some(mut filter) = dummy_media.filter {

View File

@ -2,7 +2,7 @@ use std::process::{self, Command, Stdio};
use simplelog::*;
use ffplayout_lib::{filter::v_drawtext, utils::PlayoutConfig, vec_strings};
use ffplayout_lib::{filter::v_drawtext, utils::PlayoutConfig, vec_strings, ADVANCED_CONFIG};
/// Desktop Output
///
@ -10,16 +10,18 @@ use ffplayout_lib::{filter::v_drawtext, utils::PlayoutConfig, vec_strings};
pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child {
let mut enc_filter: Vec<String> = vec![];
let mut enc_cmd = vec_strings![
"-hide_banner",
"-nostats",
"-v",
log_format,
let mut enc_cmd = vec_strings!["-hide_banner", "-nostats", "-v", log_format];
if let Some(encoder_input_cmd) = &ADVANCED_CONFIG.lock().unwrap().encoder.input_cmd {
enc_cmd.append(&mut encoder_input_cmd.clone());
}
enc_cmd.append(&mut vec_strings![
"-i",
"pipe:0",
"-window_title",
"ffplayout"
];
]);
if let Some(mut cmd) = config.out.output_cmd.clone() {
if !cmd.iter().any(|i| {

View File

@ -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,
vec_strings, ADVANCED_CONFIG,
};
/// Ingest Server for HLS
@ -47,10 +47,15 @@ fn ingest_to_hls_server(
let mut server_prefix = vec_strings!["-hide_banner", "-nostats", "-v", "level+info"];
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.unit = Ingest;
if let Some(ingest_input_cmd) = &ADVANCED_CONFIG.lock().unwrap().ingest.input_cmd {
server_prefix.append(&mut ingest_input_cmd.clone());
}
server_prefix.append(&mut stream_input.clone());
let mut is_running;
if let Some(url) = stream_input.iter().find(|s| s.contains("://")) {
@ -197,6 +202,10 @@ 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.lock().unwrap().encoder.input_cmd {
enc_prefix.append(&mut encoder_input_cmd.clone());
}
let mut read_rate = 1.0;
if let Some(begin) = &node.begin {

View File

@ -19,11 +19,14 @@ pub use hls::write_hls;
use crate::input::{ingest_server, source_generator};
use crate::utils::task_runner;
use ffplayout_lib::utils::{
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::vec_strings;
/// Player
///
@ -130,6 +133,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.lock().unwrap().decoder.input_cmd {
dec_cmd.append(&mut decoder_input_cmd.clone());
}
dec_cmd.append(&mut cmd);
if let Some(mut filter) = node.filter {

View File

@ -14,6 +14,7 @@ crossbeam-channel = "0.5"
derive_more = "0.99"
ffprobe = "0.3"
file-rotate = "0.7"
lazy_static = "1.4"
lettre = { version = "0.11", features = ["builder", "rustls-tls", "smtp-transport"], default-features = false }
lexical-sort = "0.3"
log = "0.4"

View File

@ -11,8 +11,10 @@ mod custom;
pub mod v_drawtext;
use crate::utils::{
controller::ProcessUnit::*, fps_calc, is_close, Media, OutputMode::*, PlayoutConfig,
controller::ProcessUnit::*, custom_format, fps_calc, is_close, Media, OutputMode::*,
PlayoutConfig,
};
use crate::ADVANCED_CONFIG;
use super::vec_strings;
@ -182,7 +184,9 @@ impl Default for Filters {
}
fn deinterlace(field_order: &Option<String>, chain: &mut Filters) {
if let Some(order) = field_order {
if let Some(deinterlace) = &ADVANCED_CONFIG.lock().unwrap().decoder.filters.deinterlace {
chain.add_filter(&deinterlace, 0, Video)
} else if let Some(order) = field_order {
if order != "progressive" {
chain.add_filter("yadif=0:-1:0", 0, Video)
}
@ -193,27 +197,60 @@ fn pad(aspect: f64, chain: &mut Filters, v_stream: &ffprobe::Stream, config: &Pl
if !is_close(aspect, config.processing.aspect, 0.03) {
let mut scale = String::new();
let advanced = ADVANCED_CONFIG.lock().unwrap();
if let (Some(w), Some(h)) = (v_stream.width, v_stream.height) {
if w > config.processing.width && aspect > config.processing.aspect {
scale = format!("scale={}:-1,", config.processing.width);
match &advanced.decoder.filters.pad_scale_w {
Some(pad_scale_w) => {
scale = custom_format(pad_scale_w, &[&config.processing.width])
}
None => scale = format!("scale={}:-1,", config.processing.width),
};
} else if h > config.processing.height && aspect < config.processing.aspect {
scale = format!("scale=-1:{},", config.processing.height);
match &advanced.decoder.filters.pad_scale_h {
Some(pad_scale_h) => {
scale = custom_format(pad_scale_h, &[&config.processing.width])
}
None => scale = format!("scale=-1:{},", config.processing.height),
};
}
}
if let Some(pad_video) = &advanced.decoder.filters.pad_video {
chain.add_filter(
&custom_format(
pad_video,
&[
&scale,
&config.processing.width.to_string(),
&config.processing.height.to_string(),
],
),
0,
Video,
)
} else {
chain.add_filter(
&format!(
"{scale}pad=max(iw\\,ih*({0}/{1})):ow/({0}/{1}):(ow-iw)/2:(oh-ih)/2",
config.processing.width, config.processing.height
"{}pad=max(iw\\,ih*({1}/{2})):ow/({1}/{2}):(ow-iw)/2:(oh-ih)/2",
scale, config.processing.width, config.processing.height
),
0,
Video,
)
}
}
}
fn fps(fps: f64, chain: &mut Filters, config: &PlayoutConfig) {
if fps != config.processing.fps {
chain.add_filter(&format!("fps={}", config.processing.fps), 0, Video)
let advanced = ADVANCED_CONFIG.lock().unwrap();
match &advanced.decoder.filters.fps {
Some(fps) => chain.add_filter(&custom_format(fps, &[&config.processing.fps]), 0, Video),
None => chain.add_filter(&format!("fps={}", config.processing.fps), 0, Video),
}
}
}

View File

@ -1,6 +1,17 @@
use std::sync::{Arc, Mutex};
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<Mutex<AdvancedConfig>> =
Arc::new(Mutex::new(AdvancedConfig::new()));
}

View File

@ -0,0 +1,101 @@
use std::{
env,
fs::File,
path::{Path, PathBuf},
};
use serde::{Deserialize, Serialize};
use shlex::split;
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct AdvancedConfig {
pub help: Option<String>,
pub decoder: DecoderConfig,
pub encoder: EncoderConfig,
pub ingest: IngestConfig,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct DecoderConfig {
pub input_param: Option<String>,
pub output_param: Option<String>,
pub filters: Filters,
#[serde(skip_serializing, skip_deserializing)]
pub input_cmd: Option<Vec<String>>,
#[serde(skip_serializing, skip_deserializing)]
pub output_cmd: Option<Vec<String>>,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct EncoderConfig {
pub input_param: Option<String>,
#[serde(skip_serializing, skip_deserializing)]
pub input_cmd: Option<Vec<String>>,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct IngestConfig {
pub input_param: Option<String>,
#[serde(skip_serializing, skip_deserializing)]
pub input_cmd: Option<Vec<String>>,
}
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
pub struct Filters {
pub deinterlace: Option<String>,
pub pad_scale_w: Option<String>,
pub pad_scale_h: Option<String>,
pub pad_video: Option<String>,
pub fps: Option<String>,
pub scale: Option<String>,
pub set_dar: Option<String>,
pub fade_in: Option<String>,
pub fade_out: Option<String>,
pub overlay_logo_scale: Option<String>,
pub overlay_logo: Option<String>,
pub overlay_logo_fade_in: Option<String>,
pub overlay_logo_fade_out: Option<String>,
pub tpad: Option<String>,
pub drawtext_from_file: Option<String>,
pub drawtext_from_zmq: Option<String>,
pub apad: Option<String>,
pub volume: Option<String>,
pub split: Option<String>,
}
impl AdvancedConfig {
pub fn new() -> 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) {
config = serde_yaml::from_reader(f).expect("Could not read advanced config file");
if let Some(input_parm) = &config.decoder.input_param {
config.decoder.input_cmd = split(&input_parm);
}
if let Some(output_param) = &config.decoder.output_param {
config.decoder.output_cmd = split(&output_param);
}
if let Some(input_param) = &config.encoder.input_param {
config.encoder.input_cmd = split(&input_param);
}
if let Some(input_param) = &config.ingest.input_param {
config.ingest.input_cmd = split(&input_param);
}
};
config
}
}

View File

@ -11,6 +11,8 @@ use log::LevelFilter;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use shlex::split;
use crate::ADVANCED_CONFIG;
use super::vec_strings;
use crate::utils::{free_tcp_socket, home_dir, time_to_sec, OutputMode::*};
@ -424,6 +426,19 @@ impl PlayoutConfig {
config.processing.audio_tracks = 1
}
let mut process_cmd = vec_strings![];
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.lock().unwrap().decoder.output_cmd {
if !decoder_cmd.contains(&"-r".to_string()) {
process_cmd.append(&mut vec_strings!["-r", &config.processing.fps]);
}
process_cmd.append(&mut decoder_cmd.clone());
} else {
let bitrate = format!(
"{}k",
config.processing.width * config.processing.height / 16
@ -434,13 +449,6 @@ impl PlayoutConfig {
(config.processing.width * config.processing.height / 16) / 2
);
let mut process_cmd = vec_strings![];
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 {
process_cmd.append(&mut vec_strings![
"-pix_fmt",
"yuv420p",
@ -463,7 +471,7 @@ impl PlayoutConfig {
if config.processing.copy_audio {
process_cmd.append(&mut vec_strings!["-c:a", "copy"]);
} else {
} else if ADVANCED_CONFIG.lock().unwrap().decoder.output_cmd.is_none() {
process_cmd.append(&mut pre_audio_codec(
&config.processing.custom_filter,
&config.ingest.custom_filter,

View File

@ -1,5 +1,6 @@
use std::{
ffi::OsStr,
fmt,
fs::{self, metadata, File},
io::{BufRead, BufReader, Error},
net::TcpListener,
@ -21,6 +22,7 @@ use serde::{de::Deserializer, Deserialize, Serialize};
use serde_json::json;
use simplelog::*;
pub mod advanced_config;
pub mod config;
pub mod controller;
pub mod errors;
@ -928,6 +930,46 @@ pub fn parse_log_level_filter(s: &str) -> Result<LevelFilter, &'static str> {
}
}
pub fn custom_format<T: fmt::Display>(template: &str, args: &[T]) -> String {
let mut filled_template = String::new();
let mut arg_iter = args.iter().map(|x| format!("{}", x));
let mut template_iter = template.chars();
while let Some(c) = template_iter.next() {
if c == '{' {
if let Some(nc) = template_iter.next() {
if nc == '{' {
filled_template.push('{');
} else if nc == '}' {
if let Some(arg) = arg_iter.next() {
filled_template.push_str(&arg);
} else {
filled_template.push(c);
filled_template.push(nc);
}
} else if let Some(n) = nc.to_digit(10) {
filled_template.push_str(&args[n as usize].to_string());
} else {
filled_template.push(nc);
}
}
} else if c == '}' {
if let Some(nc) = template_iter.next() {
if nc == '}' {
filled_template.push('}');
continue;
} else {
filled_template.push(nc);
}
}
} else {
filled_template.push(c);
}
}
filled_template
}
pub fn home_dir() -> Option<PathBuf> {
home_dir_inner()
}