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
This commit is contained in:
parent
9b054c8ce3
commit
32454e41d6
92
Cargo.lock
generated
92
Cargo.lock
generated
@ -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",
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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: <TOKEN>'
|
||||
```
|
||||
|
||||
|
@ -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"
|
||||
] }
|
||||
|
@ -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()
|
||||
|
@ -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: <TOKEN>'
|
||||
/// ```
|
||||
#[post("/control/{id}/playout/")]
|
||||
|
@ -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]
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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<Mutex<Vec<String>>>,
|
||||
) -> 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<Mutex<Vec<String>>>,
|
||||
) -> 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,
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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));
|
||||
};
|
||||
|
@ -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"
|
||||
|
@ -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<String> {
|
||||
pub fn filter_cmd(config: &PlayoutConfig, filter_chain: &Arc<Mutex<Vec<String>>>) -> Vec<String> {
|
||||
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<String> {
|
||||
);
|
||||
|
||||
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());
|
||||
|
||||
|
@ -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<Mutex<Vec<String>>>,
|
||||
) {
|
||||
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<String> {
|
||||
pub fn filter_chains(
|
||||
config: &PlayoutConfig,
|
||||
node: &mut Media,
|
||||
filter_chain: &Arc<Mutex<Vec<String>>>,
|
||||
) -> Vec<String> {
|
||||
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<String> {
|
||||
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");
|
||||
|
@ -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<Mutex<Vec<String>>>,
|
||||
) -> 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(':', "\\:")
|
||||
)
|
||||
}
|
||||
|
@ -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<Mutex<f64>>,
|
||||
pub date: Arc<Mutex<String>>,
|
||||
pub chain: Arc<Mutex<Vec<String>>>,
|
||||
pub current_date: Arc<Mutex<String>>,
|
||||
pub date: Arc<Mutex<String>>,
|
||||
pub list_init: Arc<AtomicBool>,
|
||||
pub time_shift: Arc<Mutex<f64>>,
|
||||
}
|
||||
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ use crate::utils::{file_extension, get_sec, Media, PlayoutConfig};
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FolderSource {
|
||||
config: PlayoutConfig,
|
||||
filter_chain: Arc<Mutex<Vec<String>>>,
|
||||
pub nodes: Arc<Mutex<Vec<Media>>>,
|
||||
current_node: Media,
|
||||
index: Arc<AtomicUsize>,
|
||||
@ -27,6 +28,7 @@ pub struct FolderSource {
|
||||
impl FolderSource {
|
||||
pub fn new(
|
||||
config: &PlayoutConfig,
|
||||
filter_chain: Arc<Mutex<Vec<String>>>,
|
||||
current_list: Arc<Mutex<Vec<Media>>>,
|
||||
global_index: Arc<AtomicUsize>,
|
||||
) -> 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);
|
||||
|
@ -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 {
|
||||
|
@ -37,7 +37,7 @@ pub fn validate_playlist(
|
||||
|
||||
if probe.format.is_none() {
|
||||
error!(
|
||||
"No Metadata from file <b><magenta>{}</></b> at <yellow>{}</>",
|
||||
"No Metadata at <yellow>{}</>, from file <b><magenta>{}</></b>",
|
||||
sec_to_time(begin),
|
||||
item.source
|
||||
);
|
||||
|
@ -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));
|
||||
|
@ -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<Mutex<Vec<String>>>) {
|
||||
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<ChildStderr>, suffix: &str) -> Result<(),
|
||||
"<bright black>[{suffix}]</> {}",
|
||||
format_log_line(line, "warning")
|
||||
)
|
||||
} else if line.contains("[error]") {
|
||||
error!(
|
||||
"<bright black>[{suffix}]</> {}",
|
||||
format_log_line(line, "error")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user