commit
9e90b2ce53
92
Cargo.lock
generated
92
Cargo.lock
generated
@ -463,9 +463,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atoi"
|
name = "atoi"
|
||||||
version = "0.4.0"
|
version = "1.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5"
|
checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
@ -629,33 +629,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.19"
|
version = "0.4.20-rc.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
|
checksum = "856628f00a013eb30ae7b136b20fba37f7a39f8026d6f735dfac07c6fce1b8cf"
|
||||||
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"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
@ -777,18 +753,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc"
|
name = "crc"
|
||||||
version = "2.1.0"
|
version = "3.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49fc9a695bca7f35f5f4c15cddc84415f66a74ea78eef08e90c5024f2b540e23"
|
checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crc-catalog",
|
"crc-catalog",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc-catalog"
|
name = "crc-catalog"
|
||||||
version = "1.1.1"
|
version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403"
|
checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
@ -1038,7 +1014,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ffplayout"
|
name = "ffplayout"
|
||||||
version = "0.12.0"
|
version = "0.13.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"crossbeam-channel 0.5.6",
|
"crossbeam-channel 0.5.6",
|
||||||
@ -1056,7 +1032,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ffplayout-api"
|
name = "ffplayout-api"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-multipart",
|
"actix-multipart",
|
||||||
@ -1064,7 +1040,7 @@ dependencies = [
|
|||||||
"actix-web-grants",
|
"actix-web-grants",
|
||||||
"actix-web-httpauth",
|
"actix-web-httpauth",
|
||||||
"argon2",
|
"argon2",
|
||||||
"chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"faccess",
|
"faccess",
|
||||||
@ -1086,9 +1062,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ffplayout-lib"
|
name = "ffplayout-lib"
|
||||||
version = "0.12.0"
|
version = "0.13.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono 0.4.20-beta.1",
|
"chrono",
|
||||||
"crossbeam-channel 0.5.6",
|
"crossbeam-channel 0.5.6",
|
||||||
"ffprobe",
|
"ffprobe",
|
||||||
"file-rotate",
|
"file-rotate",
|
||||||
@ -1121,10 +1097,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "file-rotate"
|
name = "file-rotate"
|
||||||
version = "0.6.0"
|
version = "0.7.0-rc.0"
|
||||||
source = "git+https://github.com/Ploppz/file-rotate.git?branch=timestamp-parse-fix#cb1874a15a7a18de820a57df48d3513e5a4076f4"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8453b56141b15cd55cac61ce49326799d31fe4a602ad41f9f5b9b88a42ecbdcf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono 0.4.19 (git+https://github.com/sbrocket/chrono?branch=parse-error-kind-public)",
|
"chrono",
|
||||||
"flate2",
|
"flate2",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1427,26 +1404,20 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.11.2"
|
version = "0.12.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.7.6",
|
"ahash 0.7.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "hashbrown"
|
|
||||||
version = "0.12.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashlink"
|
name = "hashlink"
|
||||||
version = "0.7.0"
|
version = "0.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7249a3129cbc1ffccd74857f81464a323a152173cdb134e0fd81bc803b29facf"
|
checksum = "d452c155cb93fecdfb02a73dd57b5d8e442c2063bd7aac72f1bc5e4263a43086"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hashbrown 0.11.2",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1579,7 +1550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"hashbrown 0.12.3",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2761,9 +2732,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx"
|
name = "sqlx"
|
||||||
version = "0.5.13"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "551873805652ba0d912fec5bbb0f8b4cdd96baf8e2ebf5970e5671092966019b"
|
checksum = "1f82cbe94f41641d6c410ded25bbf5097c240cefdf8e3b06d04198d0a96af6a4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"sqlx-macros",
|
"sqlx-macros",
|
||||||
@ -2771,16 +2742,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx-core"
|
name = "sqlx-core"
|
||||||
version = "0.5.13"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e48c61941ccf5ddcada342cd59e3e5173b007c509e1e8e990dafc830294d9dc5"
|
checksum = "6b69bf218860335ddda60d6ce85ee39f6cf6e5630e300e19757d1de15886a093"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.7.6",
|
"ahash 0.7.6",
|
||||||
"atoi",
|
"atoi",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono 0.4.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"crc",
|
"crc",
|
||||||
"crossbeam-queue 0.3.6",
|
"crossbeam-queue 0.3.6",
|
||||||
"either",
|
"either",
|
||||||
@ -2814,9 +2784,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx-macros"
|
name = "sqlx-macros"
|
||||||
version = "0.5.13"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bc0fba2b0cae21fc00fe6046f8baa4c7fcb49e379f0f592b04696607f69ed2e1"
|
checksum = "f40c63177cf23d356b159b60acd27c54af7423f1736988502e36bae9a712118f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dotenv",
|
"dotenv",
|
||||||
"either",
|
"either",
|
||||||
@ -2833,9 +2803,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlx-rt"
|
name = "sqlx-rt"
|
||||||
version = "0.5.13"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae"
|
checksum = "874e93a365a598dc3dadb197565952cb143ae4aa716f7bcc933a8d836f6bf89f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-rt",
|
"actix-rt",
|
||||||
"native-tls",
|
"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)
|
[![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.
|
Check the [releases](https://github.com/ffplayout/ffplayout/releases/latest) for pre compiled version.
|
||||||
|
|
||||||
|
@ -116,8 +116,10 @@ text:
|
|||||||
|
|
||||||
out:
|
out:
|
||||||
help_text: The final playout compression. Set the settings to your needs. 'mode'
|
help_text: The final playout compression. Set the settings to your needs. 'mode'
|
||||||
has the options 'desktop', 'hls', 'null', 'stream'.
|
has the options 'desktop', 'hls', 'null', 'stream'. Use 'stream' and adjust
|
||||||
mode: stream
|
'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: >-
|
output_param: >-
|
||||||
-c:v libx264
|
-c:v libx264
|
||||||
-crf 23
|
-crf 23
|
||||||
@ -131,5 +133,10 @@ out:
|
|||||||
-c:a aac
|
-c:a aac
|
||||||
-ar 44100
|
-ar 44100
|
||||||
-b:a 128k
|
-b:a 128k
|
||||||
-flags +global_header
|
-flags +cgop
|
||||||
-f flv rtmp://localhost/live/stream
|
-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 -p "/var/lib/ffplayout/playlists"
|
||||||
mkdir "/var/lib/ffplayout/tv-media"
|
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}. "/usr/share/ffplayout"
|
||||||
chown -R ${sysUser}. "/var/lib/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
|
- reset
|
||||||
|
|
||||||
```BASH
|
```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>'
|
-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:
|
4. activate systemd services:
|
||||||
- `systemctl enable ffplayout`
|
- `systemctl enable ffplayout`
|
||||||
- `systemctl enable --now ffpapi`
|
- `systemctl enable --now ffpapi`
|
||||||
5. initialize sqlite database for ffpapi:
|
5. add admin user to ffpapi:
|
||||||
- `ffpapi -i`
|
|
||||||
6. add admin user to ffpapi:
|
|
||||||
- `ffpapi -a`
|
- `ffpapi -a`
|
||||||
7. use a revers proxy for SSL, Port is **8787**.
|
6. use a revers proxy for SSL, Port is **8787**.
|
||||||
8. login with your browser, address without proxy would be: **http://[IP ADDRESS]: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/**.
|
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"
|
license = "GPL-3.0"
|
||||||
authors = ["Jonathan Baecker jonbae77@gmail.com"]
|
authors = ["Jonathan Baecker jonbae77@gmail.com"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
@ -15,7 +15,7 @@ actix-web = "4"
|
|||||||
actix-web-grants = "3"
|
actix-web-grants = "3"
|
||||||
actix-web-httpauth = "0.6"
|
actix-web-httpauth = "0.6"
|
||||||
argon2 = "0.4"
|
argon2 = "0.4"
|
||||||
chrono = "0.4"
|
chrono = "0.4.20-rc.1"
|
||||||
clap = { version = "3.2", features = ["derive"] }
|
clap = { version = "3.2", features = ["derive"] }
|
||||||
derive_more = "0.99"
|
derive_more = "0.99"
|
||||||
faccess = "0.2"
|
faccess = "0.2"
|
||||||
@ -31,8 +31,7 @@ serde = { version = "1.0", features = ["derive"] }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8"
|
||||||
simplelog = { version = "^0.12", features = ["paris"] }
|
simplelog = { version = "^0.12", features = ["paris"] }
|
||||||
sqlx = { version = "0.5", features = [
|
sqlx = { version = "0.6", features = [
|
||||||
"chrono",
|
|
||||||
"runtime-actix-native-tls",
|
"runtime-actix-native-tls",
|
||||||
"sqlite"
|
"sqlite"
|
||||||
] }
|
] }
|
||||||
|
@ -80,7 +80,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
info!("running ffplayout API, listen on {conn}");
|
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 || {
|
HttpServer::new(move || {
|
||||||
let auth = HttpAuthentication::bearer(validator);
|
let auth = HttpAuthentication::bearer(validator);
|
||||||
App::new()
|
App::new()
|
||||||
|
@ -482,7 +482,7 @@ pub async fn send_text_message(
|
|||||||
/// - reset
|
/// - reset
|
||||||
///
|
///
|
||||||
/// ```BASH
|
/// ```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>'
|
/// -d '{ "command": "reset" }' -H 'Authorization: <TOKEN>'
|
||||||
/// ```
|
/// ```
|
||||||
#[post("/control/{id}/playout/")]
|
#[post("/control/{id}/playout/")]
|
||||||
|
@ -4,7 +4,7 @@ description = "24/7 playout based on rust and ffmpeg"
|
|||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
authors = ["Jonathan Baecker jonbae77@gmail.com"]
|
authors = ["Jonathan Baecker jonbae77@gmail.com"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
version = "0.12.0"
|
version = "0.13.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -2,6 +2,7 @@ use std::{
|
|||||||
io::{BufRead, BufReader, Error, Read},
|
io::{BufRead, BufReader, Error, Read},
|
||||||
process::{ChildStderr, Command, Stdio},
|
process::{ChildStderr, Command, Stdio},
|
||||||
sync::atomic::Ordering,
|
sync::atomic::Ordering,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
thread,
|
thread,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -78,7 +79,7 @@ pub fn ingest_server(
|
|||||||
let stream_input = config.ingest.input_cmd.clone().unwrap();
|
let stream_input = config.ingest.input_cmd.clone().unwrap();
|
||||||
|
|
||||||
server_cmd.append(&mut stream_input.clone());
|
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());
|
server_cmd.append(&mut config.processing.settings.unwrap());
|
||||||
|
|
||||||
let mut is_running;
|
let mut is_running;
|
||||||
|
@ -38,7 +38,7 @@ pub fn source_generator(
|
|||||||
);
|
);
|
||||||
|
|
||||||
let config_clone = config.clone();
|
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();
|
let node_clone = folder_source.nodes.clone();
|
||||||
|
|
||||||
// Spawn a thread to monitor folder for file changes.
|
// 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();
|
let mut node_clone = self.nodes.lock().unwrap()[index].clone();
|
||||||
|
|
||||||
node_clone.seek = time_sec - node_clone.begin.unwrap();
|
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.duration = duration;
|
||||||
media.out = 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();
|
let mut nodes = self.nodes.lock().unwrap();
|
||||||
nodes.push(self.current_node.clone());
|
nodes.push(self.current_node.clone());
|
||||||
self.index.store(nodes.len(), Ordering::SeqCst);
|
self.index.store(nodes.len(), Ordering::SeqCst);
|
||||||
@ -364,12 +365,17 @@ impl Iterator for CurrentProgram {
|
|||||||
}
|
}
|
||||||
self.current_node.duration = duration;
|
self.current_node.duration = duration;
|
||||||
self.current_node.out = 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.nodes.lock().unwrap().push(self.current_node.clone());
|
||||||
self.last_next_ad();
|
self.last_next_ad();
|
||||||
|
|
||||||
self.current_node.last_ad = last_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);
|
self.index.fetch_add(1, Ordering::SeqCst);
|
||||||
|
|
||||||
@ -377,7 +383,11 @@ impl Iterator for CurrentProgram {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.index.store(0, Ordering::SeqCst);
|
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.last_next_ad();
|
||||||
self.current_node.last_ad = last_ad;
|
self.current_node.last_ad = last_ad;
|
||||||
|
|
||||||
@ -432,20 +442,24 @@ fn timed_source(
|
|||||||
|| !config.playlist.length.contains(':')
|
|| !config.playlist.length.contains(':')
|
||||||
{
|
{
|
||||||
// when we are in the 24 hour range, get the clip
|
// 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);
|
new_node.process = Some(true);
|
||||||
} else if total_delta <= 0.0 {
|
} else if total_delta <= 0.0 {
|
||||||
info!("Begin is over play time, skip: {}", node.source);
|
info!("Begin is over play time, skip: {}", node.source);
|
||||||
} else if total_delta < node.duration - node.seek || last {
|
} else if total_delta < node.duration - node.seek || last {
|
||||||
new_node = handle_list_end(node, total_delta);
|
new_node = handle_list_end(node, total_delta);
|
||||||
new_node.add_filter(config);
|
new_node.add_filter(config, &playout_stat.chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
new_node
|
new_node
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate the source CMD, or when clip not exist, get a dummy.
|
/// 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) {
|
if valid_source(&node.source) {
|
||||||
node.add_probe();
|
node.add_probe();
|
||||||
node.cmd = Some(seek_and_length(
|
node.cmd = Some(seek_and_length(
|
||||||
@ -454,7 +468,7 @@ fn gen_source(config: &PlayoutConfig, mut node: Media) -> Media {
|
|||||||
node.out,
|
node.out,
|
||||||
node.duration,
|
node.duration,
|
||||||
));
|
));
|
||||||
node.add_filter(config);
|
node.add_filter(config, filter_chain);
|
||||||
} else {
|
} else {
|
||||||
if node.source.is_empty() {
|
if node.source.is_empty() {
|
||||||
warn!(
|
warn!(
|
||||||
@ -467,7 +481,7 @@ fn gen_source(config: &PlayoutConfig, mut node: Media) -> Media {
|
|||||||
let (source, cmd) = gen_dummy(config, node.out - node.seek);
|
let (source, cmd) = gen_dummy(config, node.out - node.seek);
|
||||||
node.source = source;
|
node.source = source;
|
||||||
node.cmd = Some(cmd);
|
node.cmd = Some(cmd);
|
||||||
node.add_filter(config);
|
node.add_filter(config, filter_chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
node
|
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,
|
/// Handle init clip, but this clip can be the last one in playlist,
|
||||||
/// this we have to figure out and calculate the right length.
|
/// 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");
|
debug!("Playlist init");
|
||||||
let (_, total_delta) = get_delta(config, &node.begin.unwrap());
|
let (_, total_delta) = get_delta(config, &node.begin.unwrap());
|
||||||
let mut out = node.out;
|
let mut out = node.out;
|
||||||
@ -485,7 +503,7 @@ fn handle_list_init(config: &PlayoutConfig, mut node: Media) -> Media {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.out = out;
|
node.out = out;
|
||||||
gen_source(config, node)
|
gen_source(config, node, filter_chain)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// when we come to last clip in playlist,
|
/// 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 simplelog::*;
|
||||||
|
|
||||||
use ffplayout_lib::filter::v_drawtext;
|
use ffplayout_lib::filter::v_drawtext;
|
||||||
use ffplayout_lib::utils::{Media, PlayoutConfig};
|
use ffplayout_lib::utils::PlayoutConfig;
|
||||||
use ffplayout_lib::vec_strings;
|
use ffplayout_lib::vec_strings;
|
||||||
|
|
||||||
/// Desktop Output
|
/// Desktop Output
|
||||||
@ -23,7 +26,7 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child {
|
|||||||
|
|
||||||
let mut filter: String = "null,".to_string();
|
let mut filter: String = "null,".to_string();
|
||||||
filter.push_str(
|
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];
|
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 server_prefix = vec_strings!["-hide_banner", "-nostats", "-v", "level+info"];
|
||||||
let mut stream_input = config.ingest.input_cmd.clone().unwrap();
|
let mut stream_input = config.ingest.input_cmd.clone().unwrap();
|
||||||
server_prefix.append(&mut stream_input);
|
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(
|
let server_cmd = prepare_output_cmd(
|
||||||
server_prefix,
|
server_prefix,
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use std::process::{self, Command, Stdio};
|
use std::{
|
||||||
|
process::{self, Command, Stdio},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
use simplelog::*;
|
use simplelog::*;
|
||||||
|
|
||||||
use ffplayout_lib::filter::v_drawtext;
|
use ffplayout_lib::{filter::v_drawtext, utils::PlayoutConfig, vec_strings};
|
||||||
use ffplayout_lib::utils::{Media, PlayoutConfig};
|
|
||||||
use ffplayout_lib::vec_strings;
|
|
||||||
|
|
||||||
/// Desktop Output
|
/// Desktop Output
|
||||||
///
|
///
|
||||||
@ -34,7 +35,7 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child {
|
|||||||
|
|
||||||
let mut filter: String = "null,".to_string();
|
let mut filter: String = "null,".to_string();
|
||||||
filter.push_str(
|
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];
|
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 simplelog::*;
|
||||||
|
|
||||||
use ffplayout_lib::filter::v_drawtext;
|
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;
|
use ffplayout_lib::vec_strings;
|
||||||
|
|
||||||
/// Streaming Output
|
/// Streaming Output
|
||||||
@ -34,7 +37,7 @@ pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child {
|
|||||||
let mut filter = "[0:v]null,".to_string();
|
let mut filter = "[0:v]null,".to_string();
|
||||||
|
|
||||||
filter.push_str(
|
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];
|
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 mut filter = get_filter_from_json(map["message"].to_string());
|
||||||
let socket = config.text.bind_address.clone();
|
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() {
|
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}");
|
filter = format!("Parsed_drawtext_2 reinit {filter}");
|
||||||
|
|
||||||
if let Ok(reply) = executor::block_on(zmq_send(&filter, &socket.unwrap())) {
|
if let Ok(reply) = executor::block_on(zmq_send(&filter, &socket.unwrap())) {
|
||||||
return Ok(Value::String(reply));
|
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"
|
license = "GPL-3.0"
|
||||||
authors = ["Jonathan Baecker jonbae77@gmail.com"]
|
authors = ["Jonathan Baecker jonbae77@gmail.com"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
version = "0.12.0"
|
version = "0.13.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
chrono = { git = "https://github.com/chronotope/chrono.git" }
|
chrono = "0.4.20-rc.1"
|
||||||
crossbeam-channel = "0.5"
|
crossbeam-channel = "0.5"
|
||||||
ffprobe = "0.3"
|
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"
|
jsonrpc-http-server = "18.0"
|
||||||
lettre = "0.10"
|
lettre = "0.10"
|
||||||
log = "0.4"
|
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;
|
use crate::utils::PlayoutConfig;
|
||||||
|
|
||||||
/// Audio Filter
|
/// Audio Filter
|
||||||
@ -22,7 +24,7 @@ fn audio_filter(config: &PlayoutConfig) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create filter nodes for ingest live stream.
|
/// 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!(
|
let mut filter = format!(
|
||||||
"[0:v]fps={},scale={}:{},setdar=dar={},fade=in:st=0:d=0.5",
|
"[0:v]fps={},scale={}:{},setdar=dar={},fade=in:st=0:d=0.5",
|
||||||
config.processing.fps,
|
config.processing.fps,
|
||||||
@ -32,12 +34,18 @@ pub fn filter_cmd(config: &PlayoutConfig) -> Vec<String> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
let overlay = v_overlay::filter_node(config, true);
|
let overlay = v_overlay::filter_node(config, true);
|
||||||
|
let drawtext = v_drawtext::filter_node(config, None, filter_chain);
|
||||||
|
|
||||||
if !overlay.is_empty() {
|
if !overlay.is_empty() {
|
||||||
filter.push(',');
|
filter.push(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !drawtext.is_empty() {
|
||||||
|
filter.push(',');
|
||||||
|
}
|
||||||
|
|
||||||
filter.push_str(&overlay);
|
filter.push_str(&overlay);
|
||||||
|
filter.push_str(&drawtext);
|
||||||
filter.push_str("[vout1]");
|
filter.push_str("[vout1]");
|
||||||
filter.push_str(audio_filter(config).as_str());
|
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::*;
|
use simplelog::*;
|
||||||
|
|
||||||
@ -100,7 +103,9 @@ fn scale(v_stream: &ffprobe::Stream, aspect: f64, chain: &mut Filters, config: &
|
|||||||
config.processing.width, config.processing.height
|
config.processing.width, config.processing.height
|
||||||
),
|
),
|
||||||
"video",
|
"video",
|
||||||
)
|
);
|
||||||
|
} else {
|
||||||
|
chain.add_filter("null", "video");
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_close(aspect, config.processing.aspect, 0.03) {
|
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
|
/// 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
|
if config.text.add_text
|
||||||
&& (config.text.text_from_filename || config.out.mode.to_lowercase() == "hls")
|
&& (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");
|
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();
|
let mut filters = Filters::new();
|
||||||
|
|
||||||
if let Some(probe) = node.probe.as_ref() {
|
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);
|
extend_audio(node, &mut filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
add_text(node, &mut filters, config);
|
add_text(node, &mut filters, config, filter_chain);
|
||||||
fade(node, &mut filters, "video");
|
fade(node, &mut filters, "video");
|
||||||
overlay(node, &mut filters, config);
|
overlay(node, &mut filters, config);
|
||||||
realtime_filter(node, &mut filters, config, "video");
|
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 regex::Regex;
|
||||||
|
|
||||||
use crate::utils::{Media, PlayoutConfig};
|
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 filter = String::new();
|
||||||
let mut font = 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)
|
font = format!(":fontfile='{}'", config.text.fontfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.text.text_from_filename {
|
// TODO: in Rust 1.64 use let_chains instead
|
||||||
let source = node.source.clone();
|
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 regex: Regex = Regex::new(&config.text.regex).unwrap();
|
||||||
|
|
||||||
let text: String = match regex.captures(&source) {
|
let text: String = match regex.captures(&source) {
|
||||||
@ -28,8 +39,17 @@ pub fn filter_node(config: &PlayoutConfig, node: &Media) -> String {
|
|||||||
.replace(':', "\\:");
|
.replace(':', "\\:");
|
||||||
filter = format!("drawtext=text='{escape}':{}{font}", config.text.style)
|
filter = format!("drawtext=text='{escape}':{}{font}", config.text.style)
|
||||||
} else if let Some(socket) = config.text.bind_address.clone() {
|
} 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!(
|
filter = format!(
|
||||||
"zmq=b=tcp\\\\://'{}',drawtext=text=''{font}",
|
"zmq=b=tcp\\\\://'{}',drawtext={filter_cmd}",
|
||||||
socket.replace(':', "\\:")
|
socket.replace(':', "\\:")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -178,19 +178,21 @@ impl Default for PlayerControl {
|
|||||||
/// Global playout control, for move forward/backward clip, or resetting playlist/state.
|
/// Global playout control, for move forward/backward clip, or resetting playlist/state.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PlayoutStatus {
|
pub struct PlayoutStatus {
|
||||||
pub time_shift: Arc<Mutex<f64>>,
|
pub chain: Arc<Mutex<Vec<String>>>,
|
||||||
pub date: Arc<Mutex<String>>,
|
|
||||||
pub current_date: Arc<Mutex<String>>,
|
pub current_date: Arc<Mutex<String>>,
|
||||||
|
pub date: Arc<Mutex<String>>,
|
||||||
pub list_init: Arc<AtomicBool>,
|
pub list_init: Arc<AtomicBool>,
|
||||||
|
pub time_shift: Arc<Mutex<f64>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayoutStatus {
|
impl PlayoutStatus {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
time_shift: Arc::new(Mutex::new(0.0)),
|
chain: Arc::new(Mutex::new(vec![])),
|
||||||
date: Arc::new(Mutex::new(String::new())),
|
|
||||||
current_date: Arc::new(Mutex::new(String::new())),
|
current_date: Arc::new(Mutex::new(String::new())),
|
||||||
|
date: Arc::new(Mutex::new(String::new())),
|
||||||
list_init: Arc::new(AtomicBool::new(true)),
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct FolderSource {
|
pub struct FolderSource {
|
||||||
config: PlayoutConfig,
|
config: PlayoutConfig,
|
||||||
|
filter_chain: Arc<Mutex<Vec<String>>>,
|
||||||
pub nodes: Arc<Mutex<Vec<Media>>>,
|
pub nodes: Arc<Mutex<Vec<Media>>>,
|
||||||
current_node: Media,
|
current_node: Media,
|
||||||
index: Arc<AtomicUsize>,
|
index: Arc<AtomicUsize>,
|
||||||
@ -27,6 +28,7 @@ pub struct FolderSource {
|
|||||||
impl FolderSource {
|
impl FolderSource {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
config: &PlayoutConfig,
|
config: &PlayoutConfig,
|
||||||
|
filter_chain: Arc<Mutex<Vec<String>>>,
|
||||||
current_list: Arc<Mutex<Vec<Media>>>,
|
current_list: Arc<Mutex<Vec<Media>>>,
|
||||||
global_index: Arc<AtomicUsize>,
|
global_index: Arc<AtomicUsize>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
@ -77,6 +79,7 @@ impl FolderSource {
|
|||||||
|
|
||||||
Self {
|
Self {
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
|
filter_chain,
|
||||||
nodes: current_list,
|
nodes: current_list,
|
||||||
current_node: Media::new(0, String::new(), false),
|
current_node: Media::new(0, String::new(), false),
|
||||||
index: global_index,
|
index: global_index,
|
||||||
@ -114,7 +117,8 @@ impl Iterator for FolderSource {
|
|||||||
let i = self.index.load(Ordering::SeqCst);
|
let i = self.index.load(Ordering::SeqCst);
|
||||||
self.current_node = self.nodes.lock().unwrap()[i].clone();
|
self.current_node = self.nodes.lock().unwrap()[i].clone();
|
||||||
self.current_node.add_probe();
|
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.current_node.begin = Some(get_sec());
|
||||||
|
|
||||||
self.index.fetch_add(1, Ordering::SeqCst);
|
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 = self.nodes.lock().unwrap()[0].clone();
|
||||||
self.current_node.add_probe();
|
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.current_node.begin = Some(get_sec());
|
||||||
|
|
||||||
self.index.store(1, Ordering::SeqCst);
|
self.index.store(1, Ordering::SeqCst);
|
||||||
|
@ -93,7 +93,7 @@ pub fn generate_playlist(
|
|||||||
date_range = get_date_range(&date_range)
|
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();
|
let list_length = media_list.nodes.lock().unwrap().len();
|
||||||
|
|
||||||
for date in date_range {
|
for date in date_range {
|
||||||
|
@ -37,7 +37,7 @@ pub fn validate_playlist(
|
|||||||
|
|
||||||
if probe.format.is_none() {
|
if probe.format.is_none() {
|
||||||
error!(
|
error!(
|
||||||
"No Metadata from file <b><magenta>{}</></b> at <yellow>{}</>",
|
"No Metadata at <yellow>{}</>, from file <b><magenta>{}</></b>",
|
||||||
sec_to_time(begin),
|
sec_to_time(begin),
|
||||||
item.source
|
item.source
|
||||||
);
|
);
|
||||||
|
@ -229,6 +229,8 @@ pub fn init_logging(
|
|||||||
),
|
),
|
||||||
ContentLimit::Time(TimeFrequency::Daily),
|
ContentLimit::Time(TimeFrequency::Daily),
|
||||||
Compression::None,
|
Compression::None,
|
||||||
|
#[cfg(unix)]
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
app_logger.push(WriteLogger::new(LevelFilter::Debug, file_config, log_file));
|
app_logger.push(WriteLogger::new(LevelFilter::Debug, file_config, log_file));
|
||||||
|
@ -5,6 +5,7 @@ use std::{
|
|||||||
net::TcpListener,
|
net::TcpListener,
|
||||||
path::Path,
|
path::Path,
|
||||||
process::{ChildStderr, Command, Stdio},
|
process::{ChildStderr, Command, Stdio},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
time::{self, UNIX_EPOCH},
|
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();
|
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}]</> {}",
|
"<bright black>[{suffix}]</> {}",
|
||||||
format_log_line(line, "warning")
|
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
|
#!/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")
|
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"
|
rm -f "ffplayout-v${version}_${target}.tar.gz"
|
||||||
fi
|
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 .
|
cp ./target/${target}/release/ffplayout .
|
||||||
tar -czvf "ffplayout-v${version}_${target}.tar.gz" --exclude='*.db' assets docs public LICENSE README.md ffplayout
|
tar -czvf "ffplayout-v${version}_${target}.tar.gz" --exclude='*.db' assets docs public LICENSE README.md ffplayout ffpapi
|
||||||
rm -f ffplayout
|
rm -f ffplayout ffpapi
|
||||||
else
|
else
|
||||||
if [[ -f "ffplayout-v${version}_${target}.tar.gz" ]]; then
|
if [[ -f "ffplayout-v${version}_${target}.tar.gz" ]]; then
|
||||||
rm -f "ffplayout-v${version}_${target}.tar.gz"
|
rm -f "ffplayout-v${version}_${target}.tar.gz"
|
||||||
@ -54,15 +65,6 @@ for target in "${targets[@]}"; do
|
|||||||
echo ""
|
echo ""
|
||||||
done
|
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=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
|
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