From 32454e41d6535c4e60bcf84e91bb1f809bd4a44e Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Wed, 27 Jul 2022 17:58:56 +0200 Subject: [PATCH] v0.13.0 - update to chrono 0.4.20.rc.1 - preserve text filter in HLS mode when clips changes - cross compile ffpapi for macOS --- Cargo.lock | 92 +++++++++----------------- README.md | 2 +- assets/ffplayout.yml | 5 +- docs/api.md | 2 +- ffplayout-api/Cargo.toml | 7 +- ffplayout-api/src/main.rs | 2 +- ffplayout-api/src/utils/routes.rs | 2 +- ffplayout-engine/Cargo.toml | 2 +- ffplayout-engine/src/input/ingest.rs | 3 +- ffplayout-engine/src/input/mod.rs | 2 +- ffplayout-engine/src/input/playlist.rs | 42 ++++++++---- ffplayout-engine/src/output/desktop.rs | 9 ++- ffplayout-engine/src/output/hls.rs | 2 +- ffplayout-engine/src/output/null.rs | 11 +-- ffplayout-engine/src/output/stream.rs | 9 ++- ffplayout-engine/src/rpc/mod.rs | 4 ++ lib/Cargo.toml | 6 +- lib/src/filter/ingest_filter.rs | 12 +++- lib/src/filter/mod.rs | 26 ++++++-- lib/src/filter/v_drawtext.rs | 30 +++++++-- lib/src/utils/controller.rs | 10 +-- lib/src/utils/folder.rs | 9 ++- lib/src/utils/generator.rs | 2 +- lib/src/utils/json_validate.rs | 2 +- lib/src/utils/logging.rs | 2 + lib/src/utils/mod.rs | 10 ++- scripts/build_all.sh | 23 ++++--- 27 files changed, 194 insertions(+), 134 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8f39f082..2a2cbaef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -463,9 +463,9 @@ dependencies = [ [[package]] name = "atoi" -version = "0.4.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" +checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" dependencies = [ "num-traits", ] @@ -629,33 +629,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.20-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "time 0.1.44", - "winapi 0.3.9", -] - -[[package]] -name = "chrono" -version = "0.4.19" -source = "git+https://github.com/sbrocket/chrono?branch=parse-error-kind-public#d9cac30bd0fb46ed410718e6c83753a7c9daa669" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "time 0.1.44", - "winapi 0.3.9", -] - -[[package]] -name = "chrono" -version = "0.4.20-beta.1" -source = "git+https://github.com/chronotope/chrono.git#acd4ecf09fd0e5e35e2b5d5e074f6e1cc77172fc" +checksum = "856628f00a013eb30ae7b136b20fba37f7a39f8026d6f735dfac07c6fce1b8cf" dependencies = [ "num-integer", "num-traits", @@ -777,18 +753,18 @@ dependencies = [ [[package]] name = "crc" -version = "2.1.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23" +checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "1.1.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" +checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" [[package]] name = "crc32fast" @@ -1038,7 +1014,7 @@ dependencies = [ [[package]] name = "ffplayout" -version = "0.12.0" +version = "0.13.0" dependencies = [ "clap", "crossbeam-channel 0.5.6", @@ -1056,7 +1032,7 @@ dependencies = [ [[package]] name = "ffplayout-api" -version = "0.5.0" +version = "0.5.1" dependencies = [ "actix-files", "actix-multipart", @@ -1064,7 +1040,7 @@ dependencies = [ "actix-web-grants", "actix-web-httpauth", "argon2", - "chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", + "chrono", "clap", "derive_more", "faccess", @@ -1086,9 +1062,9 @@ dependencies = [ [[package]] name = "ffplayout-lib" -version = "0.12.0" +version = "0.13.0" dependencies = [ - "chrono 0.4.20-beta.1", + "chrono", "crossbeam-channel 0.5.6", "ffprobe", "file-rotate", @@ -1121,10 +1097,11 @@ dependencies = [ [[package]] name = "file-rotate" -version = "0.6.0" -source = "git+https://github.com/Ploppz/file-rotate.git?branch=timestamp-parse-fix#cb1874a15a7a18de820a57df48d3513e5a4076f4" +version = "0.7.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8453b56141b15cd55cac61ce49326799d31fe4a602ad41f9f5b9b88a42ecbdcf" dependencies = [ - "chrono 0.4.19 (git+https://github.com/sbrocket/chrono?branch=parse-error-kind-public)", + "chrono", "flate2", ] @@ -1427,26 +1404,20 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash 0.7.6", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashlink" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf" +checksum = "d452c155cb93fecdfb02a73dd57b5d8e442c2063bd7aac72f1bc5e4263a43086" dependencies = [ - "hashbrown 0.11.2", + "hashbrown", ] [[package]] @@ -1579,7 +1550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown 0.12.3", + "hashbrown", ] [[package]] @@ -2761,9 +2732,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.5.13" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551873805652ba0d912fec5bbb0f8b4cdd96baf8e2ebf5970e5671092966019b" +checksum = "1f82cbe94f41641d6c410ded25bbf5097c240cefdf8e3b06d04198d0a96af6a4" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2771,16 +2742,15 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.5.13" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5" +checksum = "6b69bf218860335ddda60d6ce85ee39f6cf6e5630e300e19757d1de15886a093" dependencies = [ "ahash 0.7.6", "atoi", "bitflags", "byteorder", "bytes", - "chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)", "crc", "crossbeam-queue 0.3.6", "either", @@ -2814,9 +2784,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.5.13" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1" +checksum = "f40c63177cf23d356b159b60acd27c54af7423f1736988502e36bae9a712118f" dependencies = [ "dotenv", "either", @@ -2833,9 +2803,9 @@ dependencies = [ [[package]] name = "sqlx-rt" -version = "0.5.13" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae" +checksum = "874e93a365a598dc3dadb197565952cb143ae4aa716f7bcc933a8d836f6bf89f" dependencies = [ "actix-rt", "native-tls", diff --git a/README.md b/README.md index 7f36e169..dd8c7105 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) -The ffplayout apps are mostly made to run on Linux as system services. But in general they should run on all platforms which are supported by Rust. At the moment the cross compiled version from *ffpapi* runs on Windows and Linux, and not on Mac. If it is needed there, it should be compile natively. +The ffplayout apps are mostly made to run on Linux as system services. But in general they should run on all platforms which are supported by Rust. Check the [releases](https://github.com/ffplayout/ffplayout/releases/latest) for pre compiled version. diff --git a/assets/ffplayout.yml b/assets/ffplayout.yml index 20be3d71..79194d42 100644 --- a/assets/ffplayout.yml +++ b/assets/ffplayout.yml @@ -116,8 +116,9 @@ text: out: help_text: The final playout compression. Set the settings to your needs. 'mode' - has the options 'desktop', 'hls', 'null', 'stream'. Use 'stream' and adjust 'output_param:' settings, - when you want to stream to a rtmp/rtsp/srt/... server. + has the options 'desktop', 'hls', 'null', 'stream'. Use 'stream' and adjust + 'output_param:' settings, when you want to stream to a rtmp/rtsp/srt/... server. + In production don't server hls playlist with ffpapi, use nginx or another web server! mode: hls output_param: >- -c:v libx264 diff --git a/docs/api.md b/docs/api.md index 896922ae..020ddcaf 100644 --- a/docs/api.md +++ b/docs/api.md @@ -184,7 +184,7 @@ curl -X POST http://127.0.0.1:8787/api/control/1/text/ \ - reset ```BASH -curl -X POST http://127.0.0.1:8787/api/control/1/playout/next/ -H 'Content-Type: application/json' +curl -X POST http://127.0.0.1:8787/api/control/1/playout/ -H 'Content-Type: application/json' -d '{ "command": "reset" }' -H 'Authorization: ' ``` diff --git a/ffplayout-api/Cargo.toml b/ffplayout-api/Cargo.toml index 35f9e004..bf9ac520 100644 --- a/ffplayout-api/Cargo.toml +++ b/ffplayout-api/Cargo.toml @@ -4,7 +4,7 @@ description = "Rest API for ffplayout" license = "GPL-3.0" authors = ["Jonathan Baecker jonbae77@gmail.com"] readme = "README.md" -version = "0.5.0" +version = "0.5.1" edition = "2021" [dependencies] @@ -15,7 +15,7 @@ actix-web = "4" actix-web-grants = "3" actix-web-httpauth = "0.6" argon2 = "0.4" -chrono = "0.4" +chrono = "0.4.20-rc.1" clap = { version = "3.2", features = ["derive"] } derive_more = "0.99" faccess = "0.2" @@ -31,8 +31,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_yaml = "0.8" simplelog = { version = "^0.12", features = ["paris"] } -sqlx = { version = "0.5", features = [ - "chrono", +sqlx = { version = "0.6", features = [ "runtime-actix-native-tls", "sqlite" ] } diff --git a/ffplayout-api/src/main.rs b/ffplayout-api/src/main.rs index 12caee4e..2b651475 100644 --- a/ffplayout-api/src/main.rs +++ b/ffplayout-api/src/main.rs @@ -80,7 +80,7 @@ async fn main() -> std::io::Result<()> { info!("running ffplayout API, listen on {conn}"); - // TODO: add allow origin (or give it to the proxy) + // no allow origin here, give it to the reverse proxy HttpServer::new(move || { let auth = HttpAuthentication::bearer(validator); App::new() diff --git a/ffplayout-api/src/utils/routes.rs b/ffplayout-api/src/utils/routes.rs index d1e7975e..a8efb9de 100644 --- a/ffplayout-api/src/utils/routes.rs +++ b/ffplayout-api/src/utils/routes.rs @@ -482,7 +482,7 @@ pub async fn send_text_message( /// - reset /// /// ```BASH -/// curl -X POST http://127.0.0.1:8787/api/control/1/playout/next/ -H 'Content-Type: application/json' +/// curl -X POST http://127.0.0.1:8787/api/control/1/playout/ -H 'Content-Type: application/json' /// -d '{ "command": "reset" }' -H 'Authorization: ' /// ``` #[post("/control/{id}/playout/")] diff --git a/ffplayout-engine/Cargo.toml b/ffplayout-engine/Cargo.toml index dfcdceee..513d3c78 100644 --- a/ffplayout-engine/Cargo.toml +++ b/ffplayout-engine/Cargo.toml @@ -4,7 +4,7 @@ description = "24/7 playout based on rust and ffmpeg" license = "GPL-3.0" authors = ["Jonathan Baecker jonbae77@gmail.com"] readme = "README.md" -version = "0.12.0" +version = "0.13.0" edition = "2021" [dependencies] diff --git a/ffplayout-engine/src/input/ingest.rs b/ffplayout-engine/src/input/ingest.rs index cc316a30..0a531d9a 100644 --- a/ffplayout-engine/src/input/ingest.rs +++ b/ffplayout-engine/src/input/ingest.rs @@ -2,6 +2,7 @@ use std::{ io::{BufRead, BufReader, Error, Read}, process::{ChildStderr, Command, Stdio}, sync::atomic::Ordering, + sync::{Arc, Mutex}, thread, }; @@ -78,7 +79,7 @@ pub fn ingest_server( let stream_input = config.ingest.input_cmd.clone().unwrap(); server_cmd.append(&mut stream_input.clone()); - server_cmd.append(&mut filter_cmd(&config)); + server_cmd.append(&mut filter_cmd(&config, &Arc::new(Mutex::new(vec![])))); server_cmd.append(&mut config.processing.settings.unwrap()); let mut is_running; diff --git a/ffplayout-engine/src/input/mod.rs b/ffplayout-engine/src/input/mod.rs index 5fcaea62..15e4a8dc 100644 --- a/ffplayout-engine/src/input/mod.rs +++ b/ffplayout-engine/src/input/mod.rs @@ -38,7 +38,7 @@ pub fn source_generator( ); let config_clone = config.clone(); - let folder_source = FolderSource::new(&config, current_list, index); + let folder_source = FolderSource::new(&config, playout_stat.chain, current_list, index); let node_clone = folder_source.nodes.clone(); // Spawn a thread to monitor folder for file changes. diff --git a/ffplayout-engine/src/input/playlist.rs b/ffplayout-engine/src/input/playlist.rs index ddea1050..06268c90 100644 --- a/ffplayout-engine/src/input/playlist.rs +++ b/ffplayout-engine/src/input/playlist.rs @@ -254,7 +254,8 @@ 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(&self.config, node_clone); + self.current_node = + handle_list_init(&self.config, node_clone, &self.playout_stat.chain); } } } @@ -307,7 +308,7 @@ impl Iterator for CurrentProgram { media.duration = duration; media.out = duration; - self.current_node = gen_source(&self.config, media); + self.current_node = gen_source(&self.config, media, &self.playout_stat.chain); let mut nodes = self.nodes.lock().unwrap(); nodes.push(self.current_node.clone()); self.index.store(nodes.len(), Ordering::SeqCst); @@ -364,12 +365,17 @@ impl Iterator for CurrentProgram { } self.current_node.duration = duration; self.current_node.out = duration; - self.current_node = gen_source(&self.config, self.current_node.clone()); + self.current_node = gen_source( + &self.config, + self.current_node.clone(), + &self.playout_stat.chain, + ); 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.config); + self.current_node + .add_filter(&self.config, &self.playout_stat.chain); self.index.fetch_add(1, Ordering::SeqCst); @@ -377,7 +383,11 @@ impl Iterator for CurrentProgram { } self.index.store(0, Ordering::SeqCst); - self.current_node = gen_source(&self.config, self.nodes.lock().unwrap()[0].clone()); + self.current_node = gen_source( + &self.config, + self.nodes.lock().unwrap()[0].clone(), + &self.playout_stat.chain, + ); self.last_next_ad(); self.current_node.last_ad = last_ad; @@ -432,20 +442,24 @@ fn timed_source( || !config.playlist.length.contains(':') { // when we are in the 24 hour range, get the clip - new_node = gen_source(config, node); + new_node = gen_source(config, node, &playout_stat.chain); new_node.process = Some(true); } else if total_delta <= 0.0 { info!("Begin is over play time, skip: {}", node.source); } else if total_delta < node.duration - node.seek || last { new_node = handle_list_end(node, total_delta); - new_node.add_filter(config); + new_node.add_filter(config, &playout_stat.chain); } new_node } /// Generate the source CMD, or when clip not exist, get a dummy. -fn gen_source(config: &PlayoutConfig, mut node: Media) -> Media { +fn gen_source( + config: &PlayoutConfig, + mut node: Media, + filter_chain: &Arc>>, +) -> Media { if valid_source(&node.source) { node.add_probe(); node.cmd = Some(seek_and_length( @@ -454,7 +468,7 @@ fn gen_source(config: &PlayoutConfig, mut node: Media) -> Media { node.out, node.duration, )); - node.add_filter(config); + node.add_filter(config, filter_chain); } else { if node.source.is_empty() { warn!( @@ -467,7 +481,7 @@ fn gen_source(config: &PlayoutConfig, mut node: Media) -> Media { let (source, cmd) = gen_dummy(config, node.out - node.seek); node.source = source; node.cmd = Some(cmd); - node.add_filter(config); + node.add_filter(config, filter_chain); } node @@ -475,7 +489,11 @@ fn gen_source(config: &PlayoutConfig, 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(config: &PlayoutConfig, mut node: Media) -> Media { +fn handle_list_init( + config: &PlayoutConfig, + mut node: Media, + filter_chain: &Arc>>, +) -> Media { debug!("Playlist init"); let (_, total_delta) = get_delta(config, &node.begin.unwrap()); let mut out = node.out; @@ -485,7 +503,7 @@ fn handle_list_init(config: &PlayoutConfig, mut node: Media) -> Media { } node.out = out; - gen_source(config, node) + gen_source(config, node, filter_chain) } /// when we come to last clip in playlist, diff --git a/ffplayout-engine/src/output/desktop.rs b/ffplayout-engine/src/output/desktop.rs index 7c88bc06..a6c45c6a 100644 --- a/ffplayout-engine/src/output/desktop.rs +++ b/ffplayout-engine/src/output/desktop.rs @@ -1,9 +1,12 @@ -use std::process::{self, Command, Stdio}; +use std::{ + process::{self, Command, Stdio}, + sync::{Arc, Mutex}, +}; use simplelog::*; use ffplayout_lib::filter::v_drawtext; -use ffplayout_lib::utils::{Media, PlayoutConfig}; +use ffplayout_lib::utils::PlayoutConfig; use ffplayout_lib::vec_strings; /// Desktop Output @@ -23,7 +26,7 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child { let mut filter: String = "null,".to_string(); filter.push_str( - v_drawtext::filter_node(config, &Media::new(0, String::new(), false)).as_str(), + v_drawtext::filter_node(config, None, &Arc::new(Mutex::new(vec![]))).as_str(), ); enc_filter = vec!["-vf".to_string(), filter]; } diff --git a/ffplayout-engine/src/output/hls.rs b/ffplayout-engine/src/output/hls.rs index 38315e26..23a2d08a 100644 --- a/ffplayout-engine/src/output/hls.rs +++ b/ffplayout-engine/src/output/hls.rs @@ -47,7 +47,7 @@ fn ingest_to_hls_server( let mut server_prefix = vec_strings!["-hide_banner", "-nostats", "-v", "level+info"]; let mut stream_input = config.ingest.input_cmd.clone().unwrap(); server_prefix.append(&mut stream_input); - let server_filter = filter_cmd(&config); + let server_filter = filter_cmd(&config, &playout_stat.chain); let server_cmd = prepare_output_cmd( server_prefix, diff --git a/ffplayout-engine/src/output/null.rs b/ffplayout-engine/src/output/null.rs index 73b73fda..b3d13568 100644 --- a/ffplayout-engine/src/output/null.rs +++ b/ffplayout-engine/src/output/null.rs @@ -1,10 +1,11 @@ -use std::process::{self, Command, Stdio}; +use std::{ + process::{self, Command, Stdio}, + sync::{Arc, Mutex}, +}; use simplelog::*; -use ffplayout_lib::filter::v_drawtext; -use ffplayout_lib::utils::{Media, PlayoutConfig}; -use ffplayout_lib::vec_strings; +use ffplayout_lib::{filter::v_drawtext, utils::PlayoutConfig, vec_strings}; /// Desktop Output /// @@ -34,7 +35,7 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child { let mut filter: String = "null,".to_string(); filter.push_str( - v_drawtext::filter_node(config, &Media::new(0, String::new(), false)).as_str(), + v_drawtext::filter_node(config, None, &Arc::new(Mutex::new(vec![]))).as_str(), ); enc_filter = vec!["-vf".to_string(), filter]; } diff --git a/ffplayout-engine/src/output/stream.rs b/ffplayout-engine/src/output/stream.rs index 85d69f7d..8b820556 100644 --- a/ffplayout-engine/src/output/stream.rs +++ b/ffplayout-engine/src/output/stream.rs @@ -1,9 +1,12 @@ -use std::process::{self, Command, Stdio}; +use std::{ + process::{self, Command, Stdio}, + sync::{Arc, Mutex}, +}; use simplelog::*; use ffplayout_lib::filter::v_drawtext; -use ffplayout_lib::utils::{prepare_output_cmd, Media, PlayoutConfig}; +use ffplayout_lib::utils::{prepare_output_cmd, PlayoutConfig}; use ffplayout_lib::vec_strings; /// Streaming Output @@ -34,7 +37,7 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child { let mut filter = "[0:v]null,".to_string(); filter.push_str( - v_drawtext::filter_node(config, &Media::new(0, String::new(), false)).as_str(), + v_drawtext::filter_node(config, None, &Arc::new(Mutex::new(vec![]))).as_str(), ); enc_filter = vec!["-filter_complex".to_string(), filter]; diff --git a/ffplayout-engine/src/rpc/mod.rs b/ffplayout-engine/src/rpc/mod.rs index 9e85bedd..5dded58f 100644 --- a/ffplayout-engine/src/rpc/mod.rs +++ b/ffplayout-engine/src/rpc/mod.rs @@ -86,8 +86,12 @@ pub fn json_rpc_server( let mut filter = get_filter_from_json(map["message"].to_string()); let socket = config.text.bind_address.clone(); + // TODO: in Rust 1.64 use let_chains instead if !filter.is_empty() && config.text.bind_address.is_some() { + let mut clips_filter = playout_stat.chain.lock().unwrap(); + *clips_filter = vec![filter.clone()]; filter = format!("Parsed_drawtext_2 reinit {filter}"); + if let Ok(reply) = executor::block_on(zmq_send(&filter, &socket.unwrap())) { return Ok(Value::String(reply)); }; diff --git a/lib/Cargo.toml b/lib/Cargo.toml index edc8a745..63a43cae 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -4,14 +4,14 @@ description = "Library for ffplayout" license = "GPL-3.0" authors = ["Jonathan Baecker jonbae77@gmail.com"] readme = "README.md" -version = "0.12.0" +version = "0.13.0" edition = "2021" [dependencies] -chrono = { git = "https://github.com/chronotope/chrono.git" } +chrono = "0.4.20-rc.1" crossbeam-channel = "0.5" ffprobe = "0.3" -file-rotate = { git = "https://github.com/Ploppz/file-rotate.git", branch = "timestamp-parse-fix" } +file-rotate = "0.7.0-rc.0" jsonrpc-http-server = "18.0" lettre = "0.10" log = "0.4" diff --git a/lib/src/filter/ingest_filter.rs b/lib/src/filter/ingest_filter.rs index 577221c7..f5dea200 100644 --- a/lib/src/filter/ingest_filter.rs +++ b/lib/src/filter/ingest_filter.rs @@ -1,4 +1,6 @@ -use crate::filter::{a_loudnorm, v_overlay}; +use std::sync::{Arc, Mutex}; + +use crate::filter::{a_loudnorm, v_drawtext, v_overlay}; use crate::utils::PlayoutConfig; /// Audio Filter @@ -22,7 +24,7 @@ fn audio_filter(config: &PlayoutConfig) -> String { } /// Create filter nodes for ingest live stream. -pub fn filter_cmd(config: &PlayoutConfig) -> Vec { +pub fn filter_cmd(config: &PlayoutConfig, filter_chain: &Arc>>) -> Vec { let mut filter = format!( "[0:v]fps={},scale={}:{},setdar=dar={},fade=in:st=0:d=0.5", config.processing.fps, @@ -32,12 +34,18 @@ pub fn filter_cmd(config: &PlayoutConfig) -> Vec { ); let overlay = v_overlay::filter_node(config, true); + let drawtext = v_drawtext::filter_node(config, None, filter_chain); if !overlay.is_empty() { filter.push(','); } + if !drawtext.is_empty() { + filter.push(','); + } + filter.push_str(&overlay); + filter.push_str(&drawtext); filter.push_str("[vout1]"); filter.push_str(audio_filter(config).as_str()); diff --git a/lib/src/filter/mod.rs b/lib/src/filter/mod.rs index ade4edc5..9f9bc071 100644 --- a/lib/src/filter/mod.rs +++ b/lib/src/filter/mod.rs @@ -1,4 +1,7 @@ -use std::path::Path; +use std::{ + path::Path, + sync::{Arc, Mutex}, +}; use simplelog::*; @@ -100,7 +103,9 @@ fn scale(v_stream: &ffprobe::Stream, aspect: f64, chain: &mut Filters, config: & config.processing.width, config.processing.height ), "video", - ) + ); + } else { + chain.add_filter("null", "video"); } if !is_close(aspect, config.processing.aspect, 0.03) { @@ -183,11 +188,16 @@ 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: &PlayoutConfig) { +fn add_text( + node: &mut Media, + chain: &mut Filters, + config: &PlayoutConfig, + filter_chain: &Arc>>, +) { if config.text.add_text && (config.text.text_from_filename || config.out.mode.to_lowercase() == "hls") { - let filter = v_drawtext::filter_node(config, node); + let filter = v_drawtext::filter_node(config, Some(node), filter_chain); chain.add_filter(&filter, "video"); } @@ -298,7 +308,11 @@ fn realtime_filter( } } -pub fn filter_chains(config: &PlayoutConfig, node: &mut Media) -> Vec { +pub fn filter_chains( + config: &PlayoutConfig, + node: &mut Media, + filter_chain: &Arc>>, +) -> Vec { let mut filters = Filters::new(); if let Some(probe) = node.probe.as_ref() { @@ -324,7 +338,7 @@ pub fn filter_chains(config: &PlayoutConfig, node: &mut Media) -> Vec { extend_audio(node, &mut filters); } - add_text(node, &mut filters, config); + add_text(node, &mut filters, config, filter_chain); fade(node, &mut filters, "video"); overlay(node, &mut filters, config); realtime_filter(node, &mut filters, config, "video"); diff --git a/lib/src/filter/v_drawtext.rs b/lib/src/filter/v_drawtext.rs index 34820ced..36ada6fe 100644 --- a/lib/src/filter/v_drawtext.rs +++ b/lib/src/filter/v_drawtext.rs @@ -1,10 +1,17 @@ -use std::path::Path; +use std::{ + path::Path, + sync::{Arc, Mutex}, +}; use regex::Regex; use crate::utils::{Media, PlayoutConfig}; -pub fn filter_node(config: &PlayoutConfig, node: &Media) -> String { +pub fn filter_node( + config: &PlayoutConfig, + node: Option<&Media>, + filter_chain: &Arc>>, +) -> String { let mut filter = String::new(); let mut font = String::new(); @@ -13,8 +20,12 @@ pub fn filter_node(config: &PlayoutConfig, node: &Media) -> String { font = format!(":fontfile='{}'", config.text.fontfile) } - if config.text.text_from_filename { - let source = node.source.clone(); + // TODO: in Rust 1.64 use let_chains instead + if config.text.text_from_filename && node.is_some() { + let source = node + .unwrap_or(&Media::new(0, String::new(), false)) + .source + .clone(); let regex: Regex = Regex::new(&config.text.regex).unwrap(); let text: String = match regex.captures(&source) { @@ -28,8 +39,17 @@ pub fn filter_node(config: &PlayoutConfig, node: &Media) -> String { .replace(':', "\\:"); filter = format!("drawtext=text='{escape}':{}{font}", config.text.style) } else if let Some(socket) = config.text.bind_address.clone() { + let chain = filter_chain.lock().unwrap(); + let mut filter_cmd = format!("text=''{font}"); + + if !chain.is_empty() { + if let Some(link) = chain.iter().find(|&l| l.contains("text")) { + filter_cmd = link.to_string(); + } + } + filter = format!( - "zmq=b=tcp\\\\://'{}',drawtext=text=''{font}", + "zmq=b=tcp\\\\://'{}',drawtext={filter_cmd}", socket.replace(':', "\\:") ) } diff --git a/lib/src/utils/controller.rs b/lib/src/utils/controller.rs index f6baa2a8..4da12c10 100644 --- a/lib/src/utils/controller.rs +++ b/lib/src/utils/controller.rs @@ -178,19 +178,21 @@ impl Default for PlayerControl { /// Global playout control, for move forward/backward clip, or resetting playlist/state. #[derive(Clone, Debug)] pub struct PlayoutStatus { - pub time_shift: Arc>, - pub date: Arc>, + pub chain: Arc>>, pub current_date: Arc>, + pub date: Arc>, pub list_init: Arc, + pub time_shift: Arc>, } impl PlayoutStatus { pub fn new() -> Self { Self { - time_shift: Arc::new(Mutex::new(0.0)), - date: Arc::new(Mutex::new(String::new())), + chain: Arc::new(Mutex::new(vec![])), current_date: Arc::new(Mutex::new(String::new())), + date: Arc::new(Mutex::new(String::new())), list_init: Arc::new(AtomicBool::new(true)), + time_shift: Arc::new(Mutex::new(0.0)), } } } diff --git a/lib/src/utils/folder.rs b/lib/src/utils/folder.rs index 0aa569f4..72ea7bf1 100644 --- a/lib/src/utils/folder.rs +++ b/lib/src/utils/folder.rs @@ -19,6 +19,7 @@ use crate::utils::{file_extension, get_sec, Media, PlayoutConfig}; #[derive(Debug, Clone)] pub struct FolderSource { config: PlayoutConfig, + filter_chain: Arc>>, pub nodes: Arc>>, current_node: Media, index: Arc, @@ -27,6 +28,7 @@ pub struct FolderSource { impl FolderSource { pub fn new( config: &PlayoutConfig, + filter_chain: Arc>>, current_list: Arc>>, global_index: Arc, ) -> Self { @@ -77,6 +79,7 @@ impl FolderSource { Self { config: config.clone(), + filter_chain, nodes: current_list, current_node: Media::new(0, String::new(), false), index: global_index, @@ -114,7 +117,8 @@ 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.config); + self.current_node + .add_filter(&self.config, &self.filter_chain); self.current_node.begin = Some(get_sec()); self.index.fetch_add(1, Ordering::SeqCst); @@ -137,7 +141,8 @@ impl Iterator for FolderSource { self.current_node = self.nodes.lock().unwrap()[0].clone(); self.current_node.add_probe(); - self.current_node.add_filter(&self.config); + self.current_node + .add_filter(&self.config, &self.filter_chain); self.current_node.begin = Some(get_sec()); self.index.store(1, Ordering::SeqCst); diff --git a/lib/src/utils/generator.rs b/lib/src/utils/generator.rs index 4baf6a25..a36a766a 100644 --- a/lib/src/utils/generator.rs +++ b/lib/src/utils/generator.rs @@ -93,7 +93,7 @@ pub fn generate_playlist( date_range = get_date_range(&date_range) } - let media_list = FolderSource::new(config, current_list, index); + let media_list = FolderSource::new(config, Arc::new(Mutex::new(vec![])), current_list, index); let list_length = media_list.nodes.lock().unwrap().len(); for date in date_range { diff --git a/lib/src/utils/json_validate.rs b/lib/src/utils/json_validate.rs index f6b47fd9..94bf850b 100644 --- a/lib/src/utils/json_validate.rs +++ b/lib/src/utils/json_validate.rs @@ -37,7 +37,7 @@ pub fn validate_playlist( if probe.format.is_none() { error!( - "No Metadata from file {} at {}", + "No Metadata at {}, from file {}", sec_to_time(begin), item.source ); diff --git a/lib/src/utils/logging.rs b/lib/src/utils/logging.rs index 466e85e4..f627c044 100644 --- a/lib/src/utils/logging.rs +++ b/lib/src/utils/logging.rs @@ -229,6 +229,8 @@ pub fn init_logging( ), ContentLimit::Time(TimeFrequency::Daily), Compression::None, + #[cfg(unix)] + None, ); app_logger.push(WriteLogger::new(LevelFilter::Debug, file_config, log_file)); diff --git a/lib/src/utils/mod.rs b/lib/src/utils/mod.rs index 5a78ed58..22f39106 100644 --- a/lib/src/utils/mod.rs +++ b/lib/src/utils/mod.rs @@ -5,6 +5,7 @@ use std::{ net::TcpListener, path::Path, process::{ChildStderr, Command, Stdio}, + sync::{Arc, Mutex}, time::{self, UNIX_EPOCH}, }; @@ -125,9 +126,9 @@ impl Media { } } - pub fn add_filter(&mut self, config: &PlayoutConfig) { + pub fn add_filter(&mut self, config: &PlayoutConfig, filter_chain: &Arc>>) { let mut node = self.clone(); - self.filter = Some(filter_chains(config, &mut node)) + self.filter = Some(filter_chains(config, &mut node, filter_chain)) } } @@ -537,6 +538,11 @@ pub fn stderr_reader(buffer: BufReader, suffix: &str) -> Result<(), "[{suffix}] {}", format_log_line(line, "warning") ) + } else if line.contains("[error]") { + error!( + "[{suffix}] {}", + format_log_line(line, "error") + ) } } diff --git a/scripts/build_all.sh b/scripts/build_all.sh index 301436e3..db123b91 100755 --- a/scripts/build_all.sh +++ b/scripts/build_all.sh @@ -1,13 +1,15 @@ #!/usr/bin/bash -cd ffplayout-frontend +if [[ ! -d public ]]; then + cd ffplayout-frontend -npm install -npm run build -yes | rm -rf ../public -mv dist ../public + npm install + npm run build + yes | rm -rf ../public + mv dist ../public -cd .. + cd .. +fi targets=("x86_64-unknown-linux-musl" "aarch64-unknown-linux-gnu" "x86_64-pc-windows-gnu" "x86_64-apple-darwin" "aarch64-apple-darwin") @@ -41,11 +43,12 @@ for target in "${targets[@]}"; do rm -f "ffplayout-v${version}_${target}.tar.gz" fi - cargo build --release --target=$target --bin ffplayout + CC="x86_64-apple-darwin20.4-cc" cargo build --release --target=$target + cp ./target/${target}/release/ffpapi . cp ./target/${target}/release/ffplayout . - tar -czvf "ffplayout-v${version}_${target}.tar.gz" --exclude='*.db' assets docs LICENSE README.md ffplayout - rm -f ffplayout + tar -czvf "ffplayout-v${version}_${target}.tar.gz" --exclude='*.db' assets docs public LICENSE README.md ffplayout ffpapi + rm -f ffplayout ffpapi else if [[ -f "ffplayout-v${version}_${target}.tar.gz" ]]; then rm -f "ffplayout-v${version}_${target}.tar.gz" @@ -55,7 +58,7 @@ for target in "${targets[@]}"; do cp ./target/${target}/release/ffpapi . cp ./target/${target}/release/ffplayout . - tar -czvf "ffplayout-v${version}_${target}.tar.gz" --exclude='*.db' assets docs LICENSE README.md ffplayout ffpapi + tar -czvf "ffplayout-v${version}_${target}.tar.gz" --exclude='*.db' assets docs public LICENSE README.md ffplayout ffpapi rm -f ffplayout ffpapi fi