Merge pull request #454 from jb-alvarado/master

delete/update user, add silence detection for validation
This commit is contained in:
jb-alvarado 2023-11-24 08:40:39 +01:00 committed by GitHub
commit 05347169b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 399 additions and 166 deletions

231
Cargo.lock generated
View File

@ -458,21 +458,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35"
dependencies = [
"concurrent-queue",
"event-listener",
"event-listener 2.5.3",
"futures-core",
]
[[package]]
name = "async-executor"
version = "1.6.0"
name = "async-channel"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b0c4a4f319e45986f347ee47fef8bf5e81c9abc3f6f58dc2391439f30df65f0"
checksum = "d37875bd9915b7d67c2f117ea2c30a0989874d0b2cb694fe25403c85763c0c9e"
dependencies = [
"async-lock",
"concurrent-queue",
"event-listener 3.1.0",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-executor"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc5ea910c42e5ab19012bab31f53cb4d63d54c3a27730f9a833a88efcf4bb52d"
dependencies = [
"async-lock 3.1.1",
"async-task",
"concurrent-queue",
"fastrand 2.0.1",
"futures-lite",
"futures-lite 2.0.1",
"slab",
]
@ -482,12 +495,12 @@ version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776"
dependencies = [
"async-channel",
"async-channel 1.9.0",
"async-executor",
"async-io",
"async-lock",
"async-lock 2.8.0",
"blocking",
"futures-lite",
"futures-lite 1.13.0",
"once_cell",
]
@ -497,11 +510,11 @@ version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af"
dependencies = [
"async-lock",
"async-lock 2.8.0",
"autocfg",
"cfg-if",
"concurrent-queue",
"futures-lite",
"futures-lite 1.13.0",
"log",
"parking",
"polling",
@ -517,7 +530,18 @@ version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b"
dependencies = [
"event-listener",
"event-listener 2.5.3",
]
[[package]]
name = "async-lock"
version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "655b9c7fe787d3b25cc0f804a1a8401790f0c5bc395beb5a64dc77d8de079105"
dependencies = [
"event-listener 3.1.0",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
@ -527,15 +551,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d"
dependencies = [
"async-attributes",
"async-channel",
"async-channel 1.9.0",
"async-global-executor",
"async-io",
"async-lock",
"async-lock 2.8.0",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
"futures-lite",
"futures-lite 1.13.0",
"gloo-timers",
"kv-log-macro",
"log",
@ -660,16 +684,16 @@ dependencies = [
[[package]]
name = "blocking"
version = "1.4.1"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a"
checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118"
dependencies = [
"async-channel",
"async-lock",
"async-channel 2.1.0",
"async-lock 3.1.1",
"async-task",
"fastrand 2.0.1",
"futures-io",
"futures-lite",
"futures-lite 2.0.1",
"piper",
"tracing",
]
@ -769,7 +793,7 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
dependencies = [
"hashbrown 0.14.2",
"hashbrown",
"stacker",
]
@ -781,9 +805,9 @@ checksum = "cca491388666e04d7248af3f60f0c40cfb0991c72205595d7c396e3510207d1a"
[[package]]
name = "clap"
version = "4.4.7"
version = "4.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
dependencies = [
"clap_builder",
"clap_derive",
@ -791,9 +815,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.7"
version = "4.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
dependencies = [
"anstream",
"anstyle",
@ -1011,7 +1035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
dependencies = [
"cfg-if",
"hashbrown 0.14.2",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
@ -1110,9 +1134,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e"
checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8"
dependencies = [
"libc",
"windows-sys",
@ -1135,6 +1159,27 @@ version = "2.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "event-listener"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d93877bcde0eb80ca09131a08d23f0a5c18a620b01db137dba666d18cd9b30c2"
dependencies = [
"concurrent-queue",
"parking",
"pin-project-lite",
]
[[package]]
name = "event-listener-strategy"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96b852f1345da36d551b9473fa1e2b1eb5c5195585c6c018118bc92a8d91160"
dependencies = [
"event-listener 3.1.0",
"pin-project-lite",
]
[[package]]
name = "faccess"
version = "0.2.4"
@ -1163,14 +1208,14 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "ffplayout"
version = "0.20.0"
version = "0.20.1"
dependencies = [
"chrono",
"clap",
"crossbeam-channel",
"ffplayout-lib",
"futures",
"itertools",
"itertools 0.12.0",
"notify",
"notify-debouncer-full",
"rand",
@ -1185,7 +1230,7 @@ dependencies = [
[[package]]
name = "ffplayout-api"
version = "0.20.0"
version = "0.20.1"
dependencies = [
"actix-files",
"actix-multipart",
@ -1224,10 +1269,11 @@ dependencies = [
[[package]]
name = "ffplayout-lib"
version = "0.20.0"
version = "0.20.1"
dependencies = [
"chrono",
"crossbeam-channel",
"derive_more",
"ffprobe",
"file-rotate",
"lettre",
@ -1413,6 +1459,20 @@ dependencies = [
"waker-fn",
]
[[package]]
name = "futures-lite"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3831c2651acb5177cbd83943f3d9c8912c5ad03c76afcc0e9511ba568ec5ebb"
dependencies = [
"fastrand 2.0.1",
"futures-core",
"futures-io",
"memchr",
"parking",
"pin-project-lite",
]
[[package]]
name = "futures-macro"
version = "0.3.29"
@ -1501,9 +1561,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.3.21"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178"
dependencies = [
"bytes",
"fnv",
@ -1511,19 +1571,13 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
"indexmap 1.9.3",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.2"
@ -1540,7 +1594,7 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
dependencies = [
"hashbrown 0.14.2",
"hashbrown",
]
[[package]]
@ -1593,9 +1647,9 @@ dependencies = [
[[package]]
name = "http"
version = "0.2.9"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb"
dependencies = [
"bytes",
"fnv",
@ -1708,16 +1762,6 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
]
[[package]]
name = "indexmap"
version = "2.1.0"
@ -1725,7 +1769,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
dependencies = [
"equivalent",
"hashbrown 0.14.2",
"hashbrown",
]
[[package]]
@ -1783,6 +1827,15 @@ dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.9"
@ -2593,13 +2646,13 @@ dependencies = [
[[package]]
name = "rpassword"
version = "7.2.0"
version = "7.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322"
checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f"
dependencies = [
"libc",
"rtoolbox",
"winapi",
"windows-sys",
]
[[package]]
@ -2624,12 +2677,12 @@ dependencies = [
[[package]]
name = "rtoolbox"
version = "0.0.1"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "034e22c514f5c0cb8a10ff341b9b048b5ceb21591f31c8f44c43b960f9b3524a"
checksum = "c247d24e63230cdb56463ae328478bd5eac8b8faa8c69461a77e8e323afac90e"
dependencies = [
"libc",
"winapi",
"windows-sys",
]
[[package]]
@ -2663,9 +2716,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.21"
version = "0.38.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e"
dependencies = [
"bitflags 2.4.1",
"errno",
@ -2676,9 +2729,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.21.8"
version = "0.21.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c"
checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9"
dependencies = [
"log",
"ring",
@ -2688,9 +2741,9 @@ dependencies = [
[[package]]
name = "rustls-pemfile"
version = "1.0.3"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64",
]
@ -2810,7 +2863,7 @@ version = "0.9.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cc7a1570e38322cfe4154732e5110f887ea57e22b76f4bfd32b5bdd3368666c"
dependencies = [
"indexmap 2.1.0",
"indexmap",
"itoa",
"ryu",
"serde",
@ -2887,9 +2940,9 @@ dependencies = [
[[package]]
name = "signature"
version = "2.1.0"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500"
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
dependencies = [
"digest",
"rand_core",
@ -2930,9 +2983,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.11.1"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
[[package]]
name = "socket2"
@ -2985,7 +3038,7 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b7b278788e7be4d0d29c0f39497a0eef3fba6bbc8e70d8bf7fde46edeaa9e85"
dependencies = [
"itertools",
"itertools 0.11.0",
"nom",
"unicode_categories",
]
@ -3017,7 +3070,7 @@ dependencies = [
"crossbeam-queue",
"dotenvy",
"either",
"event-listener",
"event-listener 2.5.3",
"futures-channel",
"futures-core",
"futures-intrusive",
@ -3025,7 +3078,7 @@ dependencies = [
"futures-util",
"hashlink",
"hex",
"indexmap 2.1.0",
"indexmap",
"log",
"memchr",
"once_cell",
@ -3298,7 +3351,7 @@ dependencies = [
"cfg-if",
"fastrand 2.0.1",
"redox_syscall 0.4.1",
"rustix 0.38.21",
"rustix 0.38.25",
"windows-sys",
]
@ -3313,7 +3366,7 @@ dependencies = [
[[package]]
name = "tests"
version = "0.20.0"
version = "0.20.1"
dependencies = [
"chrono",
"crossbeam-channel",
@ -3416,9 +3469,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.33.0"
version = "1.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9"
dependencies = [
"backtrace",
"bytes",
@ -3435,9 +3488,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "2.1.0"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
@ -3602,9 +3655,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.5.0"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
dependencies = [
"getrandom",
]
@ -3864,18 +3917,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.7.25"
version = "0.7.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cd369a67c0edfef15010f980c3cbe45d7f651deac2cd67ce097cd801de16557"
checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.25"
version = "0.7.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2f140bda219a26ccc0cdb03dba58af72590c53b22642577d88a927bc5c87d6b"
checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f"
dependencies = [
"proc-macro2",
"quote",
@ -3884,9 +3937,9 @@ dependencies = [
[[package]]
name = "zeroize"
version = "1.6.0"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
[[package]]
name = "zeromq"

View File

@ -4,7 +4,7 @@ default-members = ["ffplayout-api", "ffplayout-engine", "tests"]
resolver = "2"
[workspace.package]
version = "0.20.0"
version = "0.20.1"
license = "GPL-3.0"
repository = "https://github.com/ffplayout/ffplayout"
authors = ["Jonathan Baecker <jonbae77@gmail.com>"]

View File

@ -35,6 +35,8 @@ logging:
false will set log timestamps to UTC. Path to /var/log/ only if you run this
program as daemon. 'level' can be DEBUG, INFO, WARNING, ERROR.
'ffmpeg_level' can be info, warning, error.
'detect_silence' logs an error message if the audio line is silent for 15
seconds during the validation process.
log_to_file: true
backup_count: 7
local_time: true
@ -43,6 +45,7 @@ logging:
level: DEBUG
ffmpeg_level: error
ingest_level: warning
detect_silence: false
processing:
help_text: Default processing for all clips, to have them unique. Mode can be playlist

View File

@ -38,6 +38,19 @@ curl -X GET 'http://127.0.0.1:8787/api/user' -H 'Content-Type: application/json'
-H 'Authorization: Bearer <TOKEN>'
```
**Get User by ID**
```BASH
curl -X GET 'http://127.0.0.1:8787/api/user/2' -H 'Content-Type: application/json' \
-H 'Authorization: Bearer <TOKEN>'
```
```BASH
curl -X GET 'http://127.0.0.1:8787/api/users' -H 'Content-Type: application/json' \
-H 'Authorization: Bearer <TOKEN>'
```
**Update current User**
```BASH
@ -53,6 +66,12 @@ curl -X POST 'http://127.0.0.1:8787/api/user/' -H 'Content-Type: application/jso
-H 'Authorization: Bearer <TOKEN>'
```
```BASH
curl -X GET 'http://127.0.0.1:8787/api/user/2' -H 'Content-Type: application/json' \
-H 'Authorization: Bearer <TOKEN>'
```
#### ffpapi Settings
**Get Settings from Channel**

View File

@ -232,6 +232,45 @@ async fn get_user(
}
}
/// **Get User by ID**
///
/// ```BASH
/// curl -X GET 'http://127.0.0.1:8787/api/user/2' -H 'Content-Type: application/json' \
/// -H 'Authorization: Bearer <TOKEN>'
/// ```
#[get("/user/{name}")]
#[has_any_role("Role::Admin", type = "Role")]
async fn get_user_by_name(
pool: web::Data<Pool<Sqlite>>,
name: web::Path<String>,
) -> Result<impl Responder, ServiceError> {
match handles::select_user(&pool.into_inner(), &name).await {
Ok(user) => Ok(web::Json(user)),
Err(e) => {
error!("{e}");
Err(ServiceError::InternalServerError)
}
}
}
// **Get all User**
///
/// ```BASH
/// curl -X GET 'http://127.0.0.1:8787/api/users' -H 'Content-Type: application/json' \
/// -H 'Authorization: Bearer <TOKEN>'
/// ```
#[get("/users")]
#[has_any_role("Role::Admin", type = "Role")]
async fn get_users(pool: web::Data<Pool<Sqlite>>) -> Result<impl Responder, ServiceError> {
match handles::select_users(&pool.into_inner()).await {
Ok(users) => Ok(web::Json(users)),
Err(e) => {
error!("{e}");
Err(ServiceError::InternalServerError)
}
}
}
/// **Update current User**
///
/// ```BASH
@ -245,11 +284,16 @@ async fn update_user(
id: web::Path<i32>,
user: web::ReqData<LoginUser>,
data: web::Json<User>,
role: AuthDetails<Role>,
) -> Result<impl Responder, ServiceError> {
if id.into_inner() == user.id {
if *id == user.id || role.has_role(&Role::Admin) {
let mut fields = String::new();
if let Some(mail) = data.mail.clone() {
if !fields.is_empty() {
fields.push_str(", ");
}
fields.push_str(format!("mail = '{mail}'").as_str());
}
@ -266,7 +310,7 @@ async fn update_user(
fields.push_str(format!("password = '{password_hash}', salt = '{salt}'").as_str());
}
if handles::update_user(&pool.into_inner(), user.id, fields)
if handles::update_user(&pool.into_inner(), *id, fields)
.await
.is_ok()
{
@ -301,6 +345,27 @@ async fn add_user(
}
}
// **Delete User**
///
/// ```BASH
/// curl -X GET 'http://127.0.0.1:8787/api/user/2' -H 'Content-Type: application/json' \
/// -H 'Authorization: Bearer <TOKEN>'
/// ```
#[delete("/user/{name}")]
#[has_any_role("Role::Admin", type = "Role")]
async fn remove_user(
pool: web::Data<Pool<Sqlite>>,
name: web::Path<String>,
) -> Result<impl Responder, ServiceError> {
match handles::delete_user(&pool.into_inner(), &name).await {
Ok(_) => return Ok("Delete user success"),
Err(e) => {
error!("{e}");
Err(ServiceError::InternalServerError)
}
}
}
/// #### ffpapi Settings
///
/// **Get Settings from Channel**

View File

@ -228,6 +228,18 @@ pub async fn select_user(conn: &Pool<Sqlite>, user: &str) -> Result<User, sqlx::
sqlx::query_as(query).bind(user).fetch_one(conn).await
}
pub async fn select_user_by_id(conn: &Pool<Sqlite>, id: i32) -> Result<User, sqlx::Error> {
let query = "SELECT id, mail, username, role_id FROM user WHERE id = $1";
sqlx::query_as(query).bind(id).fetch_one(conn).await
}
pub async fn select_users(conn: &Pool<Sqlite>) -> Result<Vec<User>, sqlx::Error> {
let query = "SELECT id, username FROM user";
sqlx::query_as(query).fetch_all(conn).await
}
pub async fn insert_user(
conn: &Pool<Sqlite>,
user: User,
@ -260,6 +272,15 @@ pub async fn update_user(
sqlx::query(&query).bind(id).execute(conn).await
}
pub async fn delete_user(
conn: &Pool<Sqlite>,
name: &str,
) -> Result<SqliteQueryResult, sqlx::Error> {
let query = "DELETE FROM user WHERE username = $1;";
sqlx::query(query).bind(name).execute(conn).await
}
pub async fn select_presets(conn: &Pool<Sqlite>, id: i32) -> Result<Vec<TextPreset>, sqlx::Error> {
let query = "SELECT * FROM presets WHERE channel_id = $1";

View File

@ -10,6 +10,7 @@ pub struct User {
#[serde(skip_deserializing)]
pub id: i32,
#[sqlx(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub mail: Option<String>,
pub username: String,
#[sqlx(default)]

View File

@ -109,6 +109,9 @@ async fn main() -> std::io::Result<()> {
.wrap(auth)
.service(add_user)
.service(get_user)
.service(get_user_by_name)
.service(get_users)
.service(remove_user)
.service(get_playout_config)
.service(update_playout_config)
.service(add_preset)

View File

@ -16,7 +16,7 @@ chrono = { version = "0.4", default-features = false, features = ["clock", "std"
clap = { version = "4.3", features = ["derive"] }
crossbeam-channel = "0.5"
futures = "0.3"
itertools = "0.11"
itertools = "0.12"
notify = "6.0"
notify-debouncer-full = { version = "*", default-features = false }
rand = "0.8"

View File

@ -19,9 +19,9 @@ use ffplayout::{
};
use ffplayout_lib::utils::{
folder::fill_filler_list, generate_playlist, get_date, import::import_file, init_logging,
is_remote, send_mail, test_tcp_port, validate_ffmpeg, validate_playlist, JsonPlaylist,
OutputMode::*, PlayerControl, PlayoutStatus, ProcessControl,
errors::ProcError, folder::fill_filler_list, generate_playlist, get_date, import::import_file,
init_logging, is_remote, send_mail, test_tcp_port, validate_ffmpeg, validate_playlist,
JsonPlaylist, OutputMode::*, PlayerControl, PlayoutStatus, ProcessControl,
};
#[cfg(debug_assertions)]
@ -44,7 +44,7 @@ struct StatusData {
/// we save the time difference, so we stay in sync.
///
/// When file not exists we create it, and when it exists we get its values.
fn status_file(stat_file: &str, playout_stat: &PlayoutStatus) {
fn status_file(stat_file: &str, playout_stat: &PlayoutStatus) -> Result<(), ProcError> {
debug!("Start ffplayout v{VERSION}, status file path: <b><magenta>{stat_file}</></b>");
if !PathBuf::from(stat_file).exists() {
@ -53,23 +53,19 @@ fn status_file(stat_file: &str, playout_stat: &PlayoutStatus) {
"date": String::new(),
});
let json: String = serde_json::to_string(&data).expect("Serialize status data failed");
let json: String = serde_json::to_string(&data)?;
if let Err(e) = fs::write(stat_file, json) {
error!("Unable to write to status file <b><magenta>{stat_file}</></b>: {e}");
};
} else {
let stat_file = File::options()
.read(true)
.write(false)
.open(stat_file)
.expect("Could not open status file");
let data: StatusData =
serde_json::from_reader(stat_file).expect("Could not read status file.");
let stat_file = File::options().read(true).write(false).open(stat_file)?;
let data: StatusData = serde_json::from_reader(stat_file)?;
*playout_stat.time_shift.lock().unwrap() = data.time_shift;
*playout_stat.date.lock().unwrap() = data.date;
}
Ok(())
}
/// Set fake time for debugging.
@ -88,14 +84,14 @@ fn fake_time(args: &Args) {
/// Main function.
/// Here we check the command line arguments and start the player.
/// We also start a JSON RPC server if enabled.
fn main() {
fn main() -> Result<(), ProcError> {
let args = get_args();
// use fake time function only in debugging mode
#[cfg(debug_assertions)]
fake_time(&args);
let mut config = get_config(args.clone());
let mut config = get_config(args.clone())?;
let play_control = PlayerControl::new();
let playout_stat = PlayoutStatus::new();
let proc_control = ProcessControl::new();
@ -116,7 +112,7 @@ fn main() {
}
let logging = init_logging(&config, Some(proc_ctl1), Some(messages.clone()));
CombinedLogger::init(logging).unwrap();
CombinedLogger::init(logging)?;
if let Err(e) = validate_ffmpeg(&mut config) {
error!("{e}");
@ -126,7 +122,7 @@ fn main() {
let config_clone1 = config.clone();
let config_clone2 = config.clone();
if ![2, 4, 6, 8].contains(&config.processing.audio_channels) {
if !matches!(config.processing.audio_channels, 2 | 4 | 6 | 8) {
error!(
"Encoding {} channel(s) is not allowed. Only 2, 4, 6 and 8 channels are supported!",
config.processing.audio_channels
@ -179,16 +175,9 @@ fn main() {
let f = File::options()
.read(true)
.write(false)
.open(&playlist_path)
.expect("Could not open json playlist file.");
.open(&playlist_path)?;
let playlist: JsonPlaylist = match serde_json::from_reader(f) {
Ok(p) => p,
Err(e) => {
error!("{e:?}");
exit(1)
}
};
let playlist: JsonPlaylist = serde_json::from_reader(f)?;
validate_playlist(playlist, Arc::new(AtomicBool::new(false)), config);
@ -205,7 +194,7 @@ fn main() {
thread::spawn(move || run_server(config_clone1, play_ctl1, play_stat, proc_ctl2));
}
status_file(&config.general.stat_file, &playout_stat);
status_file(&config.general.stat_file, &playout_stat)?;
debug!(
"Use config: <b><magenta>{}</></b>",
@ -233,4 +222,6 @@ fn main() {
}
drop(msg);
Ok(())
}

View File

@ -1,7 +1,6 @@
use std::{
fs::File,
path::{Path, PathBuf},
process::exit,
};
use regex::Regex;
@ -15,23 +14,22 @@ pub use arg_parse::Args;
use ffplayout_lib::{
filter::Filters,
utils::{
config::Template, get_sec, parse_log_level_filter, sec_to_time, time_to_sec, Media,
OutputMode::*, PlayoutConfig, PlayoutStatus, ProcessMode::*,
config::Template, errors::ProcError, get_sec, parse_log_level_filter, sec_to_time,
time_to_sec, Media, OutputMode::*, PlayoutConfig, PlayoutStatus, ProcessMode::*,
},
vec_strings,
};
/// Read command line arguments, and override the config with them.
pub fn get_config(args: Args) -> PlayoutConfig {
pub fn get_config(args: Args) -> Result<PlayoutConfig, ProcError> {
let cfg_path = match args.channel {
Some(c) => {
let path = PathBuf::from(format!("/etc/ffplayout/{c}.yml"));
if !path.is_file() {
println!(
return Err(ProcError::Custom(format!(
"Config file \"{c}\" under \"/etc/ffplayout/\" not found.\n\nCheck arguments!"
);
exit(1)
)));
}
Some(path)
@ -53,17 +51,9 @@ pub fn get_config(args: Args) -> PlayoutConfig {
let f = File::options()
.read(true)
.write(false)
.open(template_file)
.expect("JSON template file");
.open(template_file)?;
let mut template: Template = match serde_json::from_reader(f) {
Ok(p) => p,
Err(e) => {
error!("Template file not readable! {e}");
exit(1)
}
};
let mut template: Template = serde_json::from_reader(f)?;
template.sources.sort_by(|d1, d2| d1.start.cmp(&d2.start));
@ -135,7 +125,7 @@ pub fn get_config(args: Args) -> PlayoutConfig {
config.processing.volume = volume;
}
config
Ok(config)
}
/// Format ingest and HLS logging output

@ -1 +1 @@
Subproject commit 03200acf1b426dc8b64dc702ab907e4184efd667
Subproject commit e060cbe885a961f295fb4f6f12bc2c86b5f94192

View File

@ -11,6 +11,7 @@ edition.workspace = true
[dependencies]
chrono = { version = "0.4", default-features = false, features = ["clock", "serde", "std"] }
crossbeam-channel = "0.5"
derive_more = "0.99"
ffprobe = "0.3"
file-rotate = "0.7"
lettre = { version = "0.11", features = ["builder", "rustls-tls", "smtp-transport"], default-features = false }

View File

@ -222,6 +222,8 @@ pub struct Logging {
pub level: LevelFilter,
pub ffmpeg_level: String,
pub ingest_level: Option<String>,
#[serde(default)]
pub detect_silence: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone)]

47
lib/src/utils/errors.rs Normal file
View File

@ -0,0 +1,47 @@
use std::io;
use derive_more::Display;
#[derive(Debug, Display)]
pub enum ProcError {
#[display(fmt = "Failed to spawn ffmpeg/ffprobe. {}", _0)]
CommandSpawn(io::Error),
#[display(fmt = "Failed to read data from ffmpeg/ffprobe. {}", _0)]
IO(io::Error),
#[display(fmt = "{}", _0)]
Custom(String),
#[display(fmt = "Regex compile error {}", _0)]
Regex(String),
#[display(fmt = "Thread error {}", _0)]
Thread(String),
}
impl From<std::io::Error> for ProcError {
fn from(err: std::io::Error) -> Self {
Self::CommandSpawn(err)
}
}
impl From<regex::Error> for ProcError {
fn from(err: regex::Error) -> Self {
Self::Regex(err.to_string())
}
}
impl From<log::SetLoggerError> for ProcError {
fn from(err: log::SetLoggerError) -> Self {
Self::Custom(err.to_string())
}
}
impl From<serde_json::Error> for ProcError {
fn from(err: serde_json::Error) -> Self {
Self::Custom(err.to_string())
}
}
impl From<Box<dyn std::any::Any + std::marker::Send>> for ProcError {
fn from(err: Box<dyn std::any::Any + std::marker::Send>) -> Self {
Self::Thread(format!("{err:?}"))
}
}

View File

@ -1,45 +1,60 @@
use std::{
io::{BufRead, BufReader, Error, ErrorKind},
io::{BufRead, BufReader},
process::{Command, Stdio},
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
time::Instant,
};
use regex::Regex;
use simplelog::*;
use crate::filter::FilterType::Audio;
use crate::utils::{
loop_image, sec_to_time, seek_and_length, valid_source, vec_strings, JsonPlaylist, Media,
OutputMode::Null, PlayoutConfig, FFMPEG_IGNORE_ERRORS, IMAGE_FORMAT,
errors::ProcError, loop_image, sec_to_time, seek_and_length, valid_source, vec_strings,
JsonPlaylist, Media, OutputMode::Null, PlayoutConfig, FFMPEG_IGNORE_ERRORS, IMAGE_FORMAT,
};
/// check if ffmpeg can read the file and apply filter to it.
/// Validate a single media file.
///
/// - Check if file exists
/// - Check if ffmpeg can read the file
/// - Check if Metadata exists
/// - Check if the file is not silent
fn check_media(
mut node: Media,
pos: usize,
begin: f64,
config: &PlayoutConfig,
) -> Result<(), Error> {
let mut enc_cmd = vec_strings!["-hide_banner", "-nostats", "-v", "level+error"];
) -> Result<(), ProcError> {
let mut enc_cmd = vec_strings!["-hide_banner", "-nostats", "-v", "level+info"];
let mut error_list = vec![];
let mut config = config.clone();
config.out.mode = Null;
let mut process_length = 0.1;
if config.logging.detect_silence {
process_length = 15.0;
let seek = node.duration / 4.0;
// Seek in file, to prevent false silence detection on intros without sound.
enc_cmd.append(&mut vec_strings!["-ss", seek]);
}
node.add_probe();
if node.probe.clone().and_then(|p| p.format).is_none() {
return Err(Error::new(
ErrorKind::Other,
format!(
"No Metadata at position <yellow>{pos}</> {}, from file <b><magenta>\"{}\"</></b>",
sec_to_time(begin),
node.source
),
));
return Err(ProcError::Custom(format!(
"No Metadata at position <yellow>{pos}</> {}, from file <b><magenta>\"{}\"</></b>",
sec_to_time(begin),
node.source
)));
}
// take care, that no seek and length command is added.
// Take care, that no seek and length command is added.
node.seek = 0.0;
node.out = node.duration;
@ -60,24 +75,30 @@ fn check_media(
let mut filter = node.filter.unwrap_or_default();
if filter.cmd().len() > 1 {
filter.cmd()[1] = filter.cmd()[1].replace("realtime=speed=1", "null")
let re_clean = Regex::new(r"volume=[0-9.]+")?;
filter.audio_chain = re_clean
.replace_all(&filter.audio_chain, "anull")
.to_string();
}
filter.add_filter("silencedetect=n=-30dB", 0, Audio);
enc_cmd.append(&mut node.cmd.unwrap_or_default());
enc_cmd.append(&mut filter.cmd());
enc_cmd.append(&mut filter.map());
enc_cmd.append(&mut vec_strings!["-t", "0.1", "-f", "null", "-"]);
enc_cmd.append(&mut vec_strings!["-t", process_length, "-f", "null", "-"]);
let mut enc_proc = match Command::new("ffmpeg")
.args(enc_cmd.clone())
let mut enc_proc = Command::new("ffmpeg")
.args(enc_cmd)
.stderr(Stdio::piped())
.spawn()
{
Err(e) => return Err(e),
Ok(proc) => proc,
};
.spawn()?;
let enc_err = BufReader::new(enc_proc.stderr.take().unwrap());
let mut silence_start = 0.0;
let mut silence_end = 0.0;
let re_start = Regex::new(r"silence_start: ([0-9]+:)?([0-9.]+)")?;
let re_end = Regex::new(r"silence_end: ([0-9]+:)?([0-9.]+)")?;
for line in enc_err.lines() {
let line = line?;
@ -91,11 +112,25 @@ fn check_media(
error_list.push(log_line);
}
}
if config.logging.detect_silence {
if let Some(start) = re_start.captures(&line).and_then(|c| c.get(2)) {
silence_start = start.as_str().parse::<f32>().unwrap_or_default();
}
if let Some(end) = re_end.captures(&line).and_then(|c| c.get(2)) {
silence_end = end.as_str().parse::<f32>().unwrap_or_default() + 0.5;
}
}
}
if silence_end - silence_start > process_length {
error_list.push("Audio is totally silent!".to_string());
}
if !error_list.is_empty() {
error!(
"<bright black>[Validator]</> ffmpeg error on position <yellow>{pos}</> - {}: <b><magenta>{}</></b>:\n{}",
"<bright black>[Validator]</> ffmpeg error on position <yellow>{pos}</> - {}: <b><magenta>{}</></b>: {}",
sec_to_time(begin),
node.source,
error_list.join("\n")
@ -136,6 +171,7 @@ pub fn validate_playlist(
length += begin;
debug!("Validate playlist from: <yellow>{date}</>");
let timer = Instant::now();
for (index, item) in playlist.program.iter().enumerate() {
if is_terminated.load(Ordering::SeqCst) {
@ -172,5 +208,5 @@ pub fn validate_playlist(
);
}
debug!("Validation done...");
debug!("Validation done, in {:.3?} ...", timer.elapsed(),);
}

View File

@ -23,6 +23,7 @@ use simplelog::*;
pub mod config;
pub mod controller;
pub mod errors;
pub mod folder;
pub mod generator;
pub mod import;
@ -373,7 +374,7 @@ pub fn modified_time(path: &str) -> Option<String> {
/// Convert a formatted time string to seconds.
pub fn time_to_sec(time_str: &str) -> f64 {
if ["now", "", "none"].contains(&time_str) || !time_str.contains(':') {
if matches!(time_str, "now" | "" | "none") || !time_str.contains(':') {
return get_sec();
}