Merge pull request #175 from jb-alvarado/master

fix drawtext, status file in home dir (fallback to temp)
This commit is contained in:
jb-alvarado 2022-08-10 11:34:54 +02:00 committed by GitHub
commit 7e88e6001f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 211 additions and 54 deletions

63
Cargo.lock generated
View File

@ -217,7 +217,7 @@ dependencies = [
"serde_urlencoded",
"smallvec",
"socket2",
"time 0.3.12",
"time 0.3.13",
"url",
]
@ -308,6 +308,15 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "android_system_properties"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
dependencies = [
"libc",
]
[[package]]
name = "argon2"
version = "0.4.1"
@ -337,9 +346,9 @@ dependencies = [
[[package]]
name = "async-channel"
version = "1.6.1"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
checksum = "4b31b87a3367ed04dbcbc252bce3f2a8172fef861d47177524c503c908dff2c6"
dependencies = [
"concurrent-queue",
"event-listener",
@ -629,10 +638,11 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.20"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6127248204b9aba09a362f6c930ef6a78f2c1b2215f8a7b398c06e1083f17af0"
checksum = "3f725f340c3854e3cb3ab736dc21f0cca183303acea3b3ffec30f141503ac8eb"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
@ -724,7 +734,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05"
dependencies = [
"percent-encoding",
"time 0.3.12",
"time 0.3.13",
"version_check",
]
@ -987,9 +997,9 @@ dependencies = [
[[package]]
name = "email_address"
version = "0.2.1"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8684b7c9cb4857dfa1e5b9629ef584ba618c9b93bae60f58cb23f4f271d0468e"
checksum = "b1b32a7a2580c4473f10f66b512c34bdd7d33c5e3473227ca833abdb5afe4809"
[[package]]
name = "encoding_rs"
@ -1106,8 +1116,9 @@ dependencies = [
"serde_yaml",
"shlex",
"simplelog",
"time 0.3.12",
"time 0.3.13",
"walkdir",
"winapi 0.3.9",
]
[[package]]
@ -1557,6 +1568,19 @@ dependencies = [
"tokio-native-tls",
]
[[package]]
name = "iana-time-zone"
version = "0.1.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1779539f58004e5dba1c1f093d44325ebeb244bfc04b791acdc0aaeca9c04570"
dependencies = [
"android_system_properties",
"core-foundation",
"js-sys",
"wasm-bindgen",
"winapi 0.3.9",
]
[[package]]
name = "idna"
version = "0.2.3"
@ -1779,9 +1803,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.127"
version = "0.2.129"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b"
checksum = "64de3cc433455c14174d42e554d4027ee631c4d046d43e3ecc6efc4636cdc7a7"
[[package]]
name = "libsqlite3-sys"
@ -2601,18 +2625,18 @@ checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711"
[[package]]
name = "serde"
version = "1.0.142"
version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2"
checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.142"
version = "1.0.143"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e"
checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
dependencies = [
"proc-macro2",
"quote",
@ -2700,7 +2724,7 @@ dependencies = [
"num-bigint",
"num-traits",
"thiserror",
"time 0.3.12",
"time 0.3.13",
]
[[package]]
@ -2712,7 +2736,7 @@ dependencies = [
"log",
"paris",
"termcolor",
"time 0.3.12",
"time 0.3.13",
]
[[package]]
@ -2945,12 +2969,11 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74b7cc93fc23ba97fde84f7eea56c55d1ba183f495c6715defdfc7b9cb8c870f"
checksum = "db76ff9fa4b1458b3c7f077f3ff9887394058460d21e634355b273aaf11eea45"
dependencies = [
"itoa",
"js-sys",
"libc",
"num_threads",
"time-macros",

View File

@ -15,7 +15,7 @@ actix-web = "4"
actix-web-grants = "3"
actix-web-httpauth = "0.6"
argon2 = "0.4"
chrono = "0.4.20"
chrono = "0.4"
clap = { version = "3.2", features = ["derive"] }
derive_more = "0.99"
faccess = "0.2"

View File

@ -34,6 +34,11 @@ pub fn log_line(line: String, level: &str) {
"<bright black>[Server]</> {}",
format_log_line(line, "error")
);
} else if line.contains("[fatal]") {
error!(
"<bright black>[Server]</> {}",
format_log_line(line, "fatal")
)
}
}

View File

@ -42,6 +42,8 @@ struct StatusData {
///
/// When file not exists we create it, and when it exists we get its values.
fn status_file(stat_file: &str, playout_stat: &PlayoutStatus) {
debug!("Status file path: <b><magenta>{stat_file}</></b>");
if !PathBuf::from(stat_file).exists() {
let data = json!({
"time_shift": 0.0,

View File

@ -18,7 +18,7 @@ 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.text_from_filename {
if let Some(socket) = config.text.bind_address.clone() {
if let Some(socket) = config.text.zmq_stream_socket.clone() {
debug!(
"Using drawtext filter, listening on address: <yellow>{}</>",
socket
@ -26,7 +26,8 @@ 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, None, &Arc::new(Mutex::new(vec![]))).as_str(),
v_drawtext::filter_node(config, None, &Arc::new(Mutex::new(vec![])), false)
.as_str(),
);
enc_filter = vec!["-vf".to_string(), filter];
}

View File

@ -49,6 +49,20 @@ fn ingest_to_hls_server(
server_prefix.append(&mut stream_input);
let server_filter = filter_cmd(&config, &playout_stat.chain);
if server_filter.len() > 1 {
let filter_chain = server_filter[1]
.split_terminator([',', ';'])
.collect::<Vec<&str>>();
for (i, link) in filter_chain.iter().enumerate() {
if link.contains("drawtext") {
playout_stat
.drawtext_server_index
.store(i, Ordering::SeqCst);
}
}
}
let server_cmd = prepare_output_cmd(
server_prefix,
server_filter,

View File

@ -27,7 +27,7 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child {
];
if config.text.add_text && !config.text.text_from_filename {
if let Some(socket) = config.text.bind_address.clone() {
if let Some(socket) = config.text.zmq_stream_socket.clone() {
debug!(
"Using drawtext filter, listening on address: <yellow>{}</>",
socket
@ -35,7 +35,8 @@ 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, None, &Arc::new(Mutex::new(vec![]))).as_str(),
v_drawtext::filter_node(config, None, &Arc::new(Mutex::new(vec![])), false)
.as_str(),
);
enc_filter = vec!["-vf".to_string(), filter];
}

View File

@ -28,7 +28,7 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child {
];
if config.text.add_text && !config.text.text_from_filename {
if let Some(socket) = config.text.bind_address.clone() {
if let Some(socket) = config.text.zmq_stream_socket.clone() {
debug!(
"Using drawtext filter, listening on address: <yellow>{}</>",
socket
@ -37,7 +37,8 @@ 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, None, &Arc::new(Mutex::new(vec![]))).as_str(),
v_drawtext::filter_node(config, None, &Arc::new(Mutex::new(vec![])), false)
.as_str(),
);
enc_filter = vec!["-filter_complex".to_string(), filter];

View File

@ -1,8 +1,8 @@
use futures::executor;
use std::{process::exit, sync::atomic::Ordering};
mod zmq_cmd;
use futures::executor::block_on;
use jsonrpc_http_server::{
hyper,
jsonrpc_core::{IoHandler, Params, Value},
@ -83,18 +83,40 @@ pub fn json_rpc_server(
&& &map["control"] == "text"
&& map.contains_key("message")
{
let mut filter = get_filter_from_json(map["message"].to_string());
let socket = config.text.bind_address.clone();
let filter = get_filter_from_json(map["message"].to_string());
// TODO: in Rust 1.64 use let_chains instead
if !filter.is_empty() && config.text.bind_address.is_some() {
if !filter.is_empty() && config.text.zmq_stream_socket.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));
};
if config.out.mode != "hls" || !proc.server_is_running.load(Ordering::SeqCst) {
let filter_stream = format!(
"Parsed_drawtext_{} reinit {filter}",
playout_stat.drawtext_stream_index.load(Ordering::SeqCst)
);
if let Ok(reply) = block_on(zmq_send(
&filter_stream,
&config.text.zmq_stream_socket.clone().unwrap(),
)) {
return Ok(Value::String(reply));
};
}
if config.out.mode == "hls" && proc.server_is_running.load(Ordering::SeqCst) {
let filter_server = format!(
"Parsed_drawtext_{} reinit {filter}",
playout_stat.drawtext_server_index.load(Ordering::SeqCst)
);
if let Ok(reply) = block_on(zmq_send(
&filter_server,
&config.text.zmq_server_socket.clone().unwrap(),
)) {
return Ok(Value::String(reply));
};
}
}
return Ok(Value::String("Last clip can not be skipped".to_string()));

View File

@ -8,7 +8,7 @@ version = "0.14.0"
edition = "2021"
[dependencies]
chrono = "0.4.20"
chrono = "0.4"
crossbeam-channel = "0.5"
ffprobe = "0.3"
file-rotate = "0.7.0"
@ -27,5 +27,13 @@ simplelog = { version = "^0.12", features = ["paris"] }
time = { version = "0.3", features = ["formatting", "macros"] }
walkdir = "2"
[target."cfg(windows)".dependencies.winapi]
version = "0.3"
features = [
"shlobj",
"std",
"winerror",
]
[target.x86_64-unknown-linux-musl.dependencies]
openssl = { version = "0.10", features = ["vendored"] }

View File

@ -34,18 +34,18 @@ pub fn filter_cmd(config: &PlayoutConfig, filter_chain: &Arc<Mutex<Vec<String>>>
);
let overlay = v_overlay::filter_node(config, true);
let drawtext = v_drawtext::filter_node(config, None, filter_chain);
let drawtext = v_drawtext::filter_node(config, None, filter_chain, true);
if !overlay.is_empty() {
filter.push(',');
filter.push_str(&overlay);
}
if !drawtext.is_empty() {
if config.out.mode == "hls" && !drawtext.is_empty() {
filter.push(',');
filter.push_str(&drawtext);
}
filter.push_str(&overlay);
filter.push_str(&drawtext);
filter.push_str("[vout1]");
filter.push_str(audio_filter(config).as_str());

View File

@ -205,7 +205,7 @@ fn add_text(
if config.text.add_text
&& (config.text.text_from_filename || config.out.mode.to_lowercase() == "hls")
{
let filter = v_drawtext::filter_node(config, Some(node), filter_chain);
let filter = v_drawtext::filter_node(config, Some(node), filter_chain, false);
chain.add_filter(&filter, "video");
}

View File

@ -11,6 +11,7 @@ pub fn filter_node(
config: &PlayoutConfig,
node: Option<&Media>,
filter_chain: &Arc<Mutex<Vec<String>>>,
is_server: bool,
) -> String {
let mut filter = String::new();
let mut font = String::new();
@ -20,6 +21,11 @@ pub fn filter_node(
font = format!(":fontfile='{}'", config.text.fontfile)
}
let zmq_socket = match is_server {
true => config.text.zmq_server_socket.clone(),
false => config.text.zmq_stream_socket.clone(),
};
// TODO: in Rust 1.64 use let_chains instead
if config.text.text_from_filename && node.is_some() {
let source = node
@ -38,7 +44,7 @@ pub fn filter_node(
.replace('%', "\\\\\\%")
.replace(':', "\\:");
filter = format!("drawtext=text='{escape}':{}{font}", config.text.style)
} else if let Some(socket) = config.text.bind_address.clone() {
} else if let Some(socket) = zmq_socket {
let chain = filter_chain.lock().unwrap();
let mut filter_cmd = format!("text=''{font}");

View File

@ -8,7 +8,7 @@ use std::{
use serde::{Deserialize, Serialize};
use shlex::split;
use crate::utils::{free_tcp_socket, time_to_sec};
use crate::utils::{free_tcp_socket, home_dir, time_to_sec};
use crate::vec_strings;
pub const DUMMY_LEN: f64 = 60.0;
@ -145,10 +145,13 @@ pub struct Text {
pub add_text: bool,
#[serde(skip_serializing, skip_deserializing)]
pub bind_address: Option<String>,
pub node_pos: Option<usize>,
#[serde(skip_serializing, skip_deserializing)]
pub node_pos: Option<usize>,
pub zmq_stream_socket: Option<String>,
#[serde(skip_serializing, skip_deserializing)]
pub zmq_server_socket: Option<String>,
pub fontfile: String,
pub text_from_filename: bool,
@ -196,8 +199,9 @@ impl PlayoutConfig {
let mut config: PlayoutConfig =
serde_yaml::from_reader(f).expect("Could not read config file.");
config.general.generate = None;
config.general.stat_file = env::temp_dir()
.join("ffplayout_status.json")
config.general.stat_file = home_dir()
.unwrap_or_else(env::temp_dir)
.join(".ffp_status")
.display()
.to_string();
let bitrate = format!(
@ -251,10 +255,13 @@ impl PlayoutConfig {
// 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.zmq_stream_socket = free_tcp_socket(String::new());
config.text.zmq_server_socket =
free_tcp_socket(config.text.zmq_stream_socket.clone().unwrap_or_default());
config.text.node_pos = Some(2);
} else {
config.text.bind_address = None;
config.text.zmq_stream_socket = None;
config.text.zmq_server_socket = None;
config.text.node_pos = None;
}
@ -272,7 +279,7 @@ impl Default for PlayoutConfig {
/// s302m has higher quality, but is experimental
/// and works not well together with the loudnorm filter.
fn pre_audio_codec(add_loudnorm: bool) -> Vec<String> {
let mut codec = vec_strings!["-c:a", "s302m", "-strict", "-2"];
let mut codec = vec_strings!["-c:a", "s302m", "-strict", "-2", "-sample_fmt", "s16"];
if add_loudnorm {
codec = vec_strings!["-c:a", "mp2", "-b:a", "384k"];

View File

@ -183,6 +183,8 @@ pub struct PlayoutStatus {
pub date: Arc<Mutex<String>>,
pub list_init: Arc<AtomicBool>,
pub time_shift: Arc<Mutex<f64>>,
pub drawtext_server_index: Arc<AtomicUsize>,
pub drawtext_stream_index: Arc<AtomicUsize>,
}
impl PlayoutStatus {
@ -193,6 +195,8 @@ impl PlayoutStatus {
date: Arc::new(Mutex::new(String::new())),
list_init: Arc::new(AtomicBool::new(true)),
time_shift: Arc::new(Mutex::new(0.0)),
drawtext_server_index: Arc::new(AtomicUsize::new(2)),
drawtext_stream_index: Arc::new(AtomicUsize::new(2)),
}
}
}

View File

@ -3,12 +3,15 @@ use std::{
fs::{self, metadata},
io::{BufRead, BufReader, Error},
net::TcpListener,
path::Path,
path::{Path, PathBuf},
process::{ChildStderr, Command, Stdio},
sync::{Arc, Mutex},
time::{self, UNIX_EPOCH},
};
#[cfg(not(windows))]
use std::env;
use chrono::{prelude::*, Duration};
use ffprobe::{ffprobe, Format, Stream};
use jsonrpc_http_server::hyper::HeaderMap;
@ -27,6 +30,9 @@ pub mod json_serializer;
mod json_validate;
mod logging;
#[cfg(windows)]
mod windows;
pub use config::{self as playout_config, PlayoutConfig, DUMMY_LEN, IMAGE_FORMAT};
pub use controller::{PlayerControl, PlayoutStatus, ProcessControl, ProcessUnit::*};
pub use generator::generate_playlist;
@ -712,18 +718,32 @@ pub fn validate_ffmpeg(config: &PlayoutConfig) -> Result<(), String> {
}
/// get a free tcp socket
pub fn free_tcp_socket() -> Option<String> {
pub fn free_tcp_socket(exclude_socket: String) -> Option<String> {
for _ in 0..100 {
let port = rand::thread_rng().gen_range(45321..54268);
let socket = format!("127.0.0.1:{port}");
if TcpListener::bind(("127.0.0.1", port)).is_ok() {
return Some(format!("127.0.0.1:{port}"));
if socket != exclude_socket && TcpListener::bind(("127.0.0.1", port)).is_ok() {
return Some(socket);
}
}
None
}
pub fn home_dir() -> Option<PathBuf> {
home_dir_inner()
}
#[cfg(windows)]
use windows::home_dir_inner;
#[cfg(any(unix, target_os = "redox"))]
fn home_dir_inner() -> Option<PathBuf> {
#[allow(deprecated)]
env::home_dir()
}
/// Get system time, in non test case.
#[cfg(not(test))]
pub fn time_now() -> DateTime<Local> {

43
lib/src/utils/windows.rs Normal file
View File

@ -0,0 +1,43 @@
use std::{env, ffi::OsString, os::windows::ffi::OsStringExt, path::PathBuf, ptr};
use winapi::shared::minwindef::MAX_PATH;
use winapi::shared::winerror::S_OK;
use winapi::um::shlobj::{SHGetFolderPathW, CSIDL_PROFILE};
pub fn home_dir_inner() -> Option<PathBuf> {
env::var_os("USERPROFILE")
.filter(|s| !s.is_empty())
.map(PathBuf::from)
.or_else(home_dir_crt)
}
#[cfg(not(target_vendor = "uwp"))]
fn home_dir_crt() -> Option<PathBuf> {
unsafe {
let mut path: Vec<u16> = Vec::with_capacity(MAX_PATH);
match SHGetFolderPathW(
ptr::null_mut(),
CSIDL_PROFILE,
ptr::null_mut(),
0,
path.as_mut_ptr(),
) {
S_OK => {
let len = wcslen(path.as_ptr());
path.set_len(len);
let s = OsString::from_wide(&path);
Some(PathBuf::from(s))
}
_ => None,
}
}
}
#[cfg(target_vendor = "uwp")]
fn home_dir_crt() -> Option<PathBuf> {
None
}
extern "C" {
fn wcslen(buf: *const u16) -> usize;
}