diff --git a/assets/ffplayout.yml b/assets/ffplayout.yml index e121438a..5dfe30fa 100644 --- a/assets/ffplayout.yml +++ b/assets/ffplayout.yml @@ -105,14 +105,10 @@ text: help_text: Overlay text in combination with libzmq for remote text manipulation. On windows fontfile path need to be like this 'C\:/WINDOWS/fonts/DejaVuSans.ttf'. In a standard environment the filter drawtext node is Parsed_drawtext_2. - 'over_pre' if True text will be overlay in pre processing. Continue same text - over multiple files is in that mode not possible. 'text_from_filename' activate the - extraction from text of a filename. With 'style' you can define the drawtext - parameters like position, color, etc. Post Text over API will override this. - With 'regex' you can format file names, to get a title from it. + 'text_from_filename' activate the extraction from text of a filename. With 'style' + you can define the drawtext parameters like position, color, etc. Post Text over + API will override this. With 'regex' you can format file names, to get a title from it. add_text: false - over_pre: false - bind_address: "127.0.0.1:5555" fontfile: "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" text_from_filename: false style: "x=(w-tw)/2:y=(h-line_h)*0.9:fontsize=24:fontcolor=#ffffff:box=1:boxcolor=#000000:boxborderw=4" diff --git a/src/filter/mod.rs b/src/filter/mod.rs index a8a2009b..56af4440 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -184,7 +184,9 @@ 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) { - if config.text.add_text && config.text.over_pre { + if config.text.add_text + && (config.text.text_from_filename || config.out.mode.to_lowercase() == "hls") + { let filter = v_drawtext::filter_node(config, node); chain.add_filter(&filter, "video"); diff --git a/src/filter/v_drawtext.rs b/src/filter/v_drawtext.rs index 8ff6cb6e..34820ced 100644 --- a/src/filter/v_drawtext.rs +++ b/src/filter/v_drawtext.rs @@ -4,7 +4,7 @@ use regex::Regex; use crate::utils::{Media, PlayoutConfig}; -pub fn filter_node(config: &PlayoutConfig, node: &mut Media) -> String { +pub fn filter_node(config: &PlayoutConfig, node: &Media) -> String { let mut filter = String::new(); let mut font = String::new(); @@ -13,7 +13,7 @@ pub fn filter_node(config: &PlayoutConfig, node: &mut Media) -> String { font = format!(":fontfile='{}'", config.text.fontfile) } - if config.text.over_pre && config.text.text_from_filename { + if config.text.text_from_filename { let source = node.source.clone(); let regex: Regex = Regex::new(&config.text.regex).unwrap(); @@ -27,10 +27,10 @@ pub fn filter_node(config: &PlayoutConfig, node: &mut Media) -> String { .replace('%', "\\\\\\%") .replace(':', "\\:"); filter = format!("drawtext=text='{escape}':{}{font}", config.text.style) - } else { + } else if let Some(socket) = config.text.bind_address.clone() { filter = format!( "zmq=b=tcp\\\\://'{}',drawtext=text=''{font}", - config.text.bind_address.replace(':', "\\:") + socket.replace(':', "\\:") ) } } diff --git a/src/output/desktop.rs b/src/output/desktop.rs index ffe72f37..8ff89ba3 100644 --- a/src/output/desktop.rs +++ b/src/output/desktop.rs @@ -14,17 +14,19 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child { 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!( - "Using drawtext filter, listening on address: {}", - config.text.bind_address - ); + if config.text.add_text && !config.text.text_from_filename { + if let Some(socket) = config.text.bind_address.clone() { + debug!( + "Using drawtext filter, listening on address: {}", + socket + ); - let mut filter: String = "null,".to_string(); - filter.push_str( - v_drawtext::filter_node(config, &mut Media::new(0, String::new(), false)).as_str(), - ); - enc_filter = vec!["-vf".to_string(), filter]; + let mut filter: String = "null,".to_string(); + filter.push_str( + v_drawtext::filter_node(config, &Media::new(0, String::new(), false)).as_str(), + ); + enc_filter = vec!["-vf".to_string(), filter]; + } } enc_cmd.append(&mut enc_filter); diff --git a/src/output/hls.rs b/src/output/hls.rs index 62dce871..4373bfd9 100644 --- a/src/output/hls.rs +++ b/src/output/hls.rs @@ -13,7 +13,7 @@ out: -hls_time 6 -hls_list_size 600 -hls_flags append_list+delete_segments+omit_endlist+program_date_time - -hls_segment_filename /var/www/html/live/stream-%09d.ts /var/www/html/live/stream.m3u8 + -hls_segment_filename /var/www/html/live/stream-%d.ts /var/www/html/live/stream.m3u8 */ diff --git a/src/output/stream.rs b/src/output/stream.rs index a9687f81..b61af29b 100644 --- a/src/output/stream.rs +++ b/src/output/stream.rs @@ -25,19 +25,21 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child { "pipe:0" ]; - if config.text.add_text && !config.text.over_pre { - info!( - "Using drawtext filter, listening on address: {}", - config.text.bind_address - ); + if config.text.add_text && !config.text.text_from_filename { + if let Some(socket) = config.text.bind_address.clone() { + debug!( + "Using drawtext filter, listening on address: {}", + socket + ); - let mut filter = "[0:v]null,".to_string(); + let mut filter = "[0:v]null,".to_string(); - filter.push_str( - v_drawtext::filter_node(config, &mut Media::new(0, String::new(), false)).as_str(), - ); + filter.push_str( + v_drawtext::filter_node(config, &Media::new(0, String::new(), false)).as_str(), + ); - enc_filter = vec!["-filter_complex".to_string(), filter]; + enc_filter = vec!["-filter_complex".to_string(), filter]; + } } if config.out.preview { diff --git a/src/utils/config.rs b/src/utils/config.rs index b6991ab8..9ad00436 100644 --- a/src/utils/config.rs +++ b/src/utils/config.rs @@ -8,7 +8,7 @@ use std::{ use serde::{Deserialize, Serialize}; use shlex::split; -use crate::utils::{time_to_sec, Args}; +use crate::utils::{free_tcp_socket, time_to_sec, Args}; use crate::vec_strings; /// Global Config @@ -137,8 +137,13 @@ pub struct Storage { pub struct Text { pub help_text: String, pub add_text: bool, - pub over_pre: bool, - pub bind_address: String, + + #[serde(skip_serializing, skip_deserializing)] + pub bind_address: Option, + + #[serde(skip_serializing, skip_deserializing)] + pub node_pos: Option, + pub fontfile: String, pub text_from_filename: bool, pub style: String, @@ -243,6 +248,17 @@ impl PlayoutConfig { config.out.preview_cmd = split(config.out.preview_param.as_str()); config.out.output_cmd = split(config.out.output_param.as_str()); + // when text overlay without text_from_filename is on, turn also the RPC server on, + // to get text messages from it + if config.text.add_text && !config.text.text_from_filename { + config.rpc_server.enable = true; + config.text.bind_address = free_tcp_socket(); + config.text.node_pos = Some(2); + } else { + config.text.bind_address = None; + config.text.node_pos = None; + } + // Read command line arguments, and override the config with them. if let Some(arg) = args { diff --git a/src/utils/json_serializer.rs b/src/utils/json_serializer.rs index a82b530c..2c669cb1 100644 --- a/src/utils/json_serializer.rs +++ b/src/utils/json_serializer.rs @@ -142,7 +142,7 @@ pub fn read_json( return set_defaults(playlist, current_file, start_sec); } - error!("Read playlist error, on: {current_file}!"); + error!("Read playlist error, on: {current_file}"); JsonPlaylist::new(date, start_sec) } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index a73033a9..d5790cd1 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,6 +1,7 @@ use std::{ fs::{self, metadata}, io::{BufRead, BufReader, Error}, + net::TcpListener, path::Path, process::{exit, ChildStderr, Command, Stdio}, time::{self, UNIX_EPOCH}, @@ -9,6 +10,7 @@ use std::{ use chrono::{prelude::*, Duration}; use ffprobe::{ffprobe, Format, Stream}; use jsonrpc_http_server::hyper::HeaderMap; +use rand::prelude::*; use regex::Regex; use reqwest::header; use serde::{Deserialize, Serialize}; @@ -596,6 +598,19 @@ pub fn validate_ffmpeg(config: &PlayoutConfig) { } } +/// get a free tcp socket +pub fn free_tcp_socket() -> Option { + for _ in 0..100 { + let port = rand::thread_rng().gen_range(45321..54268); + + if TcpListener::bind(("127.0.0.1", port)).is_ok() { + return Some(format!("127.0.0.1:{port}")); + } + } + + None +} + /// Get system time, in non test case. #[cfg(not(test))] pub fn time_now() -> DateTime {