get/update advanced config,
This commit is contained in:
parent
6de9bb9caa
commit
730439abd2
1
.gitignore
vendored
1
.gitignore
vendored
@ -25,4 +25,5 @@ ffplayout.1.gz
|
||||
/public/
|
||||
tmp/
|
||||
assets/playlist_template.json
|
||||
advanced_*.toml
|
||||
ffplayout_*.toml
|
||||
|
66
Cargo.lock
generated
66
Cargo.lock
generated
@ -679,7 +679,7 @@ version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"hashbrown 0.14.5",
|
||||
"stacker",
|
||||
]
|
||||
|
||||
@ -907,7 +907,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"hashbrown",
|
||||
"hashbrown 0.14.5",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
@ -931,6 +931,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1111,6 +1112,7 @@ dependencies = [
|
||||
"sanitize-filename",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"shlex",
|
||||
"signal-child",
|
||||
"sqlx",
|
||||
@ -1373,13 +1375,19 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap",
|
||||
"indexmap 2.2.6",
|
||||
"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.5"
|
||||
@ -1396,7 +1404,7 @@ version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7"
|
||||
dependencies = [
|
||||
"hashbrown",
|
||||
"hashbrown 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1747,6 +1755,17 @@ version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "206ca75c9c03ba3d4ace2460e57b189f39f43de612c2f85836e65c929701bb2d"
|
||||
|
||||
[[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",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
@ -1754,7 +1773,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
"hashbrown 0.14.5",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2838,7 +2858,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de514ef58196f1fc96dcaef80fe6170a1ce6215df9687a93fe8300e773fefc5"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"indexmap",
|
||||
"indexmap 2.2.6",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
@ -2885,6 +2905,36 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.2.6",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_with_macros",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "3.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test"
|
||||
version = "3.1.1"
|
||||
@ -3069,7 +3119,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"hashlink",
|
||||
"hex",
|
||||
"indexmap",
|
||||
"indexmap 2.2.6",
|
||||
"log",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
@ -3618,7 +3668,7 @@ version = "0.22.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"indexmap 2.2.6",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
|
@ -51,6 +51,7 @@ rpassword = "7.2"
|
||||
sanitize-filename = "0.5"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde_with = "3.8"
|
||||
shlex = "1.1"
|
||||
static-files = "0.2"
|
||||
sysinfo ={ version = "0.30", features = ["linux-netdevs"] }
|
||||
|
@ -15,15 +15,17 @@ const JWT_EXPIRATION_DAYS: i64 = 7;
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct Claims {
|
||||
pub id: i32,
|
||||
pub channel: i32,
|
||||
pub username: String,
|
||||
pub role: Role,
|
||||
exp: i64,
|
||||
}
|
||||
|
||||
impl Claims {
|
||||
pub fn new(id: i32, username: String, role: Role) -> Self {
|
||||
pub fn new(id: i32, channel: i32, username: String, role: Role) -> Self {
|
||||
Self {
|
||||
id,
|
||||
channel,
|
||||
username,
|
||||
role,
|
||||
exp: (Utc::now() + TimeDelta::try_days(JWT_EXPIRATION_DAYS).unwrap()).timestamp(),
|
||||
|
@ -25,6 +25,7 @@ use actix_web::{
|
||||
patch, post, put, web, HttpRequest, HttpResponse, Responder,
|
||||
};
|
||||
use actix_web_grants::{authorities::AuthDetails, proc_macro::protect};
|
||||
use shlex::split;
|
||||
|
||||
use argon2::{
|
||||
password_hash::{rand_core::OsRng, PasswordHash, SaltString},
|
||||
@ -38,7 +39,6 @@ use serde::{Deserialize, Serialize};
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::api::auth::{create_jwt, Claims};
|
||||
use crate::db::models::Role;
|
||||
use crate::utils::{
|
||||
channels::{create_channel, delete_channel},
|
||||
@ -54,10 +54,14 @@ use crate::utils::{
|
||||
public_path, read_log_file, system, TextFilter,
|
||||
};
|
||||
use crate::vec_strings;
|
||||
use crate::{
|
||||
api::auth::{create_jwt, Claims},
|
||||
utils::advanced_config::AdvancedConfig,
|
||||
};
|
||||
use crate::{
|
||||
db::{
|
||||
handles,
|
||||
models::{Channel, LoginUser, TextPreset, User},
|
||||
models::{Channel, TextPreset, User, UserMeta},
|
||||
},
|
||||
player::controller::ChannelController,
|
||||
};
|
||||
@ -180,7 +184,12 @@ pub async fn login(pool: web::Data<Pool<Sqlite>>, credentials: web::Json<User>)
|
||||
.await;
|
||||
|
||||
if verified_password.is_ok() {
|
||||
let claims = Claims::new(user.id, username.clone(), role.clone());
|
||||
let claims = Claims::new(
|
||||
user.id,
|
||||
user.channel_id.unwrap_or_default(),
|
||||
username.clone(),
|
||||
role.clone(),
|
||||
);
|
||||
|
||||
if let Ok(token) = create_jwt(claims).await {
|
||||
user.token = Some(token);
|
||||
@ -227,12 +236,15 @@ pub async fn login(pool: web::Data<Pool<Sqlite>>, credentials: web::Json<User>)
|
||||
/// -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[get("/user")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role"
|
||||
)]
|
||||
async fn get_user(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
user: web::ReqData<LoginUser>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
match handles::select_user(&pool.into_inner(), &user.username).await {
|
||||
match handles::select_user(&pool.into_inner(), user.id).await {
|
||||
Ok(user) => Ok(web::Json(user)),
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
@ -247,13 +259,13 @@ async fn get_user(
|
||||
/// curl -X GET 'http://127.0.0.1:8787/api/user/2' -H 'Content-Type: application/json' \
|
||||
/// -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[get("/user/{name}")]
|
||||
#[get("/user/{id}")]
|
||||
#[protect("Role::GlobalAdmin", ty = "Role")]
|
||||
async fn get_by_name(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
name: web::Path<String>,
|
||||
id: web::Path<i32>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
match handles::select_user(&pool.into_inner(), &name).await {
|
||||
match handles::select_user(&pool.into_inner(), *id).await {
|
||||
Ok(user) => Ok(web::Json(user)),
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
@ -287,49 +299,56 @@ async fn get_users(pool: web::Data<Pool<Sqlite>>) -> Result<impl Responder, Serv
|
||||
/// -d '{"mail": "<MAIL>", "password": "<PASS>"}' -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[put("/user/{id}")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.id || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn update_user(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
id: web::Path<i32>,
|
||||
user: web::ReqData<LoginUser>,
|
||||
data: web::Json<User>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
if *id == user.id || role.has_authority(&Role::GlobalAdmin) {
|
||||
let mut fields = String::new();
|
||||
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());
|
||||
if let Some(mail) = data.mail.clone() {
|
||||
if !fields.is_empty() {
|
||||
fields.push_str(", ");
|
||||
}
|
||||
|
||||
if !data.password.is_empty() {
|
||||
if !fields.is_empty() {
|
||||
fields.push_str(", ");
|
||||
}
|
||||
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
let password_hash = Argon2::default()
|
||||
.hash_password(data.password.clone().as_bytes(), &salt)
|
||||
.unwrap();
|
||||
|
||||
fields.push_str(format!("password = '{password_hash}'").as_str());
|
||||
}
|
||||
|
||||
if handles::update_user(&pool.into_inner(), *id, fields)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Ok("Update Success");
|
||||
};
|
||||
|
||||
return Err(ServiceError::InternalServerError);
|
||||
fields.push_str(format!("mail = '{mail}'").as_str());
|
||||
}
|
||||
|
||||
Err(ServiceError::Unauthorized("No Permission".to_string()))
|
||||
if !data.password.is_empty() {
|
||||
if !fields.is_empty() {
|
||||
fields.push_str(", ");
|
||||
}
|
||||
|
||||
let password_hash = web::block(move || {
|
||||
let salt = SaltString::generate(&mut OsRng);
|
||||
|
||||
let argon = Argon2::default()
|
||||
.hash_password(data.password.clone().as_bytes(), &salt)
|
||||
.map(|p| p.to_string());
|
||||
|
||||
argon
|
||||
})
|
||||
.await?
|
||||
.unwrap();
|
||||
|
||||
fields.push_str(format!("password = '{password_hash}'").as_str());
|
||||
}
|
||||
|
||||
if handles::update_user(&pool.into_inner(), *id, fields)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
return Ok("Update Success");
|
||||
};
|
||||
|
||||
Err(ServiceError::InternalServerError)
|
||||
}
|
||||
|
||||
/// **Add User**
|
||||
@ -360,13 +379,13 @@ async fn add_user(
|
||||
/// curl -X GET 'http://127.0.0.1:8787/api/user/2' -H 'Content-Type: application/json' \
|
||||
/// -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[delete("/user/{name}")]
|
||||
#[delete("/user/{id}")]
|
||||
#[protect("Role::GlobalAdmin", ty = "Role")]
|
||||
async fn remove_user(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
name: web::Path<String>,
|
||||
id: web::Path<i32>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
match handles::delete_user(&pool.into_inner(), &name).await {
|
||||
match handles::delete_user(&pool.into_inner(), *id).await {
|
||||
Ok(_) => return Ok("Delete user success"),
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
@ -395,10 +414,16 @@ async fn remove_user(
|
||||
/// }
|
||||
/// ```
|
||||
#[get("/channel/{id}")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn get_channel(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
id: web::Path<i32>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
if let Ok(channel) = handles::select_channel(&pool.into_inner(), &id).await {
|
||||
return Ok(web::Json(channel));
|
||||
@ -413,7 +438,7 @@ async fn get_channel(
|
||||
/// curl -X GET http://127.0.0.1:8787/api/channels -H "Authorization: Bearer <TOKEN>"
|
||||
/// ```
|
||||
#[get("/channels")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(any("Role::GlobalAdmin"), ty = "Role")]
|
||||
async fn get_all_channels(pool: web::Data<Pool<Sqlite>>) -> Result<impl Responder, ServiceError> {
|
||||
if let Ok(channel) = handles::select_all_channels(&pool.into_inner()).await {
|
||||
return Ok(web::Json(channel));
|
||||
@ -430,11 +455,18 @@ async fn get_all_channels(pool: web::Data<Pool<Sqlite>>) -> Result<impl Responde
|
||||
/// -H "Authorization: Bearer <TOKEN>"
|
||||
/// ```
|
||||
#[patch("/channel/{id}")]
|
||||
#[protect("Role::GlobalAdmin", ty = "Role")]
|
||||
#[protect(
|
||||
"Role::GlobalAdmin",
|
||||
"Role::ChannelAdmin",
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn patch_channel(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
id: web::Path<i32>,
|
||||
data: web::Json<Channel>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
if handles::update_channel(&pool.into_inner(), *id, data.into_inner())
|
||||
.await
|
||||
@ -494,6 +526,151 @@ async fn remove_channel(
|
||||
|
||||
/// #### ffplayout Config
|
||||
///
|
||||
/// **Get Advanced Config**
|
||||
///
|
||||
/// ```BASH
|
||||
/// curl -X GET http://127.0.0.1:8787/api/playout/advanced/1 -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
///
|
||||
/// Response is a JSON object
|
||||
#[get("/playout/advanced/{id}")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn get_advanced_config(
|
||||
id: web::Path<i32>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let config = manager.config.lock().unwrap().advanced.clone();
|
||||
|
||||
Ok(web::Json(config))
|
||||
}
|
||||
|
||||
/// **Update Config**
|
||||
///
|
||||
/// ```BASH
|
||||
/// curl -X PUT http://127.0.0.1:8787/api/playout/advanced/1 -H "Content-Type: application/json" \
|
||||
/// -d { <CONFIG DATA> } -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[put("/playout/advanced/{id}")]
|
||||
#[protect(
|
||||
"Role::GlobalAdmin",
|
||||
"Role::ChannelAdmin",
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn update_advanced_config(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
id: web::Path<i32>,
|
||||
data: web::Json<AdvancedConfig>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let id = manager.config.lock().unwrap().general.id;
|
||||
|
||||
if let Err(e) =
|
||||
handles::update_advanced_configuration(&pool.into_inner(), id, data.clone()).await
|
||||
{
|
||||
return Err(ServiceError::Conflict(format!("{e}")));
|
||||
}
|
||||
|
||||
let mut cfg = manager.config.lock().unwrap();
|
||||
|
||||
cfg.advanced
|
||||
.decoder
|
||||
.input_param
|
||||
.clone_from(&data.decoder.input_param);
|
||||
cfg.advanced
|
||||
.decoder
|
||||
.output_param
|
||||
.clone_from(&data.decoder.output_param);
|
||||
cfg.advanced.decoder.input_cmd = split(&data.decoder.input_param.clone().unwrap_or_default());
|
||||
cfg.advanced.decoder.output_cmd = split(&data.decoder.output_param.clone().unwrap_or_default());
|
||||
cfg.advanced
|
||||
.encoder
|
||||
.input_param
|
||||
.clone_from(&data.encoder.input_param);
|
||||
cfg.advanced.encoder.input_cmd = split(&data.encoder.input_param.clone().unwrap_or_default());
|
||||
cfg.advanced
|
||||
.ingest
|
||||
.input_param
|
||||
.clone_from(&data.encoder.input_param);
|
||||
cfg.advanced.ingest.input_cmd = split(&data.ingest.input_param.clone().unwrap_or_default());
|
||||
cfg.advanced
|
||||
.filter
|
||||
.deinterlace
|
||||
.clone_from(&data.filter.deinterlace);
|
||||
cfg.advanced
|
||||
.filter
|
||||
.pad_scale_w
|
||||
.clone_from(&data.filter.pad_scale_w);
|
||||
cfg.advanced
|
||||
.filter
|
||||
.pad_scale_h
|
||||
.clone_from(&data.filter.pad_scale_h);
|
||||
cfg.advanced
|
||||
.filter
|
||||
.pad_video
|
||||
.clone_from(&data.filter.pad_video);
|
||||
cfg.advanced.filter.fps.clone_from(&data.filter.fps);
|
||||
cfg.advanced.filter.scale.clone_from(&data.filter.scale);
|
||||
cfg.advanced.filter.set_dar.clone_from(&data.filter.set_dar);
|
||||
cfg.advanced.filter.fade_in.clone_from(&data.filter.fade_in);
|
||||
cfg.advanced
|
||||
.filter
|
||||
.fade_out
|
||||
.clone_from(&data.filter.fade_out);
|
||||
cfg.advanced
|
||||
.filter
|
||||
.overlay_logo_scale
|
||||
.clone_from(&data.filter.overlay_logo_scale);
|
||||
cfg.advanced
|
||||
.filter
|
||||
.overlay_logo_fade_in
|
||||
.clone_from(&data.filter.overlay_logo_fade_in);
|
||||
cfg.advanced
|
||||
.filter
|
||||
.overlay_logo_fade_out
|
||||
.clone_from(&data.filter.overlay_logo_fade_out);
|
||||
cfg.advanced
|
||||
.filter
|
||||
.overlay_logo
|
||||
.clone_from(&data.filter.overlay_logo);
|
||||
cfg.advanced.filter.tpad.clone_from(&data.filter.tpad);
|
||||
cfg.advanced
|
||||
.filter
|
||||
.drawtext_from_file
|
||||
.clone_from(&data.filter.drawtext_from_file);
|
||||
cfg.advanced
|
||||
.filter
|
||||
.drawtext_from_zmq
|
||||
.clone_from(&data.filter.drawtext_from_zmq);
|
||||
cfg.advanced
|
||||
.filter
|
||||
.aevalsrc
|
||||
.clone_from(&data.filter.aevalsrc);
|
||||
cfg.advanced
|
||||
.filter
|
||||
.afade_in
|
||||
.clone_from(&data.filter.afade_in);
|
||||
cfg.advanced
|
||||
.filter
|
||||
.afade_out
|
||||
.clone_from(&data.filter.afade_out);
|
||||
cfg.advanced.filter.apad.clone_from(&data.filter.apad);
|
||||
cfg.advanced.filter.volume.clone_from(&data.filter.volume);
|
||||
cfg.advanced.filter.split.clone_from(&data.filter.split);
|
||||
|
||||
Ok(web::Json("Update success"))
|
||||
}
|
||||
|
||||
/// **Get Config**
|
||||
///
|
||||
/// ```BASH
|
||||
@ -502,12 +679,16 @@ async fn remove_channel(
|
||||
///
|
||||
/// Response is a JSON object
|
||||
#[get("/playout/config/{id}")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn get_playout_config(
|
||||
_pool: web::Data<Pool<Sqlite>>,
|
||||
id: web::Path<i32>,
|
||||
_details: AuthDetails<Role>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let config = manager.config.lock().unwrap().clone();
|
||||
@ -522,12 +703,19 @@ async fn get_playout_config(
|
||||
/// -d { <CONFIG DATA> } -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[put("/playout/config/{id}")]
|
||||
#[protect("Role::GlobalAdmin", ty = "Role")]
|
||||
#[protect(
|
||||
"Role::GlobalAdmin",
|
||||
"Role::ChannelAdmin",
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn update_playout_config(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
id: web::Path<i32>,
|
||||
data: web::Json<PlayoutConfig>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let id = manager.config.lock().unwrap().general.id;
|
||||
@ -635,10 +823,16 @@ async fn update_playout_config(
|
||||
/// -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[get("/presets/{id}")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn get_presets(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
id: web::Path<i32>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
if let Ok(presets) = handles::select_presets(&pool.into_inner(), *id).await {
|
||||
return Ok(web::Json(presets));
|
||||
@ -655,11 +849,17 @@ async fn get_presets(
|
||||
/// -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[put("/presets/{id}")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn update_preset(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
id: web::Path<i32>,
|
||||
data: web::Json<TextPreset>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
if handles::update_preset(&pool.into_inner(), &id, data.into_inner())
|
||||
.await
|
||||
@ -674,15 +874,22 @@ async fn update_preset(
|
||||
/// **Add new Preset**
|
||||
///
|
||||
/// ```BASH
|
||||
/// curl -X POST http://127.0.0.1:8787/api/presets/ -H 'Content-Type: application/json' \
|
||||
/// curl -X POST http://127.0.0.1:8787/api/presets/1/ -H 'Content-Type: application/json' \
|
||||
/// -d '{ "name": "<PRESET NAME>", "text": "TEXT>", "x": "<X>", "y": "<Y>", "fontsize": 24, "line_spacing": 4, "fontcolor": "#ffffff", "box": 1, "boxcolor": "#000000", "boxborderw": 4, "alpha": 1.0, "channel_id": 1 }' \
|
||||
/// -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[post("/presets/")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[post("/presets/{id}/")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn add_preset(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
id: web::Path<i32>,
|
||||
data: web::Json<TextPreset>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
if handles::insert_preset(&pool.into_inner(), data.into_inner())
|
||||
.await
|
||||
@ -701,10 +908,16 @@ async fn add_preset(
|
||||
/// -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[delete("/presets/{id}")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn delete_preset(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
id: web::Path<i32>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
if handles::delete_preset(&pool.into_inner(), &id)
|
||||
.await
|
||||
@ -732,11 +945,17 @@ async fn delete_preset(
|
||||
/// -d '{"text": "Hello from ffplayout", "x": "(w-text_w)/2", "y": "(h-text_h)/2", fontsize": "24", "line_spacing": "4", "fontcolor": "#ffffff", "box": "1", "boxcolor": "#000000", "boxborderw": "4", "alpha": "1.0"}'
|
||||
/// ```
|
||||
#[post("/control/{id}/text/")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn send_text_message(
|
||||
id: web::Path<i32>,
|
||||
data: web::Json<TextFilter>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
|
||||
@ -757,12 +976,18 @@ pub async fn send_text_message(
|
||||
/// -d '{ "command": "reset" }' -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[post("/control/{id}/playout/")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn control_playout(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
id: web::Path<i32>,
|
||||
control: web::Json<ControlParams>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
|
||||
@ -797,10 +1022,16 @@ pub async fn control_playout(
|
||||
/// }
|
||||
/// ```
|
||||
#[get("/control/{id}/media/current")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn media_current(
|
||||
id: web::Path<i32>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let media_map = get_data_map(&manager);
|
||||
@ -822,11 +1053,17 @@ pub async fn media_current(
|
||||
/// -d '{"command": "start"}'
|
||||
/// ```
|
||||
#[post("/control/{id}/process/")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn process_control(
|
||||
id: web::Path<i32>,
|
||||
proc: web::Json<Process>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
|
||||
@ -863,11 +1100,17 @@ pub async fn process_control(
|
||||
/// -H 'Content-Type: application/json' -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[get("/playlist/{id}")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn get_playlist(
|
||||
id: web::Path<i32>,
|
||||
obj: web::Query<DateObj>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let config = manager.config.lock().unwrap().clone();
|
||||
@ -886,11 +1129,17 @@ pub async fn get_playlist(
|
||||
/// --data "{<JSON playlist data>}"
|
||||
/// ```
|
||||
#[post("/playlist/{id}/")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn save_playlist(
|
||||
id: web::Path<i32>,
|
||||
data: web::Json<JsonPlaylist>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let config = manager.config.lock().unwrap().clone();
|
||||
@ -920,14 +1169,21 @@ pub async fn save_playlist(
|
||||
/// {"start": "10:00:00", "duration": "14:00:00", "shuffle": false, "paths": ["path/3", "path/4"]}]}}'
|
||||
/// ```
|
||||
#[post("/playlist/{id}/generate/{date}")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn gen_playlist(
|
||||
params: web::Path<(i32, String)>,
|
||||
id: web::Path<i32>,
|
||||
date: web::Path<String>,
|
||||
data: Option<web::Json<PathsObj>>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(params.0).unwrap();
|
||||
manager.config.lock().unwrap().general.generate = Some(vec![params.1.clone()]);
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
manager.config.lock().unwrap().general.generate = Some(vec![date.clone()]);
|
||||
let storage_path = manager.config.lock().unwrap().global.storage_path.clone();
|
||||
|
||||
if let Some(obj) = data {
|
||||
@ -965,15 +1221,22 @@ pub async fn gen_playlist(
|
||||
/// -H 'Content-Type: application/json' -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[delete("/playlist/{id}/{date}")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn del_playlist(
|
||||
params: web::Path<(i32, String)>,
|
||||
id: web::Path<i32>,
|
||||
date: web::Path<String>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(params.0).unwrap();
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let config = manager.config.lock().unwrap().clone();
|
||||
|
||||
match delete_playlist(&config, ¶ms.1).await {
|
||||
match delete_playlist(&config, &date).await {
|
||||
Ok(m) => Ok(web::Json(m)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
@ -988,10 +1251,16 @@ pub async fn del_playlist(
|
||||
/// -H 'Content-Type: application/json' -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[get("/log/{id}")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn get_log(
|
||||
id: web::Path<i32>,
|
||||
log: web::Query<DateObj>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
read_log_file(&id, &log.date).await
|
||||
}
|
||||
@ -1005,11 +1274,17 @@ pub async fn get_log(
|
||||
/// -d '{ "source": "/" }' -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[post("/file/{id}/browse/")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn file_browser(
|
||||
id: web::Path<i32>,
|
||||
data: web::Json<PathObject>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let channel = manager.channel.lock().unwrap().clone();
|
||||
@ -1028,11 +1303,17 @@ pub async fn file_browser(
|
||||
/// -d '{"source": "<FOLDER PATH>"}' -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[post("/file/{id}/create-folder/")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn add_dir(
|
||||
id: web::Path<i32>,
|
||||
data: web::Json<PathObject>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<HttpResponse, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let config = manager.config.lock().unwrap().clone();
|
||||
@ -1047,11 +1328,17 @@ pub async fn add_dir(
|
||||
/// -d '{"source": "<SOURCE>", "target": "<TARGET>"}' -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[post("/file/{id}/rename/")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn move_rename(
|
||||
id: web::Path<i32>,
|
||||
data: web::Json<MoveObject>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let config = manager.config.lock().unwrap().clone();
|
||||
@ -1069,11 +1356,17 @@ pub async fn move_rename(
|
||||
/// -d '{"source": "<SOURCE>"}' -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[post("/file/{id}/remove/")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn remove(
|
||||
id: web::Path<i32>,
|
||||
data: web::Json<PathObject>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let config = manager.config.lock().unwrap().clone();
|
||||
@ -1090,14 +1383,21 @@ pub async fn remove(
|
||||
/// curl -X PUT http://127.0.0.1:8787/api/file/1/upload/ -H 'Authorization: Bearer <TOKEN>'
|
||||
/// -F "file=@file.mp4"
|
||||
/// ```
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[put("/file/{id}/upload/")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn save_file(
|
||||
id: web::Path<i32>,
|
||||
req: HttpRequest,
|
||||
payload: Multipart,
|
||||
obj: web::Query<FileObj>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<HttpResponse, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let config = manager.config.lock().unwrap().clone();
|
||||
@ -1178,14 +1478,21 @@ async fn get_public(public: web::Path<String>) -> Result<actix_files::NamedFile,
|
||||
/// curl -X PUT http://127.0.0.1:8787/api/file/1/import/ -H 'Authorization: Bearer <TOKEN>'
|
||||
/// -F "file=@list.m3u"
|
||||
/// ```
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[put("/file/{id}/import/")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn import_playlist(
|
||||
id: web::Path<i32>,
|
||||
req: HttpRequest,
|
||||
payload: Multipart,
|
||||
obj: web::Query<ImportObj>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<HttpResponse, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let channel_name = manager.channel.lock().unwrap().name.clone();
|
||||
@ -1234,11 +1541,17 @@ async fn import_playlist(
|
||||
/// -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[get("/program/{id}/")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
async fn get_program(
|
||||
id: web::Path<i32>,
|
||||
obj: web::Query<ProgramObj>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let config = manager.config.lock().unwrap().clone();
|
||||
@ -1326,10 +1639,16 @@ async fn get_program(
|
||||
/// -H 'Content-Type: application/json' -H 'Authorization: Bearer <TOKEN>'
|
||||
/// ```
|
||||
#[get("/system/{id}")]
|
||||
#[protect(any("Role::GlobalAdmin", "Role::User"), ty = "Role")]
|
||||
#[protect(
|
||||
any("Role::GlobalAdmin", "Role::ChannelAdmin", "Role::User"),
|
||||
ty = "Role",
|
||||
expr = "*id == user.channel || role.has_authority(&Role::GlobalAdmin)"
|
||||
)]
|
||||
pub async fn get_system_stat(
|
||||
id: web::Path<i32>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
role: AuthDetails<Role>,
|
||||
user: web::ReqData<UserMeta>,
|
||||
) -> Result<impl Responder, ServiceError> {
|
||||
let manager = controllers.lock().unwrap().get(*id).unwrap();
|
||||
let config = manager.config.lock().unwrap().clone();
|
||||
|
@ -167,15 +167,12 @@ pub async fn select_configuration(
|
||||
pub async fn insert_configuration(
|
||||
conn: &Pool<Sqlite>,
|
||||
channel_id: i32,
|
||||
playlist_path: String,
|
||||
output_param: String,
|
||||
) -> Result<SqliteQueryResult, sqlx::Error> {
|
||||
let query =
|
||||
"INSERT INTO configurations (channel_id, playlist_path, output_param) VALUES($1, $2, $3)";
|
||||
let query = "INSERT INTO configurations (channel_id, output_param) VALUES($1, $2)";
|
||||
|
||||
sqlx::query(query)
|
||||
.bind(channel_id)
|
||||
.bind(playlist_path)
|
||||
.bind(output_param)
|
||||
.execute(conn)
|
||||
.await
|
||||
@ -308,19 +305,14 @@ pub async fn select_role(conn: &Pool<Sqlite>, id: &i32) -> Result<Role, sqlx::Er
|
||||
}
|
||||
|
||||
pub async fn select_login(conn: &Pool<Sqlite>, user: &str) -> Result<User, sqlx::Error> {
|
||||
let query = "SELECT id, mail, username, password, role_id FROM user WHERE username = $1";
|
||||
let query =
|
||||
"SELECT id, mail, username, password, role_id, channel_id FROM user WHERE username = $1";
|
||||
|
||||
sqlx::query_as(query).bind(user).fetch_one(conn).await
|
||||
}
|
||||
|
||||
pub async fn select_user(conn: &Pool<Sqlite>, user: &str) -> Result<User, sqlx::Error> {
|
||||
let query = "SELECT id, mail, username, role_id FROM user WHERE username = $1";
|
||||
|
||||
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";
|
||||
pub async fn select_user(conn: &Pool<Sqlite>, id: i32) -> Result<User, sqlx::Error> {
|
||||
let query = "SELECT id, mail, username, role_id, channel_id FROM user WHERE id = $1";
|
||||
|
||||
sqlx::query_as(query).bind(id).fetch_one(conn).await
|
||||
}
|
||||
@ -367,13 +359,10 @@ 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;";
|
||||
pub async fn delete_user(conn: &Pool<Sqlite>, id: i32) -> Result<SqliteQueryResult, sqlx::Error> {
|
||||
let query = "DELETE FROM user WHERE id = $1;";
|
||||
|
||||
sqlx::query(query).bind(name).execute(conn).await
|
||||
sqlx::query(query).bind(id).execute(conn).await
|
||||
}
|
||||
|
||||
pub async fn select_presets(conn: &Pool<Sqlite>, id: i32) -> Result<Vec<TextPreset>, sqlx::Error> {
|
||||
|
@ -80,14 +80,14 @@ fn empty_string() -> String {
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||
pub struct LoginUser {
|
||||
pub struct UserMeta {
|
||||
pub id: i32,
|
||||
pub username: String,
|
||||
pub channel: i32,
|
||||
}
|
||||
|
||||
impl LoginUser {
|
||||
pub fn new(id: i32, username: String) -> Self {
|
||||
Self { id, username }
|
||||
impl UserMeta {
|
||||
pub fn new(id: i32, channel: i32) -> Self {
|
||||
Self { id, channel }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ use ffplayout::{
|
||||
api::{auth, routes::*},
|
||||
db::{
|
||||
db_pool, handles,
|
||||
models::{init_globales, LoginUser},
|
||||
models::{init_globales, UserMeta},
|
||||
},
|
||||
player::controller::{ChannelController, ChannelManager},
|
||||
sse::{broadcast::Broadcaster, routes::*, AuthState},
|
||||
@ -59,7 +59,7 @@ async fn validator(
|
||||
req.attach(vec![claims.role]);
|
||||
|
||||
req.extensions_mut()
|
||||
.insert(LoginUser::new(claims.id, claims.username));
|
||||
.insert(UserMeta::new(claims.id, claims.channel));
|
||||
|
||||
Ok(req)
|
||||
}
|
||||
@ -150,6 +150,8 @@ async fn main() -> std::io::Result<()> {
|
||||
.service(get_by_name)
|
||||
.service(get_users)
|
||||
.service(remove_user)
|
||||
.service(get_advanced_config)
|
||||
.service(update_advanced_config)
|
||||
.service(get_playout_config)
|
||||
.service(update_playout_config)
|
||||
.service(add_preset)
|
||||
@ -225,15 +227,6 @@ async fn main() -> std::io::Result<()> {
|
||||
.workers(thread_count)
|
||||
.run()
|
||||
.await
|
||||
} else if ARGS.list_channels {
|
||||
let channels = handles::select_all_channels(&pool)
|
||||
.await
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
|
||||
let ids = channels.iter().map(|c| c.id).collect::<Vec<i32>>();
|
||||
|
||||
info!("Available channels: {ids:?}");
|
||||
|
||||
Ok(())
|
||||
} else if let Some(channels) = &ARGS.channels {
|
||||
for (index, channel_id) in channels.iter().enumerate() {
|
||||
let channel = handles::select_channel(&pool, channel_id).await.unwrap();
|
||||
|
@ -1,7 +1,13 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use shlex::split;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::db::models::AdvancedConfiguration;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{serde_as, NoneAsEmptyString};
|
||||
use shlex::split;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
use crate::db::{handles, models::AdvancedConfiguration};
|
||||
use crate::utils::ServiceError;
|
||||
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct AdvancedConfig {
|
||||
@ -11,49 +17,83 @@ pub struct AdvancedConfig {
|
||||
pub ingest: IngestConfig,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct DecoderConfig {
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub input_param: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub output_param: Option<String>,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub input_cmd: Option<Vec<String>>,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub output_cmd: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct EncoderConfig {
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub input_param: Option<String>,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub input_cmd: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct IngestConfig {
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub input_param: Option<String>,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub input_cmd: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct FilterConfig {
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub deinterlace: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub pad_scale_w: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub pad_scale_h: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub pad_video: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub fps: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub scale: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub set_dar: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub fade_in: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub fade_out: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub overlay_logo_scale: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub overlay_logo_fade_in: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub overlay_logo_fade_out: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub overlay_logo: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub tpad: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub drawtext_from_file: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub drawtext_from_zmq: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub aevalsrc: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub afade_in: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub afade_out: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub apad: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub volume: Option<String>,
|
||||
#[serde_as(as = "NoneAsEmptyString")]
|
||||
pub split: Option<String>,
|
||||
}
|
||||
|
||||
@ -112,4 +152,122 @@ impl AdvancedConfig {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn dump(pool: &Pool<Sqlite>, id: i32) -> Result<(), ServiceError> {
|
||||
let config = Self::new(handles::select_advanced_configuration(pool, id).await?);
|
||||
let f_keys = [
|
||||
"deinterlace",
|
||||
"pad_scale_w",
|
||||
"pad_scale_h",
|
||||
"pad_video",
|
||||
"fps",
|
||||
"scale",
|
||||
"set_dar",
|
||||
"fade_in",
|
||||
"fade_out",
|
||||
"overlay_logo_scale",
|
||||
"overlay_logo_fade_in",
|
||||
"overlay_logo_fade_out",
|
||||
"overlay_logo",
|
||||
"tpad",
|
||||
"drawtext_from_file",
|
||||
"drawtext_from_zmq",
|
||||
"aevalsrc",
|
||||
"afade_in",
|
||||
"afade_out",
|
||||
"apad",
|
||||
"volume",
|
||||
"split",
|
||||
];
|
||||
|
||||
let toml_string = toml_edit::ser::to_string_pretty(&config)?;
|
||||
let mut doc = toml_string.parse::<toml_edit::DocumentMut>()?;
|
||||
|
||||
if let Some(decoder) = doc.get_mut("decoder").and_then(|o| o.as_table_mut()) {
|
||||
decoder
|
||||
.decor_mut()
|
||||
.set_prefix("# Changing these settings is for advanced users only!\n# There will be no support or guarantee that it will be stable after changing them.\n\n");
|
||||
}
|
||||
|
||||
if let Some(output_param) = doc
|
||||
.get_mut("decoder")
|
||||
.and_then(|d| d.get_mut("output_param"))
|
||||
.and_then(|o| o.as_value_mut())
|
||||
{
|
||||
output_param
|
||||
.decor_mut()
|
||||
.set_suffix(" # get also applied to ingest instance.");
|
||||
}
|
||||
|
||||
if let Some(filter) = doc.get_mut("filter") {
|
||||
for key in &f_keys {
|
||||
if let Some(item) = filter.get_mut(*key).and_then(|o| o.as_value_mut()) {
|
||||
match *key {
|
||||
"deinterlace" => item.decor_mut().set_suffix(" # yadif=0:-1:0"),
|
||||
"pad_scale_w" => item.decor_mut().set_suffix(" # scale={}:-1"),
|
||||
"pad_scale_h" => item.decor_mut().set_suffix(" # scale=-1:{}"),
|
||||
"pad_video" => item.decor_mut().set_suffix(
|
||||
" # pad=max(iw\\,ih*({0}/{1})):ow/({0}/{1}):(ow-iw)/2:(oh-ih)/2",
|
||||
),
|
||||
"fps" => item.decor_mut().set_suffix(" # fps={}"),
|
||||
"scale" => item.decor_mut().set_suffix(" # scale={}:{}"),
|
||||
"set_dar" => item.decor_mut().set_suffix(" # setdar=dar={}"),
|
||||
"fade_in" => item.decor_mut().set_suffix(" # fade=in:st=0:d=0.5"),
|
||||
"fade_out" => item.decor_mut().set_suffix(" # fade=out:st={}:d=1.0"),
|
||||
"overlay_logo_scale" => item.decor_mut().set_suffix(" # scale={}"),
|
||||
"overlay_logo_fade_in" => {
|
||||
item.decor_mut().set_suffix(" # fade=in:st=0:d=1.0:alpha=1")
|
||||
}
|
||||
"overlay_logo_fade_out" => item
|
||||
.decor_mut()
|
||||
.set_suffix(" # fade=out:st={}:d=1.0:alpha=1"),
|
||||
"overlay_logo" => item
|
||||
.decor_mut()
|
||||
.set_suffix(" # null[l];[v][l]overlay={}:shortest=1"),
|
||||
"tpad" => item
|
||||
.decor_mut()
|
||||
.set_suffix(" # tpad=stop_mode=add:stop_duration={}"),
|
||||
"drawtext_from_file" => {
|
||||
item.decor_mut().set_suffix(" # drawtext=text='{}':{}{}")
|
||||
}
|
||||
"drawtext_from_zmq" => item
|
||||
.decor_mut()
|
||||
.set_suffix(" # zmq=b=tcp\\\\://'{}',drawtext@dyntext={}"),
|
||||
"aevalsrc" => item.decor_mut().set_suffix(
|
||||
" # aevalsrc=0:channel_layout=stereo:duration={}:sample_rate=48000",
|
||||
),
|
||||
"afade_in" => item.decor_mut().set_suffix(" # afade=in:st=0:d=0.5"),
|
||||
"afade_out" => item.decor_mut().set_suffix(" # afade=out:st={}:d=1.0"),
|
||||
"apad" => item.decor_mut().set_suffix(" # apad=whole_dur={}"),
|
||||
"volume" => item.decor_mut().set_suffix(" # volume={}"),
|
||||
"split" => item.decor_mut().set_suffix(" # split={}{}"),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tokio::fs::write(&format!("advanced_{id}.toml"), doc.to_string()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn import(pool: &Pool<Sqlite>, import: Vec<String>) -> Result<(), ServiceError> {
|
||||
let id = import[0].parse::<i32>()?;
|
||||
let path = Path::new(&import[1]);
|
||||
|
||||
if path.is_file() {
|
||||
let mut file = tokio::fs::File::open(path).await?;
|
||||
let mut contents = String::new();
|
||||
file.read_to_string(&mut contents).await?;
|
||||
|
||||
let config: Self = toml_edit::de::from_str(&contents).unwrap();
|
||||
|
||||
handles::update_advanced_configuration(pool, id, config).await?;
|
||||
} else {
|
||||
return Err(ServiceError::BadRequest("Path not exists!".to_string()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ use crate::db::{
|
||||
handles::{self, insert_user},
|
||||
models::{GlobalSettings, User},
|
||||
};
|
||||
use crate::utils::config::PlayoutConfig;
|
||||
use crate::utils::{advanced_config::AdvancedConfig, config::PlayoutConfig};
|
||||
use crate::ARGS;
|
||||
|
||||
#[derive(Parser, Debug, Clone)]
|
||||
@ -39,9 +39,22 @@ pub struct Args {
|
||||
)]
|
||||
pub channels: Option<Vec<i32>>,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
help = "Dump advanced channel configuration to advanced_{channel}.toml"
|
||||
)]
|
||||
pub dump_advanced: Option<i32>,
|
||||
|
||||
#[clap(long, help = "Dump channel configuration to ffplayout_{channel}.toml")]
|
||||
pub dump_config: Option<i32>,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
help = "import advanced channel configuration from file. Input must be `{channel id} {path to toml}`",
|
||||
num_args = 2
|
||||
)]
|
||||
pub import_advanced: Option<Vec<String>>,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
help = "import channel configuration from file. Input must be `{channel id} {path to toml}`",
|
||||
@ -251,6 +264,32 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
|
||||
return Err(0);
|
||||
}
|
||||
|
||||
if ARGS.list_channels {
|
||||
match handles::select_all_channels(pool).await {
|
||||
Ok(channels) => {
|
||||
let chl = channels
|
||||
.iter()
|
||||
.map(|c| (c.id, c.name.clone()))
|
||||
.collect::<Vec<(i32, String)>>();
|
||||
|
||||
println!(
|
||||
"Available channels:\n{}",
|
||||
chl.iter()
|
||||
.map(|(i, t)| format!(" {i}: '{t}'"))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n")
|
||||
);
|
||||
|
||||
return Err(0);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("List channels: {e}");
|
||||
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(id) = ARGS.dump_config {
|
||||
match PlayoutConfig::dump(pool, id).await {
|
||||
Ok(_) => {
|
||||
@ -265,6 +304,34 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(id) = ARGS.dump_config {
|
||||
match PlayoutConfig::dump(pool, id).await {
|
||||
Ok(_) => {
|
||||
println!("Dump config to: ffplayout_{id}.toml");
|
||||
exit(0);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Dump config: {e}");
|
||||
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(id) = ARGS.dump_advanced {
|
||||
match AdvancedConfig::dump(pool, id).await {
|
||||
Ok(_) => {
|
||||
println!("Dump config to: advanced_{id}.toml");
|
||||
exit(0);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("Dump config: {e}");
|
||||
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(import) = &ARGS.import_config {
|
||||
match PlayoutConfig::import(pool, import.clone()).await {
|
||||
Ok(_) => {
|
||||
@ -279,5 +346,19 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(import) = &ARGS.import_advanced {
|
||||
match AdvancedConfig::import(pool, import.clone()).await {
|
||||
Ok(_) => {
|
||||
println!("Import config done...");
|
||||
exit(0);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -16,14 +16,12 @@ pub async fn create_channel(
|
||||
queue: Arc<Mutex<Vec<Arc<Mutex<MailQueue>>>>>,
|
||||
target_channel: Channel,
|
||||
) -> Result<Channel, ServiceError> {
|
||||
let channel_name = target_channel.name.to_lowercase().replace(' ', "");
|
||||
let channel = handles::insert_channel(conn, target_channel).await?;
|
||||
|
||||
let playlist_path = format!("/var/lib/ffplayout/playlists/{channel_name}");
|
||||
let output_param = format!("-c:v libx264 -crf 23 -x264-params keyint=50:min-keyint=25:scenecut=-1 -maxrate 1300k -bufsize 2600k -preset faster -tune zerolatency -profile:v Main -level 3.1 -c:a aac -ar 44100 -b:a 128k -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{0}-%d.ts /usr/share/ffplayout/public/live/stream{0}.m3u8", channel.id);
|
||||
|
||||
handles::insert_advanced_configuration(conn, channel.id).await?;
|
||||
handles::insert_configuration(conn, channel.id, playlist_path, output_param).await?;
|
||||
handles::insert_configuration(conn, channel.id, output_param).await?;
|
||||
|
||||
let config = PlayoutConfig::new(conn, channel.id).await;
|
||||
let m_queue = Arc::new(Mutex::new(MailQueue::new(channel.id, config.mail.clone())));
|
||||
|
@ -542,14 +542,14 @@ fn default_track_index() -> i32 {
|
||||
// }
|
||||
|
||||
impl PlayoutConfig {
|
||||
pub async fn new(pool: &Pool<Sqlite>, channel: i32) -> Self {
|
||||
pub async fn new(pool: &Pool<Sqlite>, channel_id: i32) -> Self {
|
||||
let global = handles::select_global(pool)
|
||||
.await
|
||||
.expect("Can't read globals");
|
||||
let config = handles::select_configuration(pool, channel)
|
||||
let config = handles::select_configuration(pool, channel_id)
|
||||
.await
|
||||
.expect("Can't read config");
|
||||
let adv_config = handles::select_advanced_configuration(pool, channel)
|
||||
let adv_config = handles::select_advanced_configuration(pool, channel_id)
|
||||
.await
|
||||
.expect("Can't read advanced config");
|
||||
|
||||
@ -567,7 +567,23 @@ impl PlayoutConfig {
|
||||
let mut output = Output::new(&config);
|
||||
|
||||
if !global.shared_storage {
|
||||
global.storage_path = global.storage_path.join(channel.to_string());
|
||||
global.storage_path = global.storage_path.join(channel_id.to_string());
|
||||
}
|
||||
|
||||
if !global.storage_path.is_dir() {
|
||||
tokio::fs::create_dir_all(&global.storage_path)
|
||||
.await
|
||||
.expect("Can't create storage folder");
|
||||
}
|
||||
|
||||
if channel_id > 1 || !global.shared_storage {
|
||||
global.playlist_path = global.playlist_path.join(channel_id.to_string());
|
||||
}
|
||||
|
||||
if !global.playlist_path.is_dir() {
|
||||
tokio::fs::create_dir_all(&global.playlist_path)
|
||||
.await
|
||||
.expect("Can't create playlist folder");
|
||||
}
|
||||
|
||||
let (filler_path, _, _) = norm_abs_path(&global.storage_path, &config.storage_filler)
|
||||
@ -696,12 +712,14 @@ impl PlayoutConfig {
|
||||
|
||||
pub async fn dump(pool: &Pool<Sqlite>, id: i32) -> Result<(), ServiceError> {
|
||||
let mut config = Self::new(pool, id).await;
|
||||
config.storage.filler = config
|
||||
.storage
|
||||
.filler
|
||||
.strip_prefix(config.global.storage_path.clone())
|
||||
.unwrap_or(&config.storage.filler)
|
||||
.to_owned();
|
||||
config.storage.filler.clone_from(
|
||||
&config
|
||||
.storage
|
||||
.filler
|
||||
.strip_prefix(config.global.storage_path.clone())
|
||||
.unwrap_or(&config.storage.filler)
|
||||
.to_path_buf(),
|
||||
);
|
||||
|
||||
let toml_string = toml_edit::ser::to_string_pretty(&config)?;
|
||||
tokio::fs::write(&format!("ffplayout_{id}.toml"), toml_string).await?;
|
||||
|
@ -107,6 +107,12 @@ impl From<toml_edit::ser::Error> for ServiceError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml_edit::TomlError> for ServiceError {
|
||||
fn from(err: toml_edit::TomlError) -> ServiceError {
|
||||
ServiceError::BadRequest(err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<uuid::Error> for ServiceError {
|
||||
fn from(err: uuid::Error) -> ServiceError {
|
||||
ServiceError::BadRequest(err.to_string())
|
||||
|
@ -13,9 +13,9 @@ pub async fn read_playlist(
|
||||
config: &PlayoutConfig,
|
||||
date: String,
|
||||
) -> Result<JsonPlaylist, ServiceError> {
|
||||
let (path, _, _) = norm_abs_path(&config.global.playlist_path, "")?;
|
||||
let mut playlist_path = path;
|
||||
let d: Vec<&str> = date.split('-').collect();
|
||||
let mut playlist_path = config.global.playlist_path.clone();
|
||||
|
||||
playlist_path = playlist_path
|
||||
.join(d[0])
|
||||
.join(d[1])
|
||||
@ -33,8 +33,8 @@ pub async fn write_playlist(
|
||||
json_data: JsonPlaylist,
|
||||
) -> Result<String, ServiceError> {
|
||||
let date = json_data.date.clone();
|
||||
let mut playlist_path = config.global.playlist_path.clone();
|
||||
let d: Vec<&str> = date.split('-').collect();
|
||||
let mut playlist_path = config.global.playlist_path.clone();
|
||||
|
||||
if !playlist_path
|
||||
.extension()
|
||||
@ -123,8 +123,9 @@ pub fn generate_playlist(manager: ChannelManager) -> Result<JsonPlaylist, Servic
|
||||
}
|
||||
|
||||
pub async fn delete_playlist(config: &PlayoutConfig, date: &str) -> Result<String, ServiceError> {
|
||||
let mut playlist_path = PathBuf::from(&config.global.playlist_path);
|
||||
let d: Vec<&str> = date.split('-').collect();
|
||||
let mut playlist_path = PathBuf::from(&config.global.playlist_path);
|
||||
|
||||
playlist_path = playlist_path
|
||||
.join(d[0])
|
||||
.join(d[1])
|
||||
|
Loading…
Reference in New Issue
Block a user