From 4cd6b885ffe017cd4ea1b6a7c547d98fe2c6d119 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Mon, 9 May 2022 15:16:40 +0200 Subject: [PATCH 01/13] remove str/string conversions in vectors, log ingest url with match --- src/input/ingest.rs | 19 ++++++++----------- src/input/playlist.rs | 2 +- src/output/desktop.rs | 5 +++-- src/output/hls.rs | 31 ++++++++++++++---------------- src/output/mod.rs | 14 +++++++------- src/output/stream.rs | 25 ++++++++++++------------ src/utils/config.rs | 44 ++++++++++++++++++++++--------------------- src/utils/logging.rs | 6 +++--- src/utils/mod.rs | 7 +++++++ 9 files changed, 79 insertions(+), 74 deletions(-) diff --git a/src/input/ingest.rs b/src/input/ingest.rs index 05961723..225b5a35 100644 --- a/src/input/ingest.rs +++ b/src/input/ingest.rs @@ -10,6 +10,7 @@ use simplelog::*; use crate::filter::ingest_filter::filter_cmd; use crate::utils::{stderr_reader, GlobalConfig, Ingest, ProcessControl}; +use crate::vec_strings; /// ffmpeg Ingest Server /// @@ -21,22 +22,18 @@ pub fn ingest_server( ) -> Result<(), Error> { let config = GlobalConfig::global(); let mut buffer: [u8; 65088] = [0; 65088]; - let filter_list = filter_cmd(); - - let mut server_cmd = vec!["-hide_banner", "-nostats", "-v", log_format.as_str()]; + let mut server_cmd = vec_strings!["-hide_banner", "-nostats", "-v", log_format]; let stream_input = config.ingest.input_cmd.clone().unwrap(); - let stream_settings = config.processing.settings.clone().unwrap(); - server_cmd.append(&mut stream_input.iter().map(String::as_str).collect()); - server_cmd.append(&mut filter_list.iter().map(String::as_str).collect()); - server_cmd.append(&mut stream_settings.iter().map(String::as_str).collect()); + server_cmd.append(&mut stream_input.clone()); + server_cmd.append(&mut filter_cmd()); + server_cmd.append(&mut config.processing.settings.clone().unwrap()); let mut is_running; - info!( - "Start ingest server, listening on: {}", - stream_input.last().unwrap() - ); + if let Some(url) = stream_input.iter().find(|s| s.contains("://")) { + info!("Start ingest server, listening on: {url}",); + }; debug!( "Server CMD: \"ffmpeg {}\"", diff --git a/src/input/playlist.rs b/src/input/playlist.rs index 36a2dd61..3b1d1f4c 100644 --- a/src/input/playlist.rs +++ b/src/input/playlist.rs @@ -243,7 +243,6 @@ impl Iterator for CurrentProgram { fn next(&mut self) -> Option { if self.playout_stat.list_init.load(Ordering::SeqCst) { - debug!("Playlist init"); self.check_update(true); if self.json_path.is_some() { @@ -451,6 +450,7 @@ fn gen_source(mut node: Media) -> Media { /// Handle init clip, but this clip can be the last one in playlist, /// this we have to figure out and calculate the right length. fn handle_list_init(mut node: Media) -> Media { + debug!("Playlist init"); let (_, total_delta) = get_delta(&node.begin.unwrap()); let mut out = node.out; diff --git a/src/output/desktop.rs b/src/output/desktop.rs index a94fa2f5..51dbbf56 100644 --- a/src/output/desktop.rs +++ b/src/output/desktop.rs @@ -4,6 +4,7 @@ use simplelog::*; use crate::filter::v_drawtext; use crate::utils::{GlobalConfig, Media}; +use crate::vec_strings; /// Desktop Output /// @@ -13,7 +14,7 @@ pub fn output(log_format: &str) -> process::Child { let mut enc_filter: Vec = vec![]; - let mut enc_cmd = vec!["-hide_banner", "-nostats", "-v", log_format, "-i", "pipe:0"]; + let mut enc_cmd = vec_strings!["-hide_banner", "-nostats", "-v", log_format, "-i", "pipe:0"]; if config.text.add_text && !config.text.over_pre { info!( @@ -26,7 +27,7 @@ pub fn output(log_format: &str) -> process::Child { enc_filter = vec!["-vf".to_string(), filter]; } - enc_cmd.append(&mut enc_filter.iter().map(String::as_str).collect()); + enc_cmd.append(&mut enc_filter); debug!( "Encoder CMD: \"ffplay {}\"", diff --git a/src/output/hls.rs b/src/output/hls.rs index deabfbf2..166fa407 100644 --- a/src/output/hls.rs +++ b/src/output/hls.rs @@ -33,6 +33,7 @@ use crate::utils::{ sec_to_time, stderr_reader, Decoder, GlobalConfig, Ingest, PlayerControl, PlayoutStatus, ProcessControl, }; +use crate::vec_strings; fn format_line(line: String, level: &str) -> String { line.replace(&format!("[{level: >5}] "), "") @@ -44,23 +45,20 @@ fn ingest_to_hls_server( mut proc_control: ProcessControl, ) -> Result<(), Error> { let config = GlobalConfig::global(); - let dec_settings = config.out.clone().output_cmd.unwrap(); let playlist_init = playout_stat.list_init; - let filter_list = filter_cmd(); - let mut server_cmd = vec!["-hide_banner", "-nostats", "-v", "level+info"]; + let mut server_cmd = vec_strings!["-hide_banner", "-nostats", "-v", "level+info"]; let stream_input = config.ingest.input_cmd.clone().unwrap(); - server_cmd.append(&mut stream_input.iter().map(String::as_str).collect()); - server_cmd.append(&mut filter_list.iter().map(String::as_str).collect()); - server_cmd.append(&mut dec_settings.iter().map(String::as_str).collect()); + server_cmd.append(&mut stream_input.clone()); + server_cmd.append(&mut filter_cmd()); + server_cmd.append(&mut config.out.clone().output_cmd.unwrap()); let mut is_running; - info!( - "Start ingest server, listening on: {}", - stream_input.last().unwrap() - ); + if let Some(url) = stream_input.iter().find(|s| s.contains("://")) { + info!("Start ingest server, listening on: {url}",); + }; debug!( "Server CMD: \"ffmpeg {}\"", @@ -137,7 +135,6 @@ pub fn write_hls( mut proc_control: ProcessControl, ) { let config = GlobalConfig::global(); - let dec_settings = config.out.clone().output_cmd.unwrap(); let ff_log_format = format!("level+{}", config.logging.ffmpeg_level.to_lowercase()); let play_stat = playout_stat.clone(); let proc_control_c = proc_control.clone(); @@ -158,7 +155,7 @@ pub fn write_hls( for node in get_source { *play_control.current_media.lock().unwrap() = Some(node.clone()); - let cmd = match node.cmd { + let mut cmd = match node.cmd { Some(cmd) => cmd, None => break, }; @@ -173,15 +170,15 @@ pub fn write_hls( node.source ); - let filter = node.filter.unwrap(); - let mut dec_cmd = vec!["-hide_banner", "-nostats", "-v", ff_log_format.as_str()]; - dec_cmd.append(&mut cmd.iter().map(String::as_str).collect()); + 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.iter().map(String::as_str).collect()); + dec_cmd.append(&mut filter); } - dec_cmd.append(&mut dec_settings.iter().map(String::as_str).collect()); + dec_cmd.append(&mut config.out.clone().output_cmd.unwrap()); debug!( "HLS writer CMD: \"ffmpeg {}\"", diff --git a/src/output/mod.rs b/src/output/mod.rs index fd2ece80..c93d4446 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -20,6 +20,7 @@ use crate::utils::{ sec_to_time, stderr_reader, Decoder, Encoder, GlobalConfig, PlayerControl, PlayoutStatus, ProcessControl, }; +use crate::vec_strings; /// Player /// @@ -36,7 +37,6 @@ pub fn player( mut proc_control: ProcessControl, ) { let config = GlobalConfig::global(); - let dec_settings = config.processing.clone().settings.unwrap(); let ff_log_format = format!("level+{}", config.logging.ffmpeg_level.to_lowercase()); let mut buffer = [0; 65088]; let mut live_on = false; @@ -80,7 +80,7 @@ pub fn player( 'source_iter: for node in get_source { *play_control.current_media.lock().unwrap() = Some(node.clone()); - let cmd = match node.cmd { + let mut cmd = match node.cmd { Some(cmd) => cmd, None => break, }; @@ -95,15 +95,15 @@ pub fn player( node.source ); - let filter = node.filter.unwrap(); - let mut dec_cmd = vec!["-hide_banner", "-nostats", "-v", ff_log_format.as_str()]; - dec_cmd.append(&mut cmd.iter().map(String::as_str).collect()); + 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.iter().map(String::as_str).collect()); + dec_cmd.append(&mut filter); } - dec_cmd.append(&mut dec_settings.iter().map(String::as_str).collect()); + dec_cmd.append(&mut config.processing.clone().settings.unwrap()); debug!( "Decoder CMD: \"ffmpeg {}\"", diff --git a/src/output/stream.rs b/src/output/stream.rs index 9c203a09..32f86c81 100644 --- a/src/output/stream.rs +++ b/src/output/stream.rs @@ -7,6 +7,7 @@ use simplelog::*; use crate::filter::v_drawtext; use crate::utils::{GlobalConfig, Media}; +use crate::vec_strings; /// Streaming Output /// @@ -14,18 +15,18 @@ use crate::utils::{GlobalConfig, Media}; pub fn output(log_format: &str) -> process::Child { let config = GlobalConfig::global(); let mut enc_filter: Vec = vec![]; - let mut preview: Vec<&str> = vec![]; - let preview_cmd = config.out.preview_cmd.as_ref().unwrap().clone(); - let output_cmd = config.out.output_cmd.as_ref().unwrap().clone(); + let mut preview: Vec = vec_strings![]; + let mut preview_cmd = config.out.preview_cmd.as_ref().unwrap().clone(); + let mut output_cmd = config.out.output_cmd.as_ref().unwrap().clone(); - let mut enc_cmd = vec![ + let mut enc_cmd = vec_strings![ "-hide_banner", "-nostats", "-v", log_format, "-re", "-i", - "pipe:0", + "pipe:0" ]; if config.text.add_text && !config.text.over_pre { @@ -34,25 +35,25 @@ pub fn output(log_format: &str) -> process::Child { config.text.bind_address ); - let mut filter: String = "[0:v]null,".to_string(); + let mut filter = "[0:v]null,".to_string(); filter.push_str(v_drawtext::filter_node(&mut Media::new(0, String::new(), false)).as_str()); if config.out.preview { filter.push_str(",split=2[v_out1][v_out2]"); - preview = vec!["-map", "[v_out1]", "-map", "0:a"]; - preview.append(&mut preview_cmd.iter().map(String::as_str).collect()); - preview.append(&mut vec!["-map", "[v_out2]", "-map", "0:a"]); + preview = vec_strings!["-map", "[v_out1]", "-map", "0:a"]; + preview.append(&mut preview_cmd); + preview.append(&mut vec_strings!["-map", "[v_out2]", "-map", "0:a"]); } enc_filter = vec!["-filter_complex".to_string(), filter]; } else if config.out.preview { - preview = preview_cmd.iter().map(String::as_str).collect() + preview = preview_cmd } - enc_cmd.append(&mut enc_filter.iter().map(String::as_str).collect()); + enc_cmd.append(&mut enc_filter); enc_cmd.append(&mut preview); - enc_cmd.append(&mut output_cmd.iter().map(String::as_str).collect()); + enc_cmd.append(&mut output_cmd); debug!( "Encoder CMD: \"ffmpeg {}\"", diff --git a/src/utils/config.rs b/src/utils/config.rs index 0c753f44..ceac3a07 100644 --- a/src/utils/config.rs +++ b/src/utils/config.rs @@ -11,6 +11,7 @@ use serde_yaml::{self}; use shlex::split; use crate::utils::{get_args, time_to_sec}; +use crate::vec_strings; /// Global Config /// @@ -167,8 +168,15 @@ impl GlobalConfig { .join("ffplayout_status.json") .display() .to_string(); - let fps = config.processing.fps.to_string(); - let bitrate = config.processing.width * config.processing.height / 10; + 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)); if config.playlist.length.contains(':') { @@ -178,35 +186,29 @@ impl GlobalConfig { } // We set the decoder settings here, so we only define them ones. - let mut settings: Vec = vec![ + let mut settings = vec_strings![ "-pix_fmt", "yuv420p", "-r", - &fps, + &config.processing.fps.to_string(), "-c:v", "mpeg2video", "-g", "1", "-b:v", - format!("{bitrate}k").as_str(), + &bitrate, "-minrate", - format!("{bitrate}k").as_str(), + &bitrate, "-maxrate", - format!("{bitrate}k").as_str(), + &bitrate, "-bufsize", - format!("{}k", bitrate / 2).as_str(), - ] - .iter() - .map(|&s| s.into()) - .collect(); + &buf_size + ]; settings.append(&mut pre_audio_codec(config.processing.add_loudnorm)); - settings.append( - &mut vec!["-ar", "48000", "-ac", "2", "-f", "mpegts", "-"] - .iter() - .map(|&s| s.into()) - .collect(), - ); + settings.append(&mut vec_strings![ + "-ar", "48000", "-ac", "2", "-f", "mpegts", "-" + ]); config.processing.settings = Some(settings); @@ -277,13 +279,13 @@ static INSTANCE: OnceCell = OnceCell::new(); /// s302m has higher quality, but is experimental /// and works not well together with the loudnorm filter. fn pre_audio_codec(add_loudnorm: bool) -> Vec { - let mut codec = vec!["-c:a", "s302m", "-strict", "-2"]; + let mut codec = vec_strings!["-c:a", "s302m", "-strict", "-2"]; if add_loudnorm { - codec = vec!["-c:a", "mp2", "-b:a", "384k"]; + codec = vec_strings!["-c:a", "mp2", "-b:a", "384k"]; } - codec.iter().map(|&s| s.into()).collect() + codec } pub fn init_config() { diff --git a/src/utils/logging.rs b/src/utils/logging.rs index e60e1005..102d6d45 100644 --- a/src/utils/logging.rs +++ b/src/utils/logging.rs @@ -131,7 +131,7 @@ impl SharedLogger for LogMailer { /// /// ToDo: maybe in next version from simplelog this is not necessary anymore. fn clean_string(text: &str) -> String { - let regex: Regex = Regex::new(r"\x1b\[[0-9;]*[mGKF]").unwrap(); + let regex = Regex::new(r"\x1b\[[0-9;]*[mGKF]").unwrap(); regex.replace_all(text, "").to_string() } @@ -169,7 +169,7 @@ pub fn init_logging() -> Vec> { let file_config = log_config .clone() .set_time_format_custom(format_description!( - "[[year]-[month]-[day] [hour]:[minute]:[second].[subsecond]]" + "[[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:5]]" )) .build(); let mut log_path = "logs/ffplayout.log".to_string(); @@ -207,7 +207,7 @@ pub fn init_logging() -> Vec> { .set_level_color(Level::Warn, Some(Color::Ansi256(208))) .set_level_color(Level::Error, Some(Color::Ansi256(9))) .set_time_format_custom(format_description!( - "\x1b[[30;1m[[[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:4]]\x1b[[0m" + "\x1b[[30;1m[[[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:5]]\x1b[[0m" )) .build(); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 23e6941d..a4022286 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -35,6 +35,13 @@ pub use logging::init_logging; use crate::filter::filter_chains; +#[macro_export] +macro_rules! vec_strings { + ($($str:expr),*) => ({ + vec![$(String::from($str),)*] as Vec + }); +} + /// Video clip struct to hold some important states and comments for current media. #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Media { From 3190aecda0a19b0a851ef1997f28b5e0eb7b0529 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Mon, 9 May 2022 15:28:28 +0200 Subject: [PATCH 02/13] cleanup --- src/output/hls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/output/hls.rs b/src/output/hls.rs index 166fa407..7bf7f8c8 100644 --- a/src/output/hls.rs +++ b/src/output/hls.rs @@ -57,7 +57,7 @@ fn ingest_to_hls_server( let mut is_running; if let Some(url) = stream_input.iter().find(|s| s.contains("://")) { - info!("Start ingest server, listening on: {url}",); + info!("Start ingest server, listening on: {url}"); }; debug!( From cd81b79d633ebb734f4ba69349c3f7c52697cbb5 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Mon, 9 May 2022 17:34:34 +0200 Subject: [PATCH 03/13] updates --- Cargo.lock | 93 ++++++++++++++++++++++++++++++++++-------------------- Cargo.toml | 8 +++-- 2 files changed, 64 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be80d786..0e42cca0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.15" +version = "3.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d" +checksum = "47582c09be7c8b32c0ab3a6181825ababb713fde6fff20fc573a3870dd45c6a0" dependencies = [ "atty", "bitflags", @@ -233,7 +233,7 @@ dependencies = [ [[package]] name = "file-rotate" version = "0.6.0" -source = "git+https://github.com/jb-alvarado/file-rotate.git#ae5062a5b82626b4d1f9fea2a17325fe1d160d4c" +source = "git+https://github.com/BourgondAries/file-rotate.git#cf713f750b67b376952cd4d856ead0c1252ab243" dependencies = [ "chrono", "flate2", @@ -761,7 +761,7 @@ dependencies = [ "kernel32-sys", "libc", "log", - "miow 0.2.2", + "miow", "net2", "slab", "winapi 0.2.8", @@ -769,16 +769,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" dependencies = [ "libc", "log", - "miow 0.3.7", - "ntapi", "wasi 0.11.0+wasi-snapshot-preview1", - "winapi 0.3.9", + "windows-sys", ] [[package]] @@ -805,15 +803,6 @@ dependencies = [ "ws2_32-sys", ] -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "native-tls" version = "0.2.10" @@ -871,15 +860,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "ntapi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "num-integer" version = "0.1.45" @@ -1066,9 +1046,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" +checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" dependencies = [ "unicode-xid", ] @@ -1282,12 +1262,12 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "socket2" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +checksum = "ca642ba17f8b2995138b1d7711829c92e98c0a25ea019de790f4f09279c4e296" dependencies = [ "libc", - "winapi 0.3.9", + "windows-sys", ] [[package]] @@ -1381,14 +1361,14 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.18.1" +version = "1.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce653fb475565de9f6fb0614b28bca8df2c430c0cf84bcd9c843f15de5414cc" +checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" dependencies = [ "bytes", "libc", "memchr", - "mio 0.8.2", + "mio 0.8.3", "num_cpus", "once_cell", "pin-project-lite", @@ -1571,6 +1551,49 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 1ffcec92..a0b98945 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ chrono = "0.4" clap = { version = "3.1", features = ["derive"] } crossbeam-channel = "0.5" ffprobe = "0.3" -file-rotate = { git = "https://github.com/jb-alvarado/file-rotate.git" } +file-rotate = { git = "https://github.com/BourgondAries/file-rotate.git" } jsonrpc-http-server = "18.0" lettre = "0.10.0-rc.6" log = "0.4" @@ -51,7 +51,11 @@ suggests = "ffmpeg" copyright = "Copyright (c) 2022, Jonathan Baecker. All rights reserved." conf-files = ["/etc/ffplayout/ffplayout.yml"] assets = [ - ["target/x86_64-unknown-linux-musl/release/ffplayout", "/usr/bin/ffplayout", "755"], + [ + "target/x86_64-unknown-linux-musl/release/ffplayout", + "/usr/bin/ffplayout", + "755" + ], ["assets/ffplayout.yml", "/etc/ffplayout/ffplayout.yml", "644"], ["assets/logo.png", "/usr/share/ffplayout/logo.png", "644"], ["README.md", "/usr/share/doc/ffplayout-engine/README", "644"], From f530a5d989a7997cf763a171f52ed9d494a264b8 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Mon, 9 May 2022 22:24:48 +0200 Subject: [PATCH 04/13] out source macros, start with tests --- src/macros/mod.rs | 6 ++++++ src/main.rs | 2 ++ src/tests/mod.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ src/utils/mod.rs | 44 ++++++++++++++++++++++++++++++++++---------- 4 files changed, 85 insertions(+), 10 deletions(-) create mode 100644 src/macros/mod.rs create mode 100644 src/tests/mod.rs diff --git a/src/macros/mod.rs b/src/macros/mod.rs new file mode 100644 index 00000000..7951c866 --- /dev/null +++ b/src/macros/mod.rs @@ -0,0 +1,6 @@ +#[macro_export] +macro_rules! vec_strings { + ($($str:expr),*) => ({ + vec![$(String::from($str),)*] as Vec + }); +} diff --git a/src/main.rs b/src/main.rs index 5de25627..4cb7a9a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,8 +14,10 @@ use simplelog::*; mod filter; mod input; +mod macros; mod output; mod rpc; +mod tests; mod utils; use crate::output::{player, write_hls}; diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 00000000..fc59a11e --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,43 @@ +#[cfg(test)] +use chrono::prelude::*; + +#[cfg(test)] +use crate::utils::*; + +#[cfg(test)] +fn get_fake_date_time(date_time: &str) -> DateTime { + let date_obj = NaiveDateTime::parse_from_str(date_time, "%Y-%m-%dT%H:%M:%S"); + + Local.from_local_datetime(&date_obj.unwrap()).unwrap() +} + +#[test] +fn mock_date_time() { + let fake_time = get_fake_date_time("2022-05-20T06:00:00"); + mock_time::set_mock_time(fake_time); + + assert_eq!( + fake_time.format("%Y-%m-%dT%H:%M:%S.2f").to_string(), + time_now().format("%Y-%m-%dT%H:%M:%S.2f").to_string() + ); +} + +#[test] +fn get_date_yesterday() { + let fake_time = get_fake_date_time("2022-05-20T05:59:24"); + mock_time::set_mock_time(fake_time); + + let date = get_date(true, 21600.0, 86400.0); + + assert_eq!("2022-05-19".to_string(), date); +} + +#[test] +fn get_date_tomorrow() { + let fake_time = get_fake_date_time("2022-05-20T23:59:30"); + mock_time::set_mock_time(fake_time); + + let date = get_date(false, 0.0, 86400.01); + + assert_eq!("2022-05-21".to_string(), date); +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index a4022286..f57b9875 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -35,13 +35,6 @@ pub use logging::init_logging; use crate::filter::filter_chains; -#[macro_export] -macro_rules! vec_strings { - ($($str:expr),*) => ({ - vec![$(String::from($str),)*] as Vec - }); -} - /// Video clip struct to hold some important states and comments for current media. #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Media { @@ -213,14 +206,14 @@ pub fn write_status(date: &str, shift: f64) { } // pub fn get_timestamp() -> i64 { -// let local: DateTime = Local::now(); +// let local: DateTime = time_now(); // local.timestamp_millis() as i64 // } /// Get current time in seconds. pub fn get_sec() -> f64 { - let local: DateTime = Local::now(); + let local: DateTime = time_now(); (local.hour() * 3600 + local.minute() * 60 + local.second()) as f64 + (local.nanosecond() as f64 / 1000000000.0) @@ -231,7 +224,7 @@ pub fn get_sec() -> f64 { /// - When time is before playlist start, get date from yesterday. /// - When given next_start is over target length (normally a full day), get date from tomorrow. pub fn get_date(seek: bool, start: f64, next_start: f64) -> String { - let local: DateTime = Local::now(); + let local: DateTime = time_now(); if seek && start > get_sec() { return (local - Duration::days(1)).format("%Y-%m-%d").to_string(); @@ -512,3 +505,34 @@ pub fn validate_ffmpeg() { warn!("ffmpeg contains no zmq filter! Text messages will not work..."); } } + +/// Get system time, in non test case. +#[cfg(not(test))] +pub fn time_now() -> DateTime { + Local::now() +} + +/// Get mocked system time, in test case. +#[cfg(test)] +pub mod mock_time { + use super::*; + use std::cell::RefCell; + + thread_local! { + static DATE_TIME_DIFF: RefCell> = RefCell::new(None); + } + + pub fn time_now() -> DateTime { + DATE_TIME_DIFF.with(|cell| match cell.borrow().as_ref().cloned() { + Some(diff) => Local::now() - diff, + None => Local::now(), + }) + } + + pub fn set_mock_time(time: DateTime) { + DATE_TIME_DIFF.with(|cell| *cell.borrow_mut() = Some(Local::now() - time)); + } +} + +#[cfg(test)] +pub use mock_time::time_now; From 01f54494467967ed4ef86df73009ae01846d1b0f Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Tue, 10 May 2022 11:26:01 +0200 Subject: [PATCH 05/13] add tollerance to next_start on last clip --- src/input/playlist.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/input/playlist.rs b/src/input/playlist.rs index 3b1d1f4c..f46a4ae3 100644 --- a/src/input/playlist.rs +++ b/src/input/playlist.rs @@ -134,7 +134,11 @@ impl CurrentProgram { duration = self.current_node.duration } - let next_start = self.current_node.begin.unwrap() - start_sec + duration + delta; + let mut next_start = self.current_node.begin.unwrap() - start_sec + duration + delta; + + if self.index.load(Ordering::SeqCst) == self.nodes.lock().unwrap().len() - 1 { + next_start += self.config.general.stop_threshold; + } if next_start >= target_length || is_close(total_delta, 0.0, 2.0) From 855a9f9a54cc4f17e1e18aa62b8928923ac88d57 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Tue, 10 May 2022 11:26:18 +0200 Subject: [PATCH 06/13] fix encoder_term --- src/output/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/output/mod.rs b/src/output/mod.rs index c93d4446..4e599dcf 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -64,7 +64,7 @@ pub fn player( // spawn a thread to log ffmpeg output error messages let error_encoder_thread = thread::spawn(move || stderr_reader(enc_err, "Encoder")); - *proc_control.decoder_term.lock().unwrap() = Some(enc_proc); + *proc_control.encoder_term.lock().unwrap() = Some(enc_proc); let ff_log_format_c = ff_log_format.clone(); let proc_control_c = proc_control.clone(); From 8fcd331c704b13842529f5ae720e0bb9847e079f Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Tue, 10 May 2022 11:26:50 +0200 Subject: [PATCH 07/13] change unit order --- src/utils/controller.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/controller.rs b/src/utils/controller.rs index 50add407..8e4b44a3 100644 --- a/src/utils/controller.rs +++ b/src/utils/controller.rs @@ -134,7 +134,7 @@ impl ProcessControl { rpc.clone().close() }; - for unit in [Decoder, Encoder, Ingest] { + for unit in [Encoder, Decoder, Ingest] { if let Err(e) = self.kill(unit) { error!("{e}") } From dda7abfa23e2686ba3f889284e1488b8ab69d5a2 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Tue, 10 May 2022 11:27:33 +0200 Subject: [PATCH 08/13] test playlist change at mindnight and at six --- src/main.rs | 3 +- src/tests/mod.rs | 91 +++++++++++++++++++++++++++++------------- src/tests/utils/mod.rs | 37 +++++++++++++++++ src/utils/arg_parse.rs | 6 +++ src/utils/config.rs | 21 +++++++++- src/utils/mod.rs | 14 ++++++- 6 files changed, 140 insertions(+), 32 deletions(-) create mode 100644 src/tests/utils/mod.rs diff --git a/src/main.rs b/src/main.rs index 4cb7a9a0..09996a0d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ mod input; mod macros; mod output; mod rpc; +#[cfg(test)] mod tests; mod utils; @@ -65,7 +66,7 @@ fn status_file(stat_file: &str, playout_stat: &PlayoutStatus) { fn main() { // Init the config, set process controller, create logging. - init_config(); + init_config(None); let config = GlobalConfig::global(); let play_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); diff --git a/src/tests/mod.rs b/src/tests/mod.rs index fc59a11e..128d68ec 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,43 +1,78 @@ -#[cfg(test)] -use chrono::prelude::*; +use std::{ + process, + thread::{self, sleep}, + time::Duration, +}; +mod utils; + +#[cfg(test)] +use crate::output::player; #[cfg(test)] use crate::utils::*; - #[cfg(test)] -fn get_fake_date_time(date_time: &str) -> DateTime { - let date_obj = NaiveDateTime::parse_from_str(date_time, "%Y-%m-%dT%H:%M:%S"); +use simplelog::*; - Local.from_local_datetime(&date_obj.unwrap()).unwrap() +fn timed_kill(sec: u64, mut proc_ctl: ProcessControl) { + sleep(Duration::from_secs(sec)); + + proc_ctl.kill_all(); + + process::exit(0); } #[test] -fn mock_date_time() { - let fake_time = get_fake_date_time("2022-05-20T06:00:00"); - mock_time::set_mock_time(fake_time); +#[ignore] +fn playlist_change_at_midnight() { + let config = TestConfig { + mode: "playlist".into(), + start: "00:00:00".into(), + length: "24:00:00".into(), + log_to_file: false, + mail_recipient: "".into(), + }; - assert_eq!( - fake_time.format("%Y-%m-%dT%H:%M:%S.2f").to_string(), - time_now().format("%Y-%m-%dT%H:%M:%S.2f").to_string() - ); + init_config(Some(config)); + + let play_control = PlayerControl::new(); + let playout_stat = PlayoutStatus::new(); + let proc_control = ProcessControl::new(); + let proc_ctl = proc_control.clone(); + + let logging = init_logging(); + CombinedLogger::init(logging).unwrap(); + + mock_time::set_mock_time("2022-05-09T23:59:45"); + + thread::spawn(move || timed_kill(30, proc_ctl)); + + player(play_control, playout_stat, proc_control); } #[test] -fn get_date_yesterday() { - let fake_time = get_fake_date_time("2022-05-20T05:59:24"); - mock_time::set_mock_time(fake_time); +#[ignore] +fn playlist_change_at_six() { + let config = TestConfig { + mode: "playlist".into(), + start: "06:00:00".into(), + length: "24:00:00".into(), + log_to_file: false, + mail_recipient: "".into(), + }; - let date = get_date(true, 21600.0, 86400.0); + init_config(Some(config)); - assert_eq!("2022-05-19".to_string(), date); -} - -#[test] -fn get_date_tomorrow() { - let fake_time = get_fake_date_time("2022-05-20T23:59:30"); - mock_time::set_mock_time(fake_time); - - let date = get_date(false, 0.0, 86400.01); - - assert_eq!("2022-05-21".to_string(), date); + let play_control = PlayerControl::new(); + let playout_stat = PlayoutStatus::new(); + let proc_control = ProcessControl::new(); + let proc_ctl = proc_control.clone(); + + let logging = init_logging(); + CombinedLogger::init(logging).unwrap(); + + mock_time::set_mock_time("2022-05-09T05:59:45"); + + thread::spawn(move || timed_kill(30, proc_ctl)); + + player(play_control, playout_stat, proc_control); } diff --git a/src/tests/utils/mod.rs b/src/tests/utils/mod.rs new file mode 100644 index 00000000..0009f476 --- /dev/null +++ b/src/tests/utils/mod.rs @@ -0,0 +1,37 @@ +#[cfg(test)] +use chrono::prelude::*; + +#[cfg(test)] +use crate::utils::*; + +#[test] +fn mock_date_time() { + let time_str = "2022-05-20T06:00:00"; + let date_obj = NaiveDateTime::parse_from_str(time_str, "%Y-%m-%dT%H:%M:%S"); + let time = Local.from_local_datetime(&date_obj.unwrap()).unwrap(); + + mock_time::set_mock_time(time_str); + + assert_eq!( + time.format("%Y-%m-%dT%H:%M:%S.2f").to_string(), + time_now().format("%Y-%m-%dT%H:%M:%S.2f").to_string() + ); +} + +#[test] +fn get_date_yesterday() { + mock_time::set_mock_time("2022-05-20T05:59:24"); + + let date = get_date(true, 21600.0, 86400.0); + + assert_eq!("2022-05-19".to_string(), date); +} + +#[test] +fn get_date_tomorrow() { + mock_time::set_mock_time("2022-05-20T23:59:30"); + + let date = get_date(false, 0.0, 86400.01); + + assert_eq!("2022-05-21".to_string(), date); +} diff --git a/src/utils/arg_parse.rs b/src/utils/arg_parse.rs index 7adb6211..09490383 100644 --- a/src/utils/arg_parse.rs +++ b/src/utils/arg_parse.rs @@ -55,6 +55,12 @@ pub struct Args { } /// Get arguments from command line, and return them. +#[cfg(not(test))] pub fn get_args() -> Args { Args::parse() } + +#[cfg(test)] +pub fn get_args() -> Args { + Args::parse_from(["-o desktop"].iter()) +} diff --git a/src/utils/config.rs b/src/utils/config.rs index ceac3a07..a2d87655 100644 --- a/src/utils/config.rs +++ b/src/utils/config.rs @@ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; use serde_yaml::{self}; use shlex::split; -use crate::utils::{get_args, time_to_sec}; +use crate::utils::{get_args, time_to_sec, TestConfig}; use crate::vec_strings; /// Global Config @@ -288,7 +288,24 @@ fn pre_audio_codec(add_loudnorm: bool) -> Vec { codec } -pub fn init_config() { +#[cfg(not(test))] +pub fn init_config(_: Option) { let config = GlobalConfig::new(); INSTANCE.set(config).unwrap(); } + +#[cfg(test)] +pub fn init_config(test_config: Option) { + let mut config = GlobalConfig::new(); + config.out.mode = "desktop".into(); + if let Some(cfg) = test_config { + config.logging.log_to_file = cfg.log_to_file; + config.mail.recipient = cfg.mail_recipient; + config.playlist.day_start = cfg.start.clone(); + config.playlist.start_sec = Some(time_to_sec(&cfg.start)); + config.playlist.length = cfg.length.clone(); + config.playlist.length_sec = Some(time_to_sec(&cfg.length)); + }; + + INSTANCE.set(config).unwrap(); +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index f57b9875..84339a81 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -506,6 +506,15 @@ pub fn validate_ffmpeg() { } } +/// In test cases we override some configuration values to fit the needs. +pub struct TestConfig { + pub mode: String, + pub start: String, + pub length: String, + pub log_to_file: bool, + pub mail_recipient: String, +} + /// Get system time, in non test case. #[cfg(not(test))] pub fn time_now() -> DateTime { @@ -529,7 +538,10 @@ pub mod mock_time { }) } - pub fn set_mock_time(time: DateTime) { + pub fn set_mock_time(date_time: &str) { + let date_obj = NaiveDateTime::parse_from_str(date_time, "%Y-%m-%dT%H:%M:%S"); + let time = Local.from_local_datetime(&date_obj.unwrap()).unwrap(); + DATE_TIME_DIFF.with(|cell| *cell.borrow_mut() = Some(Local::now() - time)); } } From 6c9eb99d80ae318041367860b7de762da3c9e7c2 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Tue, 10 May 2022 17:45:09 +0200 Subject: [PATCH 09/13] override config only ones --- src/tests/mod.rs | 45 ++++++++++++++++++++---------------------- src/tests/utils/mod.rs | 17 ++++++++++++++++ src/utils/config.rs | 5 ++++- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 128d68ec..135b8a56 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,5 +1,4 @@ use std::{ - process, thread::{self, sleep}, time::Duration, }; @@ -17,8 +16,6 @@ fn timed_kill(sec: u64, mut proc_ctl: ProcessControl) { sleep(Duration::from_secs(sec)); proc_ctl.kill_all(); - - process::exit(0); } #[test] @@ -49,30 +46,30 @@ fn playlist_change_at_midnight() { player(play_control, playout_stat, proc_control); } -#[test] -#[ignore] -fn playlist_change_at_six() { - let config = TestConfig { - mode: "playlist".into(), - start: "06:00:00".into(), - length: "24:00:00".into(), - log_to_file: false, - mail_recipient: "".into(), - }; +// #[test] +// #[ignore] +// fn playlist_change_at_six() { +// let config = TestConfig { +// mode: "playlist".into(), +// start: "06:00:00".into(), +// length: "24:00:00".into(), +// log_to_file: false, +// mail_recipient: "".into(), +// }; - init_config(Some(config)); +// init_config(Some(config)); - let play_control = PlayerControl::new(); - let playout_stat = PlayoutStatus::new(); - let proc_control = ProcessControl::new(); - let proc_ctl = proc_control.clone(); +// let play_control = PlayerControl::new(); +// let playout_stat = PlayoutStatus::new(); +// let proc_control = ProcessControl::new(); +// let proc_ctl = proc_control.clone(); - let logging = init_logging(); - CombinedLogger::init(logging).unwrap(); +// let logging = init_logging(); +// CombinedLogger::init(logging).unwrap(); - mock_time::set_mock_time("2022-05-09T05:59:45"); +// mock_time::set_mock_time("2022-05-09T05:59:45"); - thread::spawn(move || timed_kill(30, proc_ctl)); +// thread::spawn(move || timed_kill(30, proc_ctl)); - player(play_control, playout_stat, proc_control); -} +// player(play_control, playout_stat, proc_control); +// } diff --git a/src/tests/utils/mod.rs b/src/tests/utils/mod.rs index 0009f476..91835dcb 100644 --- a/src/tests/utils/mod.rs +++ b/src/tests/utils/mod.rs @@ -35,3 +35,20 @@ fn get_date_tomorrow() { assert_eq!("2022-05-21".to_string(), date); } + +#[test] +fn test_delta() { + let config = TestConfig { + mode: "playlist".into(), + start: "00:00:00".into(), + length: "24:00:00".into(), + log_to_file: false, + mail_recipient: "".into(), + }; + + init_config(Some(config)); + mock_time::set_mock_time("2022-05-09T23:59:59"); + let (delta, _) = get_delta(&86401.0); + + assert!(delta < 2.0); +} diff --git a/src/utils/config.rs b/src/utils/config.rs index a2d87655..6a1f2139 100644 --- a/src/utils/config.rs +++ b/src/utils/config.rs @@ -223,6 +223,9 @@ impl GlobalConfig { } if let Some(log_path) = args.log { + if Path::new(&log_path).is_dir() { + config.logging.log_to_file = true; + } config.logging.log_path = log_path; } @@ -269,7 +272,7 @@ impl GlobalConfig { } pub fn global() -> &'static GlobalConfig { - INSTANCE.get().expect("Config is not initialized") + INSTANCE.get_or_init(GlobalConfig::new) } } From 2a17bd469007fb44469b7b4d6c1af9079906bc6c Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 12 May 2022 07:57:45 +0200 Subject: [PATCH 10/13] change file-rotate repo --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e42cca0..362ba5e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -233,7 +233,7 @@ dependencies = [ [[package]] name = "file-rotate" version = "0.6.0" -source = "git+https://github.com/BourgondAries/file-rotate.git#cf713f750b67b376952cd4d856ead0c1252ab243" +source = "git+https://github.com/jb-alvarado/file-rotate.git#ee1dc1cea05885b8cb472191b50a044869da7e04" dependencies = [ "chrono", "flate2", diff --git a/Cargo.toml b/Cargo.toml index a0b98945..2c3aedc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ chrono = "0.4" clap = { version = "3.1", features = ["derive"] } crossbeam-channel = "0.5" ffprobe = "0.3" -file-rotate = { git = "https://github.com/BourgondAries/file-rotate.git" } +file-rotate = { git = "https://github.com/jb-alvarado/file-rotate.git" } jsonrpc-http-server = "18.0" lettre = "0.10.0-rc.6" log = "0.4" From 4b0a3f5b1a97e0b3d2297af606848e00255c9cc0 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 12 May 2022 17:56:11 +0200 Subject: [PATCH 11/13] update --- Cargo.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 362ba5e2..6dffe156 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.17" +version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47582c09be7c8b32c0ab3a6181825ababb713fde6fff20fc573a3870dd45c6a0" +checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" dependencies = [ "atty", "bitflags", @@ -111,9 +111,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.1.7" +version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" +checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" dependencies = [ "heck", "proc-macro-error", @@ -1262,12 +1262,12 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "socket2" -version = "0.4.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca642ba17f8b2995138b1d7711829c92e98c0a25ea019de790f4f09279c4e296" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", - "windows-sys", + "winapi 0.3.9", ] [[package]] @@ -1278,9 +1278,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" +checksum = "04066589568b72ec65f42d65a1a52436e954b168773148893c020269563decf2" dependencies = [ "proc-macro2", "quote", From 52f7d97ca69edeb186a35a97d19d782d3eee817f Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 12 May 2022 17:57:15 +0200 Subject: [PATCH 12/13] fix date string, simple FileRotate object --- src/utils/controller.rs | 2 +- src/utils/logging.rs | 26 ++++++++++++-------------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/utils/controller.rs b/src/utils/controller.rs index 8e4b44a3..50add407 100644 --- a/src/utils/controller.rs +++ b/src/utils/controller.rs @@ -134,7 +134,7 @@ impl ProcessControl { rpc.clone().close() }; - for unit in [Encoder, Decoder, Ingest] { + for unit in [Decoder, Encoder, Ingest] { if let Err(e) = self.kill(unit) { error!("{e}") } diff --git a/src/utils/logging.rs b/src/utils/logging.rs index 102d6d45..0f14617e 100644 --- a/src/utils/logging.rs +++ b/src/utils/logging.rs @@ -169,7 +169,7 @@ pub fn init_logging() -> Vec> { let file_config = log_config .clone() .set_time_format_custom(format_description!( - "[[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:5]]" + "[[[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:5]]" )) .build(); let mut log_path = "logs/ffplayout.log".to_string(); @@ -185,20 +185,18 @@ pub fn init_logging() -> Vec> { println!("Logging path not exists!") } - let log = || { - FileRotate::new( - log_path, - AppendTimestamp::with_format( - "%Y-%m-%d", - FileLimit::MaxFiles(app_config.backup_count), - DateFrom::DateYesterday, - ), - ContentLimit::Time(TimeFrequency::Daily), - Compression::None, - ) - }; + let log_file = FileRotate::new( + &log_path, + AppendTimestamp::with_format( + "%Y-%m-%d", + FileLimit::MaxFiles(app_config.backup_count), + DateFrom::DateYesterday, + ), + ContentLimit::Time(TimeFrequency::Daily), + Compression::None, + ); - app_logger.push(WriteLogger::new(LevelFilter::Debug, file_config, log())); + app_logger.push(WriteLogger::new(LevelFilter::Debug, file_config, log_file)); } else { let term_config = log_config .clone() From cb0850a6249844834f55a379f8d67b30f82537e6 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 12 May 2022 21:17:26 +0200 Subject: [PATCH 13/13] switch back from global config to local config --- Cargo.lock | 19 ++++++------ Cargo.toml | 1 - src/filter/ingest_filter.rs | 4 +-- src/filter/mod.rs | 8 ++--- src/filter/v_drawtext.rs | 3 +- src/input/folder.rs | 16 +++++----- src/input/ingest.rs | 6 ++-- src/input/mod.rs | 8 +++-- src/input/playlist.rs | 52 ++++++++++++++++++------------- src/main.rs | 21 ++++++------- src/output/desktop.rs | 8 ++--- src/output/hls.rs | 9 +++--- src/output/mod.rs | 11 ++++--- src/output/stream.rs | 7 +++-- src/rpc/mod.rs | 26 ++++++++-------- src/tests/mod.rs | 60 ++++++++++++++++-------------------- src/tests/utils/mod.rs | 16 +++++----- src/utils/config.rs | 34 ++------------------ src/utils/controller.rs | 4 ++- src/utils/generator.rs | 5 ++- src/utils/json_serializer.rs | 6 ++-- src/utils/logging.rs | 57 +++++++++++++++------------------- src/utils/mod.rs | 32 +++++-------------- 23 files changed, 182 insertions(+), 231 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 362ba5e2..ec5dc03e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,9 +94,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.17" +version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47582c09be7c8b32c0ab3a6181825ababb713fde6fff20fc573a3870dd45c6a0" +checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" dependencies = [ "atty", "bitflags", @@ -111,9 +111,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.1.7" +version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1" +checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" dependencies = [ "heck", "proc-macro-error", @@ -207,7 +207,6 @@ dependencies = [ "lettre", "log", "notify", - "once_cell", "openssl", "rand", "regex", @@ -1262,12 +1261,12 @@ checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "socket2" -version = "0.4.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca642ba17f8b2995138b1d7711829c92e98c0a25ea019de790f4f09279c4e296" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", - "windows-sys", + "winapi 0.3.9", ] [[package]] @@ -1278,9 +1277,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.92" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff7c592601f11445996a06f8ad0c27f094a58857c2f89e97974ab9235b92c52" +checksum = "04066589568b72ec65f42d65a1a52436e954b168773148893c020269563decf2" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 2c3aedc8..3290f6b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ jsonrpc-http-server = "18.0" lettre = "0.10.0-rc.6" log = "0.4" notify = "4.0" -once_cell = "1.10" rand = "0.8" regex = "1" serde = { version = "1.0", features = ["derive"] } diff --git a/src/filter/ingest_filter.rs b/src/filter/ingest_filter.rs index 98be5654..fb03c8db 100644 --- a/src/filter/ingest_filter.rs +++ b/src/filter/ingest_filter.rs @@ -21,9 +21,7 @@ fn audio_filter(config: &GlobalConfig) -> String { } /// Create filter nodes for ingest live stream. -pub fn filter_cmd() -> Vec { - let config = GlobalConfig::global(); - +pub fn filter_cmd(config: &GlobalConfig) -> Vec { let mut filter = format!( "[0:v]fps={},scale={}:{},setdar=dar={},fade=in:st=0:d=0.5", config.processing.fps, diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 66ea47f1..aeae829a 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -182,7 +182,7 @@ fn extend_video(node: &mut Media, chain: &mut Filters) { /// add drawtext filter for lower thirds messages fn add_text(node: &mut Media, chain: &mut Filters, config: &GlobalConfig) { if config.text.add_text && config.text.over_pre { - let filter = v_drawtext::filter_node(node); + let filter = v_drawtext::filter_node(config, node); chain.add_filter(&filter, "video"); @@ -277,7 +277,7 @@ fn realtime_filter(node: &mut Media, chain: &mut Filters, config: &GlobalConfig, if &config.out.mode.to_lowercase() == "hls" { let mut speed_filter = format!("{t}realtime=speed=1"); - let (delta, _) = get_delta(&node.begin.unwrap()); + let (delta, _) = get_delta(config, &node.begin.unwrap()); let duration = node.out - node.seek; if delta < 0.0 { @@ -292,9 +292,7 @@ fn realtime_filter(node: &mut Media, chain: &mut Filters, config: &GlobalConfig, } } -pub fn filter_chains(node: &mut Media) -> Vec { - let config = GlobalConfig::global(); - +pub fn filter_chains(config: &GlobalConfig, node: &mut Media) -> Vec { let mut filters = Filters::new(); let mut audio_map = "1:a".to_string(); filters.audio_map = Some(audio_map); diff --git a/src/filter/v_drawtext.rs b/src/filter/v_drawtext.rs index 3a787684..47c6ae14 100644 --- a/src/filter/v_drawtext.rs +++ b/src/filter/v_drawtext.rs @@ -4,8 +4,7 @@ use regex::Regex; use crate::utils::{GlobalConfig, Media}; -pub fn filter_node(node: &mut Media) -> String { - let config = GlobalConfig::global(); +pub fn filter_node(config: &GlobalConfig, node: &mut Media) -> String { let mut filter = String::new(); let mut font = String::new(); diff --git a/src/input/folder.rs b/src/input/folder.rs index ca8c49a8..aa6454e0 100644 --- a/src/input/folder.rs +++ b/src/input/folder.rs @@ -33,8 +33,11 @@ pub struct FolderSource { } impl FolderSource { - pub fn new(current_list: Arc>>, global_index: Arc) -> Self { - let config = GlobalConfig::global(); + pub fn new( + config: &GlobalConfig, + current_list: Arc>>, + global_index: Arc, + ) -> Self { let mut media_list = vec![]; let mut index: usize = 0; @@ -120,7 +123,7 @@ impl Iterator for FolderSource { let i = self.index.load(Ordering::SeqCst); self.current_node = self.nodes.lock().unwrap()[i].clone(); self.current_node.add_probe(); - self.current_node.add_filter(); + self.current_node.add_filter(&self.config); self.current_node.begin = Some(get_sec()); self.index.fetch_add(1, Ordering::SeqCst); @@ -143,7 +146,7 @@ impl Iterator for FolderSource { self.current_node = self.nodes.lock().unwrap()[0].clone(); self.current_node.add_probe(); - self.current_node.add_filter(); + self.current_node.add_filter(&self.config); self.current_node.begin = Some(get_sec()); self.index.store(1, Ordering::SeqCst); @@ -160,11 +163,10 @@ fn file_extension(filename: &Path) -> Option<&str> { /// Create a watcher, which monitor file changes. /// When a change is register, update the current file list. /// This makes it possible, to play infinitely and and always new files to it. -pub fn watchman(sources: Arc>>) { - let config = GlobalConfig::global(); +pub fn watchman(config: GlobalConfig, sources: Arc>>) { let (tx, rx) = channel(); - let path = config.storage.path.clone(); + let path = config.storage.path; if !Path::new(&path).exists() { error!("Folder path not exists: '{path}'"); diff --git a/src/input/ingest.rs b/src/input/ingest.rs index 225b5a35..51f40a52 100644 --- a/src/input/ingest.rs +++ b/src/input/ingest.rs @@ -16,18 +16,18 @@ use crate::vec_strings; /// /// Start ffmpeg in listen mode, and wait for input. pub fn ingest_server( + config: GlobalConfig, log_format: String, ingest_sender: Sender<(usize, [u8; 65088])>, mut proc_control: ProcessControl, ) -> Result<(), Error> { - let config = GlobalConfig::global(); let mut buffer: [u8; 65088] = [0; 65088]; let mut server_cmd = vec_strings!["-hide_banner", "-nostats", "-v", log_format]; let stream_input = config.ingest.input_cmd.clone().unwrap(); server_cmd.append(&mut stream_input.clone()); - server_cmd.append(&mut filter_cmd()); - server_cmd.append(&mut config.processing.settings.clone().unwrap()); + server_cmd.append(&mut filter_cmd(&config)); + server_cmd.append(&mut config.processing.settings.unwrap()); let mut is_running; diff --git a/src/input/mod.rs b/src/input/mod.rs index a8863045..394a875a 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -35,17 +35,19 @@ pub fn source_generator( &config.storage.path ); - let folder_source = FolderSource::new(current_list, index); + let config_clone = config.clone(); + let folder_source = FolderSource::new(&config, current_list, index); let node_clone = folder_source.nodes.clone(); // Spawn a thread to monitor folder for file changes. - thread::spawn(move || watchman(node_clone)); + thread::spawn(move || watchman(config_clone, node_clone)); Box::new(folder_source) as Box> } "playlist" => { info!("Playout in playlist mode"); - let program = CurrentProgram::new(playout_stat, is_terminated, current_list, index); + let program = + CurrentProgram::new(&config, playout_stat, is_terminated, current_list, index); Box::new(program) as Box> } diff --git a/src/input/playlist.rs b/src/input/playlist.rs index f46a4ae3..2b7bbad3 100644 --- a/src/input/playlist.rs +++ b/src/input/playlist.rs @@ -34,13 +34,13 @@ pub struct CurrentProgram { impl CurrentProgram { pub fn new( + config: &GlobalConfig, playout_stat: PlayoutStatus, is_terminated: Arc, current_list: Arc>>, global_index: Arc, ) -> Self { - let config = GlobalConfig::global(); - let json = read_json(None, is_terminated.clone(), true, 0.0); + let json = read_json(config, None, is_terminated.clone(), true, 0.0); *current_list.lock().unwrap() = json.program; *playout_stat.current_date.lock().unwrap() = json.date.clone(); @@ -72,7 +72,7 @@ impl CurrentProgram { // Check if playlist file got updated, and when yes we reload it and setup everything in place. fn check_update(&mut self, seek: bool) { if self.json_path.is_none() { - let json = read_json(None, self.is_terminated.clone(), seek, 0.0); + let json = read_json(&self.config, None, self.is_terminated.clone(), seek, 0.0); self.json_path = json.current_file; self.json_mod = json.modified; @@ -92,6 +92,7 @@ impl CurrentProgram { ); let json = read_json( + &self.config, self.json_path.clone(), self.is_terminated.clone(), false, @@ -127,7 +128,7 @@ impl CurrentProgram { let current_time = get_sec(); let start_sec = self.config.playlist.start_sec.unwrap(); let target_length = self.config.playlist.length_sec.unwrap(); - let (delta, total_delta) = get_delta(¤t_time); + let (delta, total_delta) = get_delta(&self.config, ¤t_time); let mut duration = self.current_node.out; if self.current_node.duration > self.current_node.out { @@ -144,7 +145,13 @@ impl CurrentProgram { || is_close(total_delta, 0.0, 2.0) || is_close(total_delta, target_length, 2.0) { - let json = read_json(None, self.is_terminated.clone(), false, next_start); + let json = read_json( + &self.config, + None, + self.is_terminated.clone(), + false, + next_start, + ); let data = json!({ "time_shift": 0.0, @@ -236,7 +243,7 @@ impl CurrentProgram { let mut node_clone = self.nodes.lock().unwrap()[index].clone(); node_clone.seek = time_sec - node_clone.begin.unwrap(); - self.current_node = handle_list_init(node_clone); + self.current_node = handle_list_init(&self.config, node_clone); } } } @@ -271,7 +278,7 @@ impl Iterator for CurrentProgram { self.init_clip(); } else { let mut current_time = get_sec(); - let (_, total_delta) = get_delta(¤t_time); + let (_, total_delta) = get_delta(&self.config, ¤t_time); let mut duration = DUMMY_LEN; if DUMMY_LEN > total_delta { @@ -288,7 +295,7 @@ impl Iterator for CurrentProgram { media.duration = duration; media.out = duration; - self.current_node = gen_source(media); + self.current_node = gen_source(&self.config, media); self.nodes.lock().unwrap().push(self.current_node.clone()); self.index .store(self.nodes.lock().unwrap().len(), Ordering::SeqCst); @@ -326,7 +333,8 @@ impl Iterator for CurrentProgram { let last_playlist = self.json_path.clone(); let last_ad = self.current_node.last_ad; self.check_for_next_playlist(); - let (_, total_delta) = get_delta(&self.config.playlist.start_sec.unwrap()); + let (_, total_delta) = + get_delta(&self.config, &self.config.playlist.start_sec.unwrap()); if last_playlist == self.json_path && total_delta.abs() > self.config.general.stop_threshold @@ -343,12 +351,12 @@ impl Iterator for CurrentProgram { } self.current_node.duration = duration; self.current_node.out = duration; - self.current_node = gen_source(self.current_node.clone()); + self.current_node = gen_source(&self.config, self.current_node.clone()); self.nodes.lock().unwrap().push(self.current_node.clone()); self.last_next_ad(); self.current_node.last_ad = last_ad; - self.current_node.add_filter(); + self.current_node.add_filter(&self.config); self.index.fetch_add(1, Ordering::SeqCst); @@ -356,7 +364,7 @@ impl Iterator for CurrentProgram { } self.index.store(0, Ordering::SeqCst); - self.current_node = gen_source(self.nodes.lock().unwrap()[0].clone()); + self.current_node = gen_source(&self.config, self.nodes.lock().unwrap()[0].clone()); self.last_next_ad(); self.current_node.last_ad = last_ad; @@ -377,7 +385,7 @@ fn timed_source( last: bool, playout_stat: &PlayoutStatus, ) -> Media { - let (delta, total_delta) = get_delta(&node.begin.unwrap()); + let (delta, total_delta) = get_delta(config, &node.begin.unwrap()); let mut shifted_delta = delta; let mut new_node = node.clone(); new_node.process = Some(false); @@ -397,7 +405,7 @@ fn timed_source( debug!("Total time remaining: {total_delta:.3}"); - let sync = check_sync(shifted_delta); + let sync = check_sync(config, shifted_delta); if !sync { new_node.cmd = None; @@ -411,7 +419,7 @@ fn timed_source( || !config.playlist.length.contains(':') { // when we are in the 24 hour range, get the clip - new_node = gen_source(node); + new_node = gen_source(config, node); new_node.process = Some(true); } else if total_delta <= 0.0 { info!("Begin is over play time, skip: {}", node.source); @@ -423,7 +431,7 @@ fn timed_source( } /// Generate the source CMD, or when clip not exist, get a dummy. -fn gen_source(mut node: Media) -> Media { +fn gen_source(config: &GlobalConfig, mut node: Media) -> Media { if Path::new(&node.source).is_file() { node.add_probe(); node.cmd = Some(seek_and_length( @@ -432,7 +440,7 @@ fn gen_source(mut node: Media) -> Media { node.out, node.duration, )); - node.add_filter(); + node.add_filter(config); } else { if node.source.chars().count() == 0 { warn!( @@ -442,10 +450,10 @@ fn gen_source(mut node: Media) -> Media { } else { error!("File not found: {}", node.source); } - let (source, cmd) = gen_dummy(node.out - node.seek); + let (source, cmd) = gen_dummy(config, node.out - node.seek); node.source = source; node.cmd = Some(cmd); - node.add_filter(); + node.add_filter(config); } node @@ -453,9 +461,9 @@ fn gen_source(mut node: Media) -> Media { /// Handle init clip, but this clip can be the last one in playlist, /// this we have to figure out and calculate the right length. -fn handle_list_init(mut node: Media) -> Media { +fn handle_list_init(config: &GlobalConfig, mut node: Media) -> Media { debug!("Playlist init"); - let (_, total_delta) = get_delta(&node.begin.unwrap()); + let (_, total_delta) = get_delta(config, &node.begin.unwrap()); let mut out = node.out; if node.out - node.seek > total_delta { @@ -463,7 +471,7 @@ fn handle_list_init(mut node: Media) -> Media { } node.out = out; - gen_source(node) + gen_source(config, node) } /// when we come to last clip in playlist, diff --git a/src/main.rs b/src/main.rs index 09996a0d..8ec7937a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,8 +23,8 @@ mod utils; use crate::output::{player, write_hls}; use crate::utils::{ - generate_playlist, init_config, init_logging, validate_ffmpeg, GlobalConfig, PlayerControl, - PlayoutStatus, ProcessControl, + generate_playlist, init_logging, validate_ffmpeg, GlobalConfig, PlayerControl, PlayoutStatus, + ProcessControl, }; use rpc::json_rpc_server; @@ -65,22 +65,21 @@ fn status_file(stat_file: &str, playout_stat: &PlayoutStatus) { } fn main() { - // Init the config, set process controller, create logging. - init_config(None); - let config = GlobalConfig::global(); + let config = GlobalConfig::new(); + let config_clone = config.clone(); let play_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); let proc_control = ProcessControl::new(); - let logging = init_logging(); + let logging = init_logging(&config); CombinedLogger::init(logging).unwrap(); - validate_ffmpeg(); + validate_ffmpeg(&config); status_file(&config.general.stat_file, &playout_stat); if let Some(range) = config.general.generate.clone() { // run a simple playlist generator and save them to disk - generate_playlist(range); + generate_playlist(&config, range); exit(0); } @@ -91,15 +90,15 @@ fn main() { if config.rpc_server.enable { // If RPC server is enable we also fire up a JSON RPC server. - thread::spawn(move || json_rpc_server(play_ctl, play_stat, proc_ctl)); + thread::spawn(move || json_rpc_server(config_clone, play_ctl, play_stat, proc_ctl)); } if &config.out.mode.to_lowercase() == "hls" { // write files/playlist to HLS m3u8 playlist - write_hls(play_control, playout_stat, proc_control); + write_hls(&config, play_control, playout_stat, proc_control); } else { // play on desktop or stream to a remote target - player(play_control, playout_stat, proc_control); + player(&config, play_control, playout_stat, proc_control); } info!("Playout done..."); diff --git a/src/output/desktop.rs b/src/output/desktop.rs index 51dbbf56..5f0925a7 100644 --- a/src/output/desktop.rs +++ b/src/output/desktop.rs @@ -9,9 +9,7 @@ use crate::vec_strings; /// Desktop Output /// /// Instead of streaming, we run a ffplay instance and play on desktop. -pub fn output(log_format: &str) -> process::Child { - let config = GlobalConfig::global(); - +pub fn output(config: &GlobalConfig, log_format: &str) -> process::Child { let mut enc_filter: Vec = vec![]; let mut enc_cmd = vec_strings!["-hide_banner", "-nostats", "-v", log_format, "-i", "pipe:0"]; @@ -23,7 +21,9 @@ pub fn output(log_format: &str) -> process::Child { ); let mut filter: String = "null,".to_string(); - filter.push_str(v_drawtext::filter_node(&mut Media::new(0, String::new(), false)).as_str()); + filter.push_str( + v_drawtext::filter_node(config, &mut Media::new(0, String::new(), false)).as_str(), + ); enc_filter = vec!["-vf".to_string(), filter]; } diff --git a/src/output/hls.rs b/src/output/hls.rs index 7bf7f8c8..ccfa2b98 100644 --- a/src/output/hls.rs +++ b/src/output/hls.rs @@ -41,17 +41,17 @@ fn format_line(line: String, level: &str) -> String { /// Ingest Server for HLS fn ingest_to_hls_server( + config: GlobalConfig, playout_stat: PlayoutStatus, mut proc_control: ProcessControl, ) -> Result<(), Error> { - let config = GlobalConfig::global(); let playlist_init = playout_stat.list_init; let mut server_cmd = vec_strings!["-hide_banner", "-nostats", "-v", "level+info"]; let stream_input = config.ingest.input_cmd.clone().unwrap(); server_cmd.append(&mut stream_input.clone()); - server_cmd.append(&mut filter_cmd()); + server_cmd.append(&mut filter_cmd(&config)); server_cmd.append(&mut config.out.clone().output_cmd.unwrap()); let mut is_running; @@ -130,11 +130,12 @@ fn ingest_to_hls_server( /// /// Write with single ffmpeg instance directly to a HLS playlist. pub fn write_hls( + config: &GlobalConfig, play_control: PlayerControl, playout_stat: PlayoutStatus, mut proc_control: ProcessControl, ) { - let config = GlobalConfig::global(); + let config_clone = config.clone(); let ff_log_format = format!("level+{}", config.logging.ffmpeg_level.to_lowercase()); let play_stat = playout_stat.clone(); let proc_control_c = proc_control.clone(); @@ -149,7 +150,7 @@ pub fn write_hls( // spawn a thread for ffmpeg ingest server and create a channel for package sending if config.ingest.enable { - thread::spawn(move || ingest_to_hls_server(play_stat, proc_control_c)); + thread::spawn(move || ingest_to_hls_server(config_clone, play_stat, proc_control_c)); } for node in get_source { diff --git a/src/output/mod.rs b/src/output/mod.rs index 4e599dcf..deffb339 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -32,11 +32,12 @@ use crate::vec_strings; /// When a live ingest arrive, it stops the current playing and switch to the live source. /// When ingest stops, it switch back to playlist/folder mode. pub fn player( + config: &GlobalConfig, play_control: PlayerControl, playout_stat: PlayoutStatus, mut proc_control: ProcessControl, ) { - let config = GlobalConfig::global(); + let config_clone = config.clone(); let ff_log_format = format!("level+{}", config.logging.ffmpeg_level.to_lowercase()); let mut buffer = [0; 65088]; let mut live_on = false; @@ -53,8 +54,8 @@ pub fn player( // get ffmpeg output instance let mut enc_proc = match config.out.mode.as_str() { - "desktop" => desktop::output(&ff_log_format), - "stream" => stream::output(&ff_log_format), + "desktop" => desktop::output(config, &ff_log_format), + "stream" => stream::output(config, &ff_log_format), _ => panic!("Output mode doesn't exists!"), }; @@ -74,7 +75,9 @@ pub fn player( if config.ingest.enable { let (ingest_sender, rx) = bounded(96); ingest_receiver = Some(rx); - thread::spawn(move || ingest_server(ff_log_format_c, ingest_sender, proc_control_c)); + thread::spawn(move || { + ingest_server(config_clone, ff_log_format_c, ingest_sender, proc_control_c) + }); } 'source_iter: for node in get_source { diff --git a/src/output/stream.rs b/src/output/stream.rs index 32f86c81..2f74d3bc 100644 --- a/src/output/stream.rs +++ b/src/output/stream.rs @@ -12,8 +12,7 @@ use crate::vec_strings; /// Streaming Output /// /// Prepare the ffmpeg command for streaming output -pub fn output(log_format: &str) -> process::Child { - let config = GlobalConfig::global(); +pub fn output(config: &GlobalConfig, log_format: &str) -> process::Child { let mut enc_filter: Vec = vec![]; let mut preview: Vec = vec_strings![]; let mut preview_cmd = config.out.preview_cmd.as_ref().unwrap().clone(); @@ -36,7 +35,9 @@ pub fn output(log_format: &str) -> process::Child { ); let mut filter = "[0:v]null,".to_string(); - filter.push_str(v_drawtext::filter_node(&mut Media::new(0, String::new(), false)).as_str()); + filter.push_str( + v_drawtext::filter_node(config, &mut Media::new(0, String::new(), false)).as_str(), + ); if config.out.preview { filter.push_str(",split=2[v_out1][v_out2]"); diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index a8e56d1e..75d2a645 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -56,11 +56,13 @@ fn get_data_map(config: &GlobalConfig, media: Media) -> Map { /// - get last clip /// - reset player state to original clip pub fn json_rpc_server( + config: GlobalConfig, play_control: PlayerControl, playout_stat: PlayoutStatus, proc_control: ProcessControl, ) { - let config = GlobalConfig::global(); + let addr = config.rpc_server.address.clone(); + let auth = config.rpc_server.authorization.clone(); let mut io = IoHandler::default(); let proc = proc_control.clone(); @@ -90,10 +92,10 @@ pub fn json_rpc_server( let mut media = play_control.current_list.lock().unwrap()[index].clone(); media.add_probe(); - let (delta, _) = get_delta(&media.begin.unwrap_or(0.0)); + let (delta, _) = get_delta(&config, &media.begin.unwrap_or(0.0)); *time_shift = delta; *date = current_date.clone(); - write_status(¤t_date, delta); + write_status(&config, ¤t_date, delta); data_map.insert("operation".to_string(), json!("move_to_next")); data_map.insert("shifted_seconds".to_string(), json!(delta)); @@ -129,10 +131,10 @@ pub fn json_rpc_server( play_control.index.fetch_sub(2, Ordering::SeqCst); media.add_probe(); - let (delta, _) = get_delta(&media.begin.unwrap_or(0.0)); + let (delta, _) = get_delta(&config, &media.begin.unwrap_or(0.0)); *time_shift = delta; *date = current_date.clone(); - write_status(¤t_date, delta); + write_status(&config, ¤t_date, delta); data_map.insert("operation".to_string(), json!("move_to_last")); data_map.insert("shifted_seconds".to_string(), json!(delta)); @@ -164,7 +166,7 @@ pub fn json_rpc_server( *date = current_date.clone(); playout_stat.list_init.store(true, Ordering::SeqCst); - write_status(¤t_date, 0.0); + write_status(&config, ¤t_date, 0.0); data_map.insert("operation".to_string(), json!("reset_playout_state")); @@ -177,7 +179,7 @@ pub fn json_rpc_server( // get infos about current clip if map.contains_key("media") && &map["media"] == "current" { if let Some(media) = play_control.current_media.lock().unwrap().clone() { - let data_map = get_data_map(config, media); + let data_map = get_data_map(&config, media); return Ok(Value::Object(data_map)); }; @@ -190,7 +192,7 @@ pub fn json_rpc_server( if index < play_control.current_list.lock().unwrap().len() { let media = play_control.current_list.lock().unwrap()[index].clone(); - let data_map = get_data_map(config, media); + let data_map = get_data_map(&config, media); return Ok(Value::Object(data_map)); } @@ -205,7 +207,7 @@ pub fn json_rpc_server( if index > 1 && index - 2 < play_control.current_list.lock().unwrap().len() { let media = play_control.current_list.lock().unwrap()[index - 2].clone(); - let data_map = get_data_map(config, media); + let data_map = get_data_map(&config, media); return Ok(Value::Object(data_map)); } @@ -223,9 +225,9 @@ pub fn json_rpc_server( AccessControlAllowOrigin::Null, ])) // add middleware, for authentication - .request_middleware(|request: hyper::Request| { + .request_middleware(move |request: hyper::Request| { if request.headers().contains_key("authorization") - && request.headers()["authorization"] == config.rpc_server.authorization + && request.headers()["authorization"] == auth { if request.uri() == "/status" { println!("{:?}", request.headers().contains_key("authorization")); @@ -238,7 +240,7 @@ pub fn json_rpc_server( } }) .rest_api(RestApi::Secure) - .start_http(&config.rpc_server.address.parse().unwrap()) + .start_http(&addr.parse().unwrap()) .expect("Unable to start RPC server"); *proc_control.rpc_handle.lock().unwrap() = Some(server.close_handle()); diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 135b8a56..49e87121 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -21,55 +21,49 @@ fn timed_kill(sec: u64, mut proc_ctl: ProcessControl) { #[test] #[ignore] fn playlist_change_at_midnight() { - let config = TestConfig { - mode: "playlist".into(), - start: "00:00:00".into(), - length: "24:00:00".into(), - log_to_file: false, - mail_recipient: "".into(), - }; - - init_config(Some(config)); + let mut config = GlobalConfig::new(); + config.mail.recipient = "".into(); + config.processing.mode = "playlist".into(); + config.playlist.day_start = "00:00:00".into(); + config.playlist.length = "24:00:00".into(); + config.logging.log_to_file = false; let play_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); let proc_control = ProcessControl::new(); let proc_ctl = proc_control.clone(); - let logging = init_logging(); + let logging = init_logging(&config); CombinedLogger::init(logging).unwrap(); mock_time::set_mock_time("2022-05-09T23:59:45"); thread::spawn(move || timed_kill(30, proc_ctl)); - player(play_control, playout_stat, proc_control); + player(&config, play_control, playout_stat, proc_control); } -// #[test] -// #[ignore] -// fn playlist_change_at_six() { -// let config = TestConfig { -// mode: "playlist".into(), -// start: "06:00:00".into(), -// length: "24:00:00".into(), -// log_to_file: false, -// mail_recipient: "".into(), -// }; +#[test] +#[ignore] +fn playlist_change_at_six() { + let mut config = GlobalConfig::new(); + config.mail.recipient = "".into(); + config.processing.mode = "playlist".into(); + config.playlist.day_start = "06:00:00".into(); + config.playlist.length = "24:00:00".into(); + config.logging.log_to_file = false; -// init_config(Some(config)); + let play_control = PlayerControl::new(); + let playout_stat = PlayoutStatus::new(); + let proc_control = ProcessControl::new(); + let proc_ctl = proc_control.clone(); -// let play_control = PlayerControl::new(); -// let playout_stat = PlayoutStatus::new(); -// let proc_control = ProcessControl::new(); -// let proc_ctl = proc_control.clone(); + let logging = init_logging(&config); + CombinedLogger::init(logging).unwrap(); -// let logging = init_logging(); -// CombinedLogger::init(logging).unwrap(); + mock_time::set_mock_time("2022-05-09T05:59:45"); -// mock_time::set_mock_time("2022-05-09T05:59:45"); + thread::spawn(move || timed_kill(30, proc_ctl)); -// thread::spawn(move || timed_kill(30, proc_ctl)); - -// player(play_control, playout_stat, proc_control); -// } + player(&config, play_control, playout_stat, proc_control); +} diff --git a/src/tests/utils/mod.rs b/src/tests/utils/mod.rs index 91835dcb..edd71f49 100644 --- a/src/tests/utils/mod.rs +++ b/src/tests/utils/mod.rs @@ -38,17 +38,15 @@ fn get_date_tomorrow() { #[test] fn test_delta() { - let config = TestConfig { - mode: "playlist".into(), - start: "00:00:00".into(), - length: "24:00:00".into(), - log_to_file: false, - mail_recipient: "".into(), - }; + let mut config = GlobalConfig::new(); + config.mail.recipient = "".into(); + config.processing.mode = "playlist".into(); + config.playlist.day_start = "00:00:00".into(); + config.playlist.length = "24:00:00".into(); + config.logging.log_to_file = false; - init_config(Some(config)); mock_time::set_mock_time("2022-05-09T23:59:59"); - let (delta, _) = get_delta(&86401.0); + let (delta, _) = get_delta(&config, &86401.0); assert!(delta < 2.0); } diff --git a/src/utils/config.rs b/src/utils/config.rs index 6a1f2139..08886368 100644 --- a/src/utils/config.rs +++ b/src/utils/config.rs @@ -5,12 +5,10 @@ use std::{ process, }; -use once_cell::sync::OnceCell; use serde::{Deserialize, Serialize}; -use serde_yaml::{self}; use shlex::split; -use crate::utils::{get_args, time_to_sec, TestConfig}; +use crate::utils::{get_args, time_to_sec}; use crate::vec_strings; /// Global Config @@ -138,7 +136,7 @@ pub struct Out { impl GlobalConfig { /// Read config from YAML file, and set some extra config values. - fn new() -> Self { + pub fn new() -> Self { let args = get_args(); let mut config_path = match env::current_exe() { Ok(path) => path.parent().unwrap().join("ffplayout.yml"), @@ -270,14 +268,8 @@ impl GlobalConfig { config } - - pub fn global() -> &'static GlobalConfig { - INSTANCE.get_or_init(GlobalConfig::new) - } } -static INSTANCE: OnceCell = OnceCell::new(); - /// When add_loudnorm is False we use a different audio encoder, /// s302m has higher quality, but is experimental /// and works not well together with the loudnorm filter. @@ -290,25 +282,3 @@ fn pre_audio_codec(add_loudnorm: bool) -> Vec { codec } - -#[cfg(not(test))] -pub fn init_config(_: Option) { - let config = GlobalConfig::new(); - INSTANCE.set(config).unwrap(); -} - -#[cfg(test)] -pub fn init_config(test_config: Option) { - let mut config = GlobalConfig::new(); - config.out.mode = "desktop".into(); - if let Some(cfg) = test_config { - config.logging.log_to_file = cfg.log_to_file; - config.mail.recipient = cfg.mail_recipient; - config.playlist.day_start = cfg.start.clone(); - config.playlist.start_sec = Some(time_to_sec(&cfg.start)); - config.playlist.length = cfg.length.clone(); - config.playlist.length_sec = Some(time_to_sec(&cfg.length)); - }; - - INSTANCE.set(config).unwrap(); -} diff --git a/src/utils/controller.rs b/src/utils/controller.rs index 8e4b44a3..90da1fb1 100644 --- a/src/utils/controller.rs +++ b/src/utils/controller.rs @@ -136,7 +136,9 @@ impl ProcessControl { for unit in [Encoder, Decoder, Ingest] { if let Err(e) = self.kill(unit) { - error!("{e}") + if !e.contains("exited process") { + error!("{e}") + } } } } diff --git a/src/utils/generator.rs b/src/utils/generator.rs index eb32bf7c..b842deda 100644 --- a/src/utils/generator.rs +++ b/src/utils/generator.rs @@ -50,8 +50,7 @@ fn get_date_range(date_range: &[String]) -> Vec { } /// Generate playlists -pub fn generate_playlist(mut date_range: Vec) { - let config = GlobalConfig::global(); +pub fn generate_playlist(config: &GlobalConfig, mut date_range: Vec) { let total_length = config.playlist.length_sec.unwrap(); let current_list = Arc::new(Mutex::new(vec![Media::new(0, "".to_string(), false)])); let index = Arc::new(AtomicUsize::new(0)); @@ -70,7 +69,7 @@ pub fn generate_playlist(mut date_range: Vec) { date_range = get_date_range(&date_range) } - let media_list = FolderSource::new(current_list, index); + let media_list = FolderSource::new(config, current_list, index); let list_length = media_list.nodes.lock().unwrap().len(); for date in date_range { diff --git a/src/utils/json_serializer.rs b/src/utils/json_serializer.rs index 4214b908..36e504a2 100644 --- a/src/utils/json_serializer.rs +++ b/src/utils/json_serializer.rs @@ -48,13 +48,13 @@ impl Playlist { /// Read json playlist file, fills Playlist struct and set some extra values, /// which we need to process. pub fn read_json( + config: &GlobalConfig, path: Option, is_terminated: Arc, seek: bool, next_start: f64, ) -> Playlist { - let config = GlobalConfig::global(); - + let config_clone = config.clone(); let mut playlist_path = Path::new(&config.playlist.path).to_owned(); let mut start_sec = config.playlist.start_sec.unwrap(); let date = get_date(seek, start_sec, next_start); @@ -113,7 +113,7 @@ pub fn read_json( let list_clone = playlist.clone(); - thread::spawn(move || validate_playlist(list_clone, is_terminated, config.clone())); + thread::spawn(move || validate_playlist(list_clone, is_terminated, config_clone)); playlist } diff --git a/src/utils/logging.rs b/src/utils/logging.rs index 102d6d45..4abaa7b9 100644 --- a/src/utils/logging.rs +++ b/src/utils/logging.rs @@ -25,26 +25,21 @@ use simplelog::*; use crate::utils::GlobalConfig; /// send log messages to mail recipient -fn send_mail(msg: String) { - let config = GlobalConfig::global(); - +fn send_mail(cfg: &GlobalConfig, msg: String) { let email = Message::builder() - .from(config.mail.sender_addr.parse().unwrap()) - .to(config.mail.recipient.parse().unwrap()) - .subject(config.mail.subject.clone()) + .from(cfg.mail.sender_addr.parse().unwrap()) + .to(cfg.mail.recipient.parse().unwrap()) + .subject(cfg.mail.subject.clone()) .header(header::ContentType::TEXT_PLAIN) .body(clean_string(&msg)) .unwrap(); - let credentials = Credentials::new( - config.mail.sender_addr.clone(), - config.mail.sender_pass.clone(), - ); + let credentials = Credentials::new(cfg.mail.sender_addr.clone(), cfg.mail.sender_pass.clone()); - let mut transporter = SmtpTransport::relay(config.mail.smtp_server.clone().as_str()); + let mut transporter = SmtpTransport::relay(cfg.mail.smtp_server.clone().as_str()); - if config.mail.starttls { - transporter = SmtpTransport::starttls_relay(config.mail.smtp_server.clone().as_str()) + if cfg.mail.starttls { + transporter = SmtpTransport::starttls_relay(cfg.mail.smtp_server.clone().as_str()) } let mailer = transporter.unwrap().credentials(credentials).build(); @@ -59,11 +54,11 @@ fn send_mail(msg: String) { /// Basic Mail Queue /// /// Check every give seconds for messages and send them. -fn mail_queue(messages: Arc>>, interval: u64) { +fn mail_queue(cfg: GlobalConfig, messages: Arc>>, interval: u64) { loop { if messages.lock().unwrap().len() > 0 { let msg = messages.lock().unwrap().join("\n"); - send_mail(msg); + send_mail(&cfg, msg); messages.lock().unwrap().clear(); } @@ -141,8 +136,8 @@ fn clean_string(text: &str) -> String { /// - console logger /// - file logger /// - mail logger -pub fn init_logging() -> Vec> { - let config = GlobalConfig::global(); +pub fn init_logging(config: &GlobalConfig) -> Vec> { + let config_clone = config.clone(); let app_config = config.logging.clone(); let mut time_level = LevelFilter::Off; let mut app_logger: Vec> = vec![]; @@ -169,7 +164,7 @@ pub fn init_logging() -> Vec> { let file_config = log_config .clone() .set_time_format_custom(format_description!( - "[[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:5]]" + "[[[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:5]]" )) .build(); let mut log_path = "logs/ffplayout.log".to_string(); @@ -185,20 +180,18 @@ pub fn init_logging() -> Vec> { println!("Logging path not exists!") } - let log = || { - FileRotate::new( - log_path, - AppendTimestamp::with_format( - "%Y-%m-%d", - FileLimit::MaxFiles(app_config.backup_count), - DateFrom::DateYesterday, - ), - ContentLimit::Time(TimeFrequency::Daily), - Compression::None, - ) - }; + let log_file = FileRotate::new( + log_path, + AppendTimestamp::with_format( + "%Y-%m-%d", + FileLimit::MaxFiles(app_config.backup_count), + DateFrom::DateYesterday, + ), + ContentLimit::Time(TimeFrequency::Daily), + Compression::None, + ); - app_logger.push(WriteLogger::new(LevelFilter::Debug, file_config, log())); + app_logger.push(WriteLogger::new(LevelFilter::Debug, file_config, log_file)); } else { let term_config = log_config .clone() @@ -225,7 +218,7 @@ pub fn init_logging() -> Vec> { let messages_clone = messages.clone(); let interval = config.mail.interval; - thread::spawn(move || mail_queue(messages_clone, interval)); + thread::spawn(move || mail_queue(config_clone, messages_clone, interval)); let mail_config = log_config.build(); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 84339a81..e3e11af3 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -26,7 +26,7 @@ mod json_validate; mod logging; pub use arg_parse::get_args; -pub use config::{init_config, GlobalConfig}; +pub use config::GlobalConfig; pub use controller::{PlayerControl, PlayoutStatus, ProcessControl, ProcessUnit::*}; pub use generator::generate_playlist; pub use json_serializer::{read_json, Playlist, DUMMY_LEN}; @@ -119,9 +119,9 @@ impl Media { } } - pub fn add_filter(&mut self) { + pub fn add_filter(&mut self, config: &GlobalConfig) { let mut node = self.clone(); - self.filter = Some(filter_chains(&mut node)) + self.filter = Some(filter_chains(config, &mut node)) } } @@ -190,8 +190,7 @@ impl MediaProbe { /// Write current status to status file in temp folder. /// /// The status file is init in main function and mostly modified in RPC server. -pub fn write_status(date: &str, shift: f64) { - let config = GlobalConfig::global(); +pub fn write_status(config: &GlobalConfig, date: &str, shift: f64) { let stat_file = config.general.stat_file.clone(); let data = json!({ @@ -286,8 +285,7 @@ pub fn is_close(a: f64, b: f64, to: f64) -> bool { /// if we still in sync. /// /// We also get here the global delta between clip start and time when a new playlist should start. -pub fn get_delta(begin: &f64) -> (f64, f64) { - let config = GlobalConfig::global(); +pub fn get_delta(config: &GlobalConfig, begin: &f64) -> (f64, f64) { let mut current_time = get_sec(); let start = config.playlist.start_sec.unwrap(); let length = time_to_sec(&config.playlist.length); @@ -318,9 +316,7 @@ pub fn get_delta(begin: &f64) -> (f64, f64) { } /// Check if clip in playlist is in sync with global time. -pub fn check_sync(delta: f64) -> bool { - let config = GlobalConfig::global(); - +pub fn check_sync(config: &GlobalConfig, delta: f64) -> bool { if delta.abs() > config.general.stop_threshold && config.general.stop_threshold > 0.0 { error!("Clip begin out of sync for {} seconds", delta); return false; @@ -330,8 +326,7 @@ pub fn check_sync(delta: f64) -> bool { } /// Create a dummy clip as a placeholder for missing video files. -pub fn gen_dummy(duration: f64) -> (String, Vec) { - let config = GlobalConfig::global(); +pub fn gen_dummy(config: &GlobalConfig, duration: f64) -> (String, Vec) { let color = "#121212"; let source = format!( "color=c={color}:s={}x{}:d={duration}", @@ -475,9 +470,7 @@ fn ffmpeg_libs_and_filter() -> (Vec, Vec) { /// Validate ffmpeg/ffprobe/ffplay. /// /// Check if they are in system and has all filters and codecs we need. -pub fn validate_ffmpeg() { - let config = GlobalConfig::global(); - +pub fn validate_ffmpeg(config: &GlobalConfig) { is_in_system("ffmpeg"); is_in_system("ffprobe"); @@ -506,15 +499,6 @@ pub fn validate_ffmpeg() { } } -/// In test cases we override some configuration values to fit the needs. -pub struct TestConfig { - pub mode: String, - pub start: String, - pub length: String, - pub log_to_file: bool, - pub mail_recipient: String, -} - /// Get system time, in non test case. #[cfg(not(test))] pub fn time_now() -> DateTime {