From b97f30c2b4ced0b437b1770832af868d9ccf7187 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Tue, 14 Jun 2022 12:09:31 +0200 Subject: [PATCH] rename GlobalConfig and Playlist struct, get playlist config --- src/api/auth.rs | 6 ++-- src/api/handles.rs | 22 +++--------- src/api/routes.rs | 65 +++++++++++++++++++++++++++++++++--- src/api/utils.rs | 24 +++++++++++++ src/bin/ffpapi.rs | 14 +++++--- src/filter/a_loudnorm.rs | 4 +-- src/filter/ingest_filter.rs | 6 ++-- src/filter/mod.rs | 27 +++++++++------ src/filter/v_drawtext.rs | 4 +-- src/filter/v_overlay.rs | 4 +-- src/input/folder.rs | 8 ++--- src/input/ingest.rs | 8 +++-- src/input/mod.rs | 4 +-- src/input/playlist.rs | 12 +++---- src/main.rs | 6 ++-- src/output/desktop.rs | 4 +-- src/output/hls.rs | 6 ++-- src/output/mod.rs | 5 +-- src/output/stream.rs | 4 +-- src/rpc/mod.rs | 6 ++-- src/tests/mod.rs | 4 +-- src/tests/utils/mod.rs | 2 +- src/utils/config.rs | 8 ++--- src/utils/generator.rs | 6 ++-- src/utils/json_serializer.rs | 24 +++++++------ src/utils/json_validate.rs | 8 +++-- src/utils/mod.rs | 18 +++++----- 27 files changed, 200 insertions(+), 109 deletions(-) diff --git a/src/api/auth.rs b/src/api/auth.rs index 14c625a2..ba34919e 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize}; use crate::api::utils::GlobalSettings; -// Token lifetime and Secret key are hardcoded for clarity -const JWT_EXPIRATION_MINUTES: i64 = 60; +// Token lifetime +const JWT_EXPIRATION_DAYS: i64 = 7; #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] pub struct Claims { @@ -23,7 +23,7 @@ impl Claims { id, username, role, - exp: (Utc::now() + Duration::minutes(JWT_EXPIRATION_MINUTES)).timestamp(), + exp: (Utc::now() + Duration::days(JWT_EXPIRATION_DAYS)).timestamp(), } } } diff --git a/src/api/handles.rs b/src/api/handles.rs index 6882c5c7..2c247f84 100644 --- a/src/api/handles.rs +++ b/src/api/handles.rs @@ -1,35 +1,23 @@ -use std::path::Path; - use argon2::{ password_hash::{rand_core::OsRng, SaltString}, Argon2, PasswordHasher, }; -use faccess::PathExt; + use rand::{distributions::Alphanumeric, Rng}; use simplelog::*; use sqlx::{migrate::MigrateDatabase, sqlite::SqliteQueryResult, Pool, Sqlite, SqlitePool}; -use crate::api::models::{Settings, User}; use crate::api::utils::GlobalSettings; +use crate::api::{ + models::{Settings, User}, + utils::db_path, +}; #[derive(Debug, sqlx::FromRow)] struct Role { name: String, } -pub fn db_path() -> Result> { - let sys_path = Path::new("/usr/share/ffplayout"); - let mut db_path = String::from("./ffplayout.db"); - - if sys_path.is_dir() && sys_path.writable() { - db_path = String::from("/usr/share/ffplayout/ffplayout.db"); - } else if Path::new("./assets").is_dir() { - db_path = String::from("./assets/ffplayout.db"); - } - - Ok(db_path) -} - async fn create_schema() -> Result { let conn = db_connection().await?; let query = "PRAGMA foreign_keys = ON; diff --git a/src/api/routes.rs b/src/api/routes.rs index fcfc32a6..82cd7a54 100644 --- a/src/api/routes.rs +++ b/src/api/routes.rs @@ -1,5 +1,5 @@ use actix_web::{get, http::StatusCode, patch, post, put, web, Responder}; -use actix_web_grants::proc_macro::has_any_role; +use actix_web_grants::{permissions::AuthDetails, proc_macro::has_any_role}; use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, SaltString}, Argon2, PasswordHasher, PasswordVerifier, @@ -14,9 +14,11 @@ use crate::api::{ db_add_user, db_get_settings, db_login, db_role, db_update_settings, db_update_user, }, models::{LoginUser, Settings, User}, - utils::Role, + utils::{read_playout_config, Role}, }; +use crate::utils::playout_config; + #[derive(Serialize)] struct ResponseObj { message: String, @@ -24,6 +26,37 @@ struct ResponseObj { data: Option, } +#[derive(Debug, Serialize, Clone)] +struct ResponsePlayoutConfig { + general: Option, + rpc_server: Option, + mail: Option, + logging: Option, + processing: Option, + ingest: Option, + playlist: Option, + storage: Option, + text: Option, + out: Option, +} + +impl ResponsePlayoutConfig { + fn new() -> Self { + Self { + general: None, + rpc_server: None, + mail: None, + logging: None, + processing: None, + ingest: None, + playlist: None, + storage: None, + text: None, + out: None, + } + } +} + /// curl -X GET http://127.0.0.1:8080/api/settings/1 -H "Authorization: Bearer " #[get("/settings/{id}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] @@ -56,13 +89,35 @@ async fn patch_settings( Err(ServiceError::InternalServerError) } +/// curl -X GET http://localhost:8080/api/playout/config/1 --header 'Authorization: ' #[get("/playout/config/{id}")] #[has_any_role("Role::Admin", "Role::User", type = "Role")] -async fn get_playout_config(id: web::Path) -> Result { +async fn get_playout_config( + id: web::Path, + details: AuthDetails, +) -> Result { if let Ok(settings) = db_get_settings(&id).await { - println!("{:?}", settings.config_path); + if let Ok(config) = read_playout_config(&settings.config_path) { + let mut playout_cfg = ResponsePlayoutConfig::new(); - return Ok("settings"); + playout_cfg.playlist = Some(config.playlist); + playout_cfg.storage = Some(config.storage); + playout_cfg.text = Some(config.text); + + if details.has_role(&Role::Admin) { + playout_cfg.general = Some(config.general); + playout_cfg.rpc_server = Some(config.rpc_server); + playout_cfg.mail = Some(config.mail); + playout_cfg.logging = Some(config.logging); + playout_cfg.processing = Some(config.processing); + playout_cfg.ingest = Some(config.ingest); + playout_cfg.out = Some(config.out); + + return Ok(web::Json(playout_cfg)); + } + + return Ok(web::Json(playout_cfg)); + } }; Err(ServiceError::InternalServerError) diff --git a/src/api/utils.rs b/src/api/utils.rs index 71caa0aa..1838fc19 100644 --- a/src/api/utils.rs +++ b/src/api/utils.rs @@ -1,3 +1,6 @@ +use std::{error::Error, fs::File, path::Path}; + +use faccess::PathExt; use once_cell::sync::OnceCell; use simplelog::*; @@ -6,6 +9,7 @@ use crate::api::{ handles::{db_add_user, db_global, db_init}, models::User, }; +use crate::utils::PlayoutConfig; #[derive(PartialEq, Clone)] pub enum Role { @@ -53,6 +57,19 @@ pub async fn init_config() { INSTANCE.set(config).unwrap(); } +pub fn db_path() -> Result> { + let sys_path = Path::new("/usr/share/ffplayout"); + let mut db_path = String::from("./ffplayout.db"); + + if sys_path.is_dir() && sys_path.writable() { + db_path = String::from("/usr/share/ffplayout/ffplayout.db"); + } else if Path::new("./assets").is_dir() { + db_path = String::from("./assets/ffplayout.db"); + } + + Ok(db_path) +} + pub async fn run_args(args: Args) -> Result<(), i32> { if !args.init && args.listen.is_none() && args.username.is_none() { error!("Wrong number of arguments! Run ffpapi --help for more information."); @@ -96,3 +113,10 @@ pub async fn run_args(args: Args) -> Result<(), i32> { Ok(()) } + +pub fn read_playout_config(path: &str) -> Result> { + let file = File::open(path)?; + let config: PlayoutConfig = serde_yaml::from_reader(file)?; + + Ok(config) +} diff --git a/src/bin/ffpapi.rs b/src/bin/ffpapi.rs index 220bef3c..007b4bcb 100644 --- a/src/bin/ffpapi.rs +++ b/src/bin/ffpapi.rs @@ -1,4 +1,4 @@ -use std::process::exit; +use std::{path::Path, process::exit}; use actix_web::{dev::ServiceRequest, middleware, web, App, Error, HttpMessage, HttpServer}; use actix_web_grants::permissions::AttachPermissions; @@ -14,9 +14,9 @@ use ffplayout_engine::{ auth, models::LoginUser, routes::{add_user, get_playout_config, get_settings, login, patch_settings, update_user}, - utils::{init_config, run_args, Role}, + utils::{db_path, init_config, run_args, Role}, }, - utils::{init_logging, GlobalConfig}, + utils::{init_logging, PlayoutConfig}, }; async fn validator(req: ServiceRequest, credentials: BearerAuth) -> Result { @@ -35,7 +35,7 @@ async fn validator(req: ServiceRequest, credentials: BearerAuth) -> Result std::io::Result<()> { let args = Args::parse(); - let mut config = GlobalConfig::new(None); + let mut config = PlayoutConfig::new(None); config.mail.recipient = String::new(); config.logging.log_to_file = false; config.logging.timestamp = false; @@ -48,6 +48,12 @@ async fn main() -> std::io::Result<()> { } if let Some(conn) = args.listen { + if let Ok(p) = db_path() { + if !Path::new(&p).is_file() { + error!("Database is not initialized! Init DB first and add admin user."); + exit(1); + } + } init_config().await; let ip_port = conn.split(':').collect::>(); let addr = ip_port[0]; diff --git a/src/filter/a_loudnorm.rs b/src/filter/a_loudnorm.rs index efc6d3b6..57662082 100644 --- a/src/filter/a_loudnorm.rs +++ b/src/filter/a_loudnorm.rs @@ -1,9 +1,9 @@ -use crate::utils::GlobalConfig; +use crate::utils::PlayoutConfig; /// Loudnorm Audio Filter /// /// Add loudness normalization. -pub fn filter_node(config: &GlobalConfig) -> String { +pub fn filter_node(config: &PlayoutConfig) -> String { format!( "loudnorm=I={}:TP={}:LRA={}", config.processing.loud_i, config.processing.loud_tp, config.processing.loud_lra diff --git a/src/filter/ingest_filter.rs b/src/filter/ingest_filter.rs index 9838ee89..577221c7 100644 --- a/src/filter/ingest_filter.rs +++ b/src/filter/ingest_filter.rs @@ -1,10 +1,10 @@ use crate::filter::{a_loudnorm, v_overlay}; -use crate::utils::GlobalConfig; +use crate::utils::PlayoutConfig; /// Audio Filter /// /// If needed we add audio filters to the server instance. -fn audio_filter(config: &GlobalConfig) -> String { +fn audio_filter(config: &PlayoutConfig) -> String { let mut audio_chain = ";[0:a]afade=in:st=0:d=0.5".to_string(); if config.processing.loudnorm_ingest { @@ -22,7 +22,7 @@ fn audio_filter(config: &GlobalConfig) -> String { } /// Create filter nodes for ingest live stream. -pub fn filter_cmd(config: &GlobalConfig) -> Vec { +pub fn filter_cmd(config: &PlayoutConfig) -> Vec { let mut filter = format!( "[0:v]fps={},scale={}:{},setdar=dar={},fade=in:st=0:d=0.5", config.processing.fps, diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 9c99aa68..a8a2009b 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -7,7 +7,7 @@ pub mod ingest_filter; pub mod v_drawtext; pub mod v_overlay; -use crate::utils::{get_delta, is_close, GlobalConfig, Media}; +use crate::utils::{get_delta, is_close, Media, PlayoutConfig}; #[derive(Debug, Clone)] struct Filters { @@ -72,7 +72,7 @@ fn deinterlace(field_order: &Option, chain: &mut Filters) { } } -fn pad(aspect: f64, chain: &mut Filters, config: &GlobalConfig) { +fn pad(aspect: f64, chain: &mut Filters, config: &PlayoutConfig) { if !is_close(aspect, config.processing.aspect, 0.03) { chain.add_filter( &format!( @@ -84,13 +84,13 @@ fn pad(aspect: f64, chain: &mut Filters, config: &GlobalConfig) { } } -fn fps(fps: f64, chain: &mut Filters, config: &GlobalConfig) { +fn fps(fps: f64, chain: &mut Filters, config: &PlayoutConfig) { if fps != config.processing.fps { chain.add_filter(&format!("fps={}", config.processing.fps), "video") } } -fn scale(v_stream: &ffprobe::Stream, aspect: f64, chain: &mut Filters, config: &GlobalConfig) { +fn scale(v_stream: &ffprobe::Stream, aspect: f64, chain: &mut Filters, config: &PlayoutConfig) { // width: i64, height: i64 if let (Some(w), Some(h)) = (v_stream.width, v_stream.height) { if w != config.processing.width || h != config.processing.height { @@ -137,7 +137,7 @@ fn fade(node: &mut Media, chain: &mut Filters, codec_type: &str) { } } -fn overlay(node: &mut Media, chain: &mut Filters, config: &GlobalConfig) { +fn overlay(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) { if config.processing.add_logo && Path::new(&config.processing.logo).is_file() && &node.category.clone().unwrap_or_default() != "advertisement" @@ -183,7 +183,7 @@ fn extend_video(node: &mut Media, chain: &mut Filters) { } /// add drawtext filter for lower thirds messages -fn add_text(node: &mut Media, chain: &mut Filters, config: &GlobalConfig) { +fn add_text(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) { if config.text.add_text && config.text.over_pre { let filter = v_drawtext::filter_node(config, node); @@ -233,7 +233,7 @@ fn extend_audio(node: &mut Media, chain: &mut Filters) { } /// Add single pass loudnorm filter to audio line. -fn add_loudnorm(node: &mut Media, chain: &mut Filters, config: &GlobalConfig) { +fn add_loudnorm(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) { if config.processing.add_loudnorm && !node .probe @@ -247,13 +247,13 @@ fn add_loudnorm(node: &mut Media, chain: &mut Filters, config: &GlobalConfig) { } } -fn audio_volume(chain: &mut Filters, config: &GlobalConfig) { +fn audio_volume(chain: &mut Filters, config: &PlayoutConfig) { if config.processing.volume != 1.0 { chain.add_filter(&format!("volume={}", config.processing.volume), "audio") } } -fn aspect_calc(aspect_string: &Option, config: &GlobalConfig) -> f64 { +fn aspect_calc(aspect_string: &Option, config: &PlayoutConfig) -> f64 { let mut source_aspect = config.processing.aspect; if let Some(aspect) = aspect_string { @@ -276,7 +276,12 @@ fn fps_calc(r_frame_rate: &str) -> f64 { } /// This realtime filter is important for HLS output to stay in sync. -fn realtime_filter(node: &mut Media, chain: &mut Filters, config: &GlobalConfig, codec_type: &str) { +fn realtime_filter( + node: &mut Media, + chain: &mut Filters, + config: &PlayoutConfig, + codec_type: &str, +) { let mut t = ""; if codec_type == "audio" { @@ -300,7 +305,7 @@ fn realtime_filter(node: &mut Media, chain: &mut Filters, config: &GlobalConfig, } } -pub fn filter_chains(config: &GlobalConfig, node: &mut Media) -> Vec { +pub fn filter_chains(config: &PlayoutConfig, node: &mut Media) -> Vec { let mut filters = Filters::new(); if let Some(probe) = node.probe.as_ref() { diff --git a/src/filter/v_drawtext.rs b/src/filter/v_drawtext.rs index 47c6ae14..8ff6cb6e 100644 --- a/src/filter/v_drawtext.rs +++ b/src/filter/v_drawtext.rs @@ -2,9 +2,9 @@ use std::path::Path; use regex::Regex; -use crate::utils::{GlobalConfig, Media}; +use crate::utils::{Media, PlayoutConfig}; -pub fn filter_node(config: &GlobalConfig, node: &mut Media) -> String { +pub fn filter_node(config: &PlayoutConfig, node: &mut Media) -> String { let mut filter = String::new(); let mut font = String::new(); diff --git a/src/filter/v_overlay.rs b/src/filter/v_overlay.rs index 0607090c..de736a9c 100644 --- a/src/filter/v_overlay.rs +++ b/src/filter/v_overlay.rs @@ -1,11 +1,11 @@ use std::path::Path; -use crate::utils::GlobalConfig; +use crate::utils::PlayoutConfig; /// Overlay Filter /// /// When a logo is set, we create here the filter for the server. -pub fn filter_node(config: &GlobalConfig, add_tail: bool) -> String { +pub fn filter_node(config: &PlayoutConfig, add_tail: bool) -> String { let mut logo_chain = String::new(); if config.processing.add_logo && Path::new(&config.processing.logo).is_file() { diff --git a/src/input/folder.rs b/src/input/folder.rs index 9c9cf69c..7dbd68d9 100644 --- a/src/input/folder.rs +++ b/src/input/folder.rs @@ -19,14 +19,14 @@ use rand::{seq::SliceRandom, thread_rng}; use simplelog::*; use walkdir::WalkDir; -use crate::utils::{get_sec, GlobalConfig, Media}; +use crate::utils::{get_sec, Media, PlayoutConfig}; /// Folder Sources /// /// Like playlist source, we create here a folder list for iterate over it. #[derive(Debug, Clone)] pub struct FolderSource { - config: GlobalConfig, + config: PlayoutConfig, pub nodes: Arc>>, current_node: Media, index: Arc, @@ -34,7 +34,7 @@ pub struct FolderSource { impl FolderSource { pub fn new( - config: &GlobalConfig, + config: &PlayoutConfig, current_list: Arc>>, global_index: Arc, ) -> Self { @@ -163,7 +163,7 @@ fn file_extension(filename: &Path) -> Option<&str> { /// When a change is register, update the current file list. /// This makes it possible, to play infinitely and and always new files to it. pub fn watchman( - config: GlobalConfig, + config: PlayoutConfig, is_terminated: Arc, sources: Arc>>, ) { diff --git a/src/input/ingest.rs b/src/input/ingest.rs index e65b72c2..a5cd4c28 100644 --- a/src/input/ingest.rs +++ b/src/input/ingest.rs @@ -9,7 +9,7 @@ use crossbeam_channel::Sender; use simplelog::*; use crate::filter::ingest_filter::filter_cmd; -use crate::utils::{format_log_line, GlobalConfig, Ingest, ProcessControl}; +use crate::utils::{format_log_line, Ingest, PlayoutConfig, ProcessControl}; use crate::vec_strings; pub fn log_line(line: String, level: &str) { @@ -55,6 +55,10 @@ fn server_monitor( ); } + if line.contains("Address already in use") { + proc_ctl.kill_all(); + } + log_line(line, level); } @@ -65,7 +69,7 @@ fn server_monitor( /// /// Start ffmpeg in listen mode, and wait for input. pub fn ingest_server( - config: GlobalConfig, + config: PlayoutConfig, ingest_sender: Sender<(usize, [u8; 65088])>, mut proc_control: ProcessControl, ) -> Result<(), Error> { diff --git a/src/input/mod.rs b/src/input/mod.rs index a3dff4e5..9c1ffc6e 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -9,7 +9,7 @@ use std::{ use simplelog::*; -use crate::utils::{GlobalConfig, Media, PlayoutStatus}; +use crate::utils::{Media, PlayoutConfig, PlayoutStatus}; pub mod folder; pub mod ingest; @@ -21,7 +21,7 @@ pub use playlist::CurrentProgram; /// Create a source iterator from playlist, or from folder. pub fn source_generator( - config: GlobalConfig, + config: PlayoutConfig, current_list: Arc>>, index: Arc, playout_stat: PlayoutStatus, diff --git a/src/input/playlist.rs b/src/input/playlist.rs index c67e71ac..8b2b0ad9 100644 --- a/src/input/playlist.rs +++ b/src/input/playlist.rs @@ -12,7 +12,7 @@ use simplelog::*; use crate::utils::{ check_sync, gen_dummy, get_delta, get_sec, is_close, is_remote, json_serializer::read_json, - modified_time, seek_and_length, valid_source, GlobalConfig, Media, PlayoutStatus, DUMMY_LEN, + modified_time, seek_and_length, valid_source, Media, PlayoutConfig, PlayoutStatus, DUMMY_LEN, }; /// Struct for current playlist. @@ -20,7 +20,7 @@ use crate::utils::{ /// Here we prepare the init clip and build a iterator where we pull our clips. #[derive(Debug)] pub struct CurrentProgram { - config: GlobalConfig, + config: PlayoutConfig, start_sec: f64, json_mod: Option, json_path: Option, @@ -34,7 +34,7 @@ pub struct CurrentProgram { impl CurrentProgram { pub fn new( - config: &GlobalConfig, + config: &PlayoutConfig, playout_stat: PlayoutStatus, is_terminated: Arc, current_list: Arc>>, @@ -390,7 +390,7 @@ impl Iterator for CurrentProgram { /// - return clip only if we are in 24 hours time range fn timed_source( node: Media, - config: &GlobalConfig, + config: &PlayoutConfig, last: bool, playout_stat: &PlayoutStatus, ) -> Media { @@ -440,7 +440,7 @@ fn timed_source( } /// Generate the source CMD, or when clip not exist, get a dummy. -fn gen_source(config: &GlobalConfig, mut node: Media) -> Media { +fn gen_source(config: &PlayoutConfig, mut node: Media) -> Media { if valid_source(&node.source) { node.add_probe(); node.cmd = Some(seek_and_length( @@ -470,7 +470,7 @@ fn gen_source(config: &GlobalConfig, mut node: Media) -> Media { /// Handle init clip, but this clip can be the last one in playlist, /// this we have to figure out and calculate the right length. -fn handle_list_init(config: &GlobalConfig, mut node: Media) -> Media { +fn handle_list_init(config: &PlayoutConfig, mut node: Media) -> Media { debug!("Playlist init"); let (_, total_delta) = get_delta(config, &node.begin.unwrap()); let mut out = node.out; diff --git a/src/main.rs b/src/main.rs index da758623..1e7654c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,8 +14,8 @@ use ffplayout_engine::{ output::{player, write_hls}, rpc::json_rpc_server, utils::{ - generate_playlist, get_args, init_logging, send_mail, validate_ffmpeg, GlobalConfig, - PlayerControl, PlayoutStatus, ProcessControl, + generate_playlist, get_args, init_logging, send_mail, validate_ffmpeg, PlayerControl, + PlayoutConfig, PlayoutStatus, ProcessControl, }, }; @@ -57,7 +57,7 @@ fn status_file(stat_file: &str, playout_stat: &PlayoutStatus) { fn main() { let args = get_args(); - let config = GlobalConfig::new(Some(args)); + let config = PlayoutConfig::new(Some(args)); let config_clone = config.clone(); let play_control = PlayerControl::new(); let playout_stat = PlayoutStatus::new(); diff --git a/src/output/desktop.rs b/src/output/desktop.rs index 5f0925a7..ffe72f37 100644 --- a/src/output/desktop.rs +++ b/src/output/desktop.rs @@ -3,13 +3,13 @@ use std::process::{self, Command, Stdio}; use simplelog::*; use crate::filter::v_drawtext; -use crate::utils::{GlobalConfig, Media}; +use crate::utils::{Media, PlayoutConfig}; use crate::vec_strings; /// Desktop Output /// /// Instead of streaming, we run a ffplay instance and play on desktop. -pub fn output(config: &GlobalConfig, log_format: &str) -> process::Child { +pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child { let mut enc_filter: Vec = vec![]; let mut enc_cmd = vec_strings!["-hide_banner", "-nostats", "-v", log_format, "-i", "pipe:0"]; diff --git a/src/output/hls.rs b/src/output/hls.rs index e326fab0..62dce871 100644 --- a/src/output/hls.rs +++ b/src/output/hls.rs @@ -30,14 +30,14 @@ use simplelog::*; use crate::filter::ingest_filter::filter_cmd; use crate::input::{ingest::log_line, source_generator}; use crate::utils::{ - prepare_output_cmd, sec_to_time, stderr_reader, Decoder, GlobalConfig, Ingest, PlayerControl, + prepare_output_cmd, sec_to_time, stderr_reader, Decoder, Ingest, PlayerControl, PlayoutConfig, PlayoutStatus, ProcessControl, }; use crate::vec_strings; /// Ingest Server for HLS fn ingest_to_hls_server( - config: GlobalConfig, + config: PlayoutConfig, playout_stat: PlayoutStatus, mut proc_control: ProcessControl, ) -> Result<(), Error> { @@ -131,7 +131,7 @@ fn ingest_to_hls_server( /// /// Write with single ffmpeg instance directly to a HLS playlist. pub fn write_hls( - config: &GlobalConfig, + config: &PlayoutConfig, play_control: PlayerControl, playout_stat: PlayoutStatus, mut proc_control: ProcessControl, diff --git a/src/output/mod.rs b/src/output/mod.rs index f6884e6c..d2e18f68 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -17,7 +17,8 @@ pub use hls::write_hls; use crate::input::{ingest_server, source_generator}; use crate::utils::{ - sec_to_time, stderr_reader, Decoder, GlobalConfig, PlayerControl, PlayoutStatus, ProcessControl, + sec_to_time, stderr_reader, Decoder, PlayerControl, PlayoutConfig, PlayoutStatus, + ProcessControl, }; use crate::vec_strings; @@ -31,7 +32,7 @@ use crate::vec_strings; /// When a live ingest arrive, it stops the current playing and switch to the live source. /// When ingest stops, it switch back to playlist/folder mode. pub fn player( - config: &GlobalConfig, + config: &PlayoutConfig, play_control: PlayerControl, playout_stat: PlayoutStatus, mut proc_control: ProcessControl, diff --git a/src/output/stream.rs b/src/output/stream.rs index 836fdb59..a9687f81 100644 --- a/src/output/stream.rs +++ b/src/output/stream.rs @@ -3,13 +3,13 @@ use std::process::{self, Command, Stdio}; use simplelog::*; use crate::filter::v_drawtext; -use crate::utils::{prepare_output_cmd, GlobalConfig, Media}; +use crate::utils::{prepare_output_cmd, Media, PlayoutConfig}; use crate::vec_strings; /// Streaming Output /// /// Prepare the ffmpeg command for streaming output -pub fn output(config: &GlobalConfig, log_format: &str) -> process::Child { +pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child { let mut enc_cmd = vec![]; let mut enc_filter = vec![]; let mut preview_cmd = config.out.preview_cmd.as_ref().unwrap().clone(); diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index fadb1834..5d1740ad 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -9,7 +9,7 @@ use serde_json::{json, Map}; use simplelog::*; use crate::utils::{ - get_delta, get_sec, sec_to_time, write_status, GlobalConfig, Media, PlayerControl, + get_delta, get_sec, sec_to_time, write_status, Media, PlayerControl, PlayoutConfig, PlayoutStatus, ProcessControl, }; @@ -25,7 +25,7 @@ fn get_media_map(media: Media) -> Value { } /// prepare json object for response -fn get_data_map(config: &GlobalConfig, media: Media) -> Map { +fn get_data_map(config: &PlayoutConfig, media: Media) -> Map { let mut data_map = Map::new(); let begin = media.begin.unwrap_or(0.0); @@ -56,7 +56,7 @@ fn get_data_map(config: &GlobalConfig, media: Media) -> Map { /// - get last clip /// - reset player state to original clip pub fn json_rpc_server( - config: GlobalConfig, + config: PlayoutConfig, play_control: PlayerControl, playout_stat: PlayoutStatus, proc_control: ProcessControl, diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 859b2026..f629ede5 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -21,7 +21,7 @@ fn timed_kill(sec: u64, mut proc_ctl: ProcessControl) { #[test] #[ignore] fn playlist_change_at_midnight() { - let mut config = GlobalConfig::new(None); + let mut config = PlayoutConfig::new(None); config.mail.recipient = "".into(); config.processing.mode = "playlist".into(); config.playlist.day_start = "00:00:00".into(); @@ -46,7 +46,7 @@ fn playlist_change_at_midnight() { #[test] #[ignore] fn playlist_change_at_six() { - let mut config = GlobalConfig::new(None); + let mut config = PlayoutConfig::new(None); config.mail.recipient = "".into(); config.processing.mode = "playlist".into(); config.playlist.day_start = "06:00:00".into(); diff --git a/src/tests/utils/mod.rs b/src/tests/utils/mod.rs index af2766b7..f50cd743 100644 --- a/src/tests/utils/mod.rs +++ b/src/tests/utils/mod.rs @@ -39,7 +39,7 @@ fn get_date_tomorrow() { #[test] fn test_delta() { - let mut config = GlobalConfig::new(None); + let mut config = PlayoutConfig::new(None); config.mail.recipient = "".into(); config.processing.mode = "playlist".into(); config.playlist.day_start = "00:00:00".into(); diff --git a/src/utils/config.rs b/src/utils/config.rs index 8c291c24..950cb48b 100644 --- a/src/utils/config.rs +++ b/src/utils/config.rs @@ -15,7 +15,7 @@ use crate::vec_strings; /// /// This we init ones, when ffplayout is starting and use them globally in the hole program. #[derive(Debug, Serialize, Deserialize, Clone)] -pub struct GlobalConfig { +pub struct PlayoutConfig { pub general: General, pub rpc_server: RpcServer, pub mail: Mail, @@ -134,7 +134,7 @@ pub struct Out { pub output_cmd: Option>, } -impl GlobalConfig { +impl PlayoutConfig { /// Read config from YAML file, and set some extra config values. pub fn new(args: Option) -> Self { let mut config_path = PathBuf::from("/etc/ffplayout/ffplayout.yml"); @@ -161,7 +161,7 @@ impl GlobalConfig { } }; - let mut config: GlobalConfig = + let mut config: PlayoutConfig = serde_yaml::from_reader(f).expect("Could not read config file."); config.general.generate = None; config.general.stat_file = env::temp_dir() @@ -275,7 +275,7 @@ impl GlobalConfig { } } -impl Default for GlobalConfig { +impl Default for PlayoutConfig { fn default() -> Self { Self::new(None) } diff --git a/src/utils/generator.rs b/src/utils/generator.rs index b842deda..1ec9d168 100644 --- a/src/utils/generator.rs +++ b/src/utils/generator.rs @@ -17,7 +17,7 @@ use chrono::{Duration, NaiveDate}; use simplelog::*; use crate::input::FolderSource; -use crate::utils::{json_serializer::Playlist, GlobalConfig, Media}; +use crate::utils::{json_serializer::JsonPlaylist, Media, PlayoutConfig}; /// Generate a vector with dates, from given range. fn get_date_range(date_range: &[String]) -> Vec { @@ -50,7 +50,7 @@ fn get_date_range(date_range: &[String]) -> Vec { } /// Generate playlists -pub fn generate_playlist(config: &GlobalConfig, mut date_range: Vec) { +pub fn generate_playlist(config: &PlayoutConfig, mut date_range: Vec) { let total_length = config.playlist.length_sec.unwrap(); let current_list = Arc::new(Mutex::new(vec![Media::new(0, "".to_string(), false)])); let index = Arc::new(AtomicUsize::new(0)); @@ -103,7 +103,7 @@ pub fn generate_playlist(config: &GlobalConfig, mut date_range: Vec) { let mut length = 0.0; let mut round = 0; - let mut playlist = Playlist { + let mut playlist = JsonPlaylist { date, current_file: None, start_sec: None, diff --git a/src/utils/json_serializer.rs b/src/utils/json_serializer.rs index 5408c6c4..a82b530c 100644 --- a/src/utils/json_serializer.rs +++ b/src/utils/json_serializer.rs @@ -9,14 +9,14 @@ use std::{ use simplelog::*; use crate::utils::{ - get_date, is_remote, modified_time, time_from_header, validate_playlist, GlobalConfig, Media, + get_date, is_remote, modified_time, time_from_header, validate_playlist, Media, PlayoutConfig, }; pub const DUMMY_LEN: f64 = 60.0; /// This is our main playlist object, it holds all necessary information for the current day. #[derive(Debug, Serialize, Deserialize, Clone)] -pub struct Playlist { +pub struct JsonPlaylist { pub date: String, #[serde(skip_serializing, skip_deserializing)] @@ -31,7 +31,7 @@ pub struct Playlist { pub program: Vec, } -impl Playlist { +impl JsonPlaylist { fn new(date: String, start: f64) -> Self { let mut media = Media::new(0, String::new(), false); media.begin = Some(start); @@ -47,7 +47,11 @@ impl Playlist { } } -fn set_defaults(mut playlist: Playlist, current_file: String, mut start_sec: f64) -> Playlist { +fn set_defaults( + mut playlist: JsonPlaylist, + current_file: String, + mut start_sec: f64, +) -> JsonPlaylist { playlist.current_file = Some(current_file); playlist.start_sec = Some(start_sec); @@ -66,15 +70,15 @@ fn set_defaults(mut playlist: Playlist, current_file: String, mut start_sec: f64 playlist } -/// Read json playlist file, fills Playlist struct and set some extra values, +/// Read json playlist file, fills JsonPlaylist struct and set some extra values, /// which we need to process. pub fn read_json( - config: &GlobalConfig, + config: &PlayoutConfig, path: Option, is_terminated: Arc, seek: bool, next_start: f64, -) -> Playlist { +) -> JsonPlaylist { let config_clone = config.clone(); let mut playlist_path = Path::new(&config.playlist.path).to_owned(); let start_sec = config.playlist.start_sec.unwrap(); @@ -104,7 +108,7 @@ pub fn read_json( let headers = resp.headers().clone(); if let Ok(body) = resp.text() { - let mut playlist: Playlist = + let mut playlist: JsonPlaylist = serde_json::from_str(&body).expect("Could't read remote json playlist."); if let Some(time) = time_from_header(&headers) { @@ -127,7 +131,7 @@ pub fn read_json( .write(false) .open(¤t_file) .expect("Could not open json playlist file."); - let mut playlist: Playlist = + let mut playlist: JsonPlaylist = serde_json::from_reader(f).expect("Could't read json playlist file."); playlist.modified = modified_time(¤t_file); @@ -140,5 +144,5 @@ pub fn read_json( error!("Read playlist error, on: {current_file}!"); - Playlist::new(date, start_sec) + JsonPlaylist::new(date, start_sec) } diff --git a/src/utils/json_validate.rs b/src/utils/json_validate.rs index f16024bf..5a359917 100644 --- a/src/utils/json_validate.rs +++ b/src/utils/json_validate.rs @@ -5,7 +5,7 @@ use std::sync::{ use simplelog::*; -use crate::utils::{sec_to_time, valid_source, GlobalConfig, MediaProbe, Playlist}; +use crate::utils::{sec_to_time, valid_source, JsonPlaylist, MediaProbe, PlayoutConfig}; /// Validate a given playlist, to check if: /// @@ -14,7 +14,11 @@ use crate::utils::{sec_to_time, valid_source, GlobalConfig, MediaProbe, Playlist /// - total playtime fits target length from config /// /// This function we run in a thread, to don't block the main function. -pub fn validate_playlist(playlist: Playlist, is_terminated: Arc, config: GlobalConfig) { +pub fn validate_playlist( + playlist: JsonPlaylist, + is_terminated: Arc, + config: PlayoutConfig, +) { let date = playlist.date; let mut length = config.playlist.length_sec.unwrap(); let mut begin = config.playlist.start_sec.unwrap(); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 232a6b68..a73033a9 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -16,7 +16,7 @@ use serde_json::json; use simplelog::*; mod arg_parse; -mod config; +pub mod config; pub mod controller; mod generator; pub mod json_serializer; @@ -24,10 +24,10 @@ mod json_validate; mod logging; pub use arg_parse::{get_args, Args}; -pub use config::GlobalConfig; +pub use config::{self as playout_config, PlayoutConfig}; pub use controller::{PlayerControl, PlayoutStatus, ProcessControl, ProcessUnit::*}; pub use generator::generate_playlist; -pub use json_serializer::{read_json, Playlist, DUMMY_LEN}; +pub use json_serializer::{read_json, JsonPlaylist, DUMMY_LEN}; pub use json_validate::validate_playlist; pub use logging::{init_logging, send_mail}; @@ -123,7 +123,7 @@ impl Media { } } - pub fn add_filter(&mut self, config: &GlobalConfig) { + pub fn add_filter(&mut self, config: &PlayoutConfig) { let mut node = self.clone(); self.filter = Some(filter_chains(config, &mut node)) } @@ -191,7 +191,7 @@ impl MediaProbe { /// Write current status to status file in temp folder. /// /// The status file is init in main function and mostly modified in RPC server. -pub fn write_status(config: &GlobalConfig, date: &str, shift: f64) { +pub fn write_status(config: &PlayoutConfig, date: &str, shift: f64) { let data = json!({ "time_shift": shift, "date": date, @@ -308,7 +308,7 @@ pub fn is_close(a: f64, b: f64, to: f64) -> bool { /// if we still in sync. /// /// We also get here the global delta between clip start and time when a new playlist should start. -pub fn get_delta(config: &GlobalConfig, begin: &f64) -> (f64, f64) { +pub fn get_delta(config: &PlayoutConfig, begin: &f64) -> (f64, f64) { let mut current_time = get_sec(); let start = config.playlist.start_sec.unwrap(); let length = time_to_sec(&config.playlist.length); @@ -339,7 +339,7 @@ pub fn get_delta(config: &GlobalConfig, begin: &f64) -> (f64, f64) { } /// Check if clip in playlist is in sync with global time. -pub fn check_sync(config: &GlobalConfig, delta: f64) -> bool { +pub fn check_sync(config: &PlayoutConfig, delta: f64) -> bool { if delta.abs() > config.general.stop_threshold && config.general.stop_threshold > 0.0 { error!("Clip begin out of sync for {delta:.3} seconds. Stop playout!"); return false; @@ -349,7 +349,7 @@ pub fn check_sync(config: &GlobalConfig, delta: f64) -> bool { } /// Create a dummy clip as a placeholder for missing video files. -pub fn gen_dummy(config: &GlobalConfig, duration: f64) -> (String, Vec) { +pub fn gen_dummy(config: &PlayoutConfig, duration: f64) -> (String, Vec) { let color = "#121212"; let source = format!( "color=c={color}:s={}x{}:d={duration}", @@ -567,7 +567,7 @@ fn ffmpeg_libs_and_filter() -> (Vec, Vec) { /// Validate ffmpeg/ffprobe/ffplay. /// /// Check if they are in system and has all filters and codecs we need. -pub fn validate_ffmpeg(config: &GlobalConfig) { +pub fn validate_ffmpeg(config: &PlayoutConfig) { is_in_system("ffmpeg"); is_in_system("ffprobe");