commit
9e90b2ce53
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,10 @@ text:
|
||||
|
||||
out:
|
||||
help_text: The final playout compression. Set the settings to your needs. 'mode'
|
||||
has the options 'desktop', 'hls', 'null', 'stream'.
|
||||
mode: stream
|
||||
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
|
||||
-crf 23
|
||||
@ -131,5 +133,10 @@ out:
|
||||
-c:a aac
|
||||
-ar 44100
|
||||
-b:a 128k
|
||||
-flags +global_header
|
||||
-f flv rtmp://localhost/live/stream
|
||||
-flags +cgop
|
||||
-f hls
|
||||
-hls_time 6
|
||||
-hls_list_size 600
|
||||
-hls_flags append_list+delete_segments+omit_endlist
|
||||
-hls_segment_filename /usr/share/ffplayout/public/live/stream-%d.ts
|
||||
/usr/share/ffplayout/public/live/stream.m3u8
|
||||
|
4
debian/postinst
vendored
4
debian/postinst
vendored
@ -11,7 +11,9 @@ if [ ! -d "/usr/share/ffplayout/db" ]; then
|
||||
mkdir -p "/var/lib/ffplayout/playlists"
|
||||
mkdir "/var/lib/ffplayout/tv-media"
|
||||
|
||||
/usr/bin/ffpapi -i
|
||||
IP=$(hostname -I | cut -d ' ' -f1)
|
||||
|
||||
/usr/bin/ffpapi -i -d $IP
|
||||
|
||||
chown -R ${sysUser}. "/usr/share/ffplayout"
|
||||
chown -R ${sysUser}. "/var/lib/ffplayout"
|
||||
|
@ -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>'
|
||||
```
|
||||
|
||||
|
@ -8,12 +8,10 @@ ffplayout provides ***.deb** amd ***.rpm** packages, which makes it more easy to
|
||||
4. activate systemd services:
|
||||
- `systemctl enable ffplayout`
|
||||
- `systemctl enable --now ffpapi`
|
||||
5. initialize sqlite database for ffpapi:
|
||||
- `ffpapi -i`
|
||||
6. add admin user to ffpapi:
|
||||
5. add admin user to ffpapi:
|
||||
- `ffpapi -a`
|
||||
7. use a revers proxy for SSL, Port is **8787**.
|
||||
8. login with your browser, address without proxy would be: **http://[IP ADDRESS]:8787**
|
||||
6. use a revers proxy for SSL, Port is **8787**.
|
||||
7. login with your browser, address without proxy would be: **http://[IP ADDRESS]:8787**
|
||||
|
||||
Default location for playlists and media files are: **/var/lib/ffplayout/**. If you need to change them, the media storage folder needs a symlink to **/usr/share/ffplayout/public/**.
|
||||
|
||||
|
@ -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));
|
||||
};
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit c02141af54762961cf559248a3c9d42e9d317e0b
|
||||
Subproject commit d0a2fa6921172d667ce718e9362d4076fc16c23c
|
@ -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,5 +1,15 @@
|
||||
#!/usr/bin/bash
|
||||
|
||||
if [[ ! -d public ]]; then
|
||||
cd ffplayout-frontend
|
||||
|
||||
npm install
|
||||
npm run build
|
||||
yes | rm -rf ../public
|
||||
mv dist ../public
|
||||
|
||||
cd ..
|
||||
fi
|
||||
|
||||
targets=("x86_64-unknown-linux-musl" "aarch64-unknown-linux-gnu" "x86_64-pc-windows-gnu" "x86_64-apple-darwin" "aarch64-apple-darwin")
|
||||
|
||||
@ -33,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 public 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"
|
||||
@ -54,15 +65,6 @@ for target in "${targets[@]}"; do
|
||||
echo ""
|
||||
done
|
||||
|
||||
cd ffplayout-frontend
|
||||
|
||||
npm install
|
||||
npm run build
|
||||
yes | rm -rf ../public
|
||||
mv dist ../public
|
||||
|
||||
cd ..
|
||||
|
||||
cargo deb --target=x86_64-unknown-linux-musl -p ffplayout --manifest-path=ffplayout-engine/Cargo.toml -o ffplayout_${version}_amd64.deb
|
||||
cargo deb --target=aarch64-unknown-linux-gnu --variant=arm64 -p ffplayout --manifest-path=ffplayout-engine/Cargo.toml -o ffplayout_${version}_arm64.deb
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user