rename GlobalConfig and Playlist struct, get playlist config

This commit is contained in:
jb-alvarado 2022-06-14 12:09:31 +02:00
parent 74f968e284
commit b97f30c2b4
27 changed files with 200 additions and 109 deletions

View File

@ -6,8 +6,8 @@ use serde::{Deserialize, Serialize};
use crate::api::utils::GlobalSettings; use crate::api::utils::GlobalSettings;
// Token lifetime and Secret key are hardcoded for clarity // Token lifetime
const JWT_EXPIRATION_MINUTES: i64 = 60; const JWT_EXPIRATION_DAYS: i64 = 7;
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct Claims { pub struct Claims {
@ -23,7 +23,7 @@ impl Claims {
id, id,
username, username,
role, role,
exp: (Utc::now() + Duration::minutes(JWT_EXPIRATION_MINUTES)).timestamp(), exp: (Utc::now() + Duration::days(JWT_EXPIRATION_DAYS)).timestamp(),
} }
} }
} }

View File

@ -1,35 +1,23 @@
use std::path::Path;
use argon2::{ use argon2::{
password_hash::{rand_core::OsRng, SaltString}, password_hash::{rand_core::OsRng, SaltString},
Argon2, PasswordHasher, Argon2, PasswordHasher,
}; };
use faccess::PathExt;
use rand::{distributions::Alphanumeric, Rng}; use rand::{distributions::Alphanumeric, Rng};
use simplelog::*; use simplelog::*;
use sqlx::{migrate::MigrateDatabase, sqlite::SqliteQueryResult, Pool, Sqlite, SqlitePool}; use sqlx::{migrate::MigrateDatabase, sqlite::SqliteQueryResult, Pool, Sqlite, SqlitePool};
use crate::api::models::{Settings, User};
use crate::api::utils::GlobalSettings; use crate::api::utils::GlobalSettings;
use crate::api::{
models::{Settings, User},
utils::db_path,
};
#[derive(Debug, sqlx::FromRow)] #[derive(Debug, sqlx::FromRow)]
struct Role { struct Role {
name: String, name: String,
} }
pub fn db_path() -> Result<String, Box<dyn std::error::Error>> {
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<SqliteQueryResult, sqlx::Error> { async fn create_schema() -> Result<SqliteQueryResult, sqlx::Error> {
let conn = db_connection().await?; let conn = db_connection().await?;
let query = "PRAGMA foreign_keys = ON; let query = "PRAGMA foreign_keys = ON;

View File

@ -1,5 +1,5 @@
use actix_web::{get, http::StatusCode, patch, post, put, web, Responder}; 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::{ use argon2::{
password_hash::{rand_core::OsRng, PasswordHash, SaltString}, password_hash::{rand_core::OsRng, PasswordHash, SaltString},
Argon2, PasswordHasher, PasswordVerifier, 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, db_add_user, db_get_settings, db_login, db_role, db_update_settings, db_update_user,
}, },
models::{LoginUser, Settings, User}, models::{LoginUser, Settings, User},
utils::Role, utils::{read_playout_config, Role},
}; };
use crate::utils::playout_config;
#[derive(Serialize)] #[derive(Serialize)]
struct ResponseObj<T> { struct ResponseObj<T> {
message: String, message: String,
@ -24,6 +26,37 @@ struct ResponseObj<T> {
data: Option<T>, data: Option<T>,
} }
#[derive(Debug, Serialize, Clone)]
struct ResponsePlayoutConfig {
general: Option<playout_config::General>,
rpc_server: Option<playout_config::RpcServer>,
mail: Option<playout_config::Mail>,
logging: Option<playout_config::Logging>,
processing: Option<playout_config::Processing>,
ingest: Option<playout_config::Ingest>,
playlist: Option<playout_config::Playlist>,
storage: Option<playout_config::Storage>,
text: Option<playout_config::Text>,
out: Option<playout_config::Out>,
}
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 <TOKEN>" /// curl -X GET http://127.0.0.1:8080/api/settings/1 -H "Authorization: Bearer <TOKEN>"
#[get("/settings/{id}")] #[get("/settings/{id}")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")] #[has_any_role("Role::Admin", "Role::User", type = "Role")]
@ -56,13 +89,35 @@ async fn patch_settings(
Err(ServiceError::InternalServerError) Err(ServiceError::InternalServerError)
} }
/// curl -X GET http://localhost:8080/api/playout/config/1 --header 'Authorization: <TOKEN>'
#[get("/playout/config/{id}")] #[get("/playout/config/{id}")]
#[has_any_role("Role::Admin", "Role::User", type = "Role")] #[has_any_role("Role::Admin", "Role::User", type = "Role")]
async fn get_playout_config(id: web::Path<i64>) -> Result<impl Responder, ServiceError> { async fn get_playout_config(
id: web::Path<i64>,
details: AuthDetails<Role>,
) -> Result<impl Responder, ServiceError> {
if let Ok(settings) = db_get_settings(&id).await { 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) Err(ServiceError::InternalServerError)

View File

@ -1,3 +1,6 @@
use std::{error::Error, fs::File, path::Path};
use faccess::PathExt;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use simplelog::*; use simplelog::*;
@ -6,6 +9,7 @@ use crate::api::{
handles::{db_add_user, db_global, db_init}, handles::{db_add_user, db_global, db_init},
models::User, models::User,
}; };
use crate::utils::PlayoutConfig;
#[derive(PartialEq, Clone)] #[derive(PartialEq, Clone)]
pub enum Role { pub enum Role {
@ -53,6 +57,19 @@ pub async fn init_config() {
INSTANCE.set(config).unwrap(); INSTANCE.set(config).unwrap();
} }
pub fn db_path() -> Result<String, Box<dyn std::error::Error>> {
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> { pub async fn run_args(args: Args) -> Result<(), i32> {
if !args.init && args.listen.is_none() && args.username.is_none() { if !args.init && args.listen.is_none() && args.username.is_none() {
error!("Wrong number of arguments! Run ffpapi --help for more information."); 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(()) Ok(())
} }
pub fn read_playout_config(path: &str) -> Result<PlayoutConfig, Box<dyn Error>> {
let file = File::open(path)?;
let config: PlayoutConfig = serde_yaml::from_reader(file)?;
Ok(config)
}

View File

@ -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::{dev::ServiceRequest, middleware, web, App, Error, HttpMessage, HttpServer};
use actix_web_grants::permissions::AttachPermissions; use actix_web_grants::permissions::AttachPermissions;
@ -14,9 +14,9 @@ use ffplayout_engine::{
auth, auth,
models::LoginUser, models::LoginUser,
routes::{add_user, get_playout_config, get_settings, login, patch_settings, update_user}, 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<ServiceRequest, Error> { async fn validator(req: ServiceRequest, credentials: BearerAuth) -> Result<ServiceRequest, Error> {
@ -35,7 +35,7 @@ async fn validator(req: ServiceRequest, credentials: BearerAuth) -> Result<Servi
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
let args = Args::parse(); let args = Args::parse();
let mut config = GlobalConfig::new(None); let mut config = PlayoutConfig::new(None);
config.mail.recipient = String::new(); config.mail.recipient = String::new();
config.logging.log_to_file = false; config.logging.log_to_file = false;
config.logging.timestamp = false; config.logging.timestamp = false;
@ -48,6 +48,12 @@ async fn main() -> std::io::Result<()> {
} }
if let Some(conn) = args.listen { 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; init_config().await;
let ip_port = conn.split(':').collect::<Vec<&str>>(); let ip_port = conn.split(':').collect::<Vec<&str>>();
let addr = ip_port[0]; let addr = ip_port[0];

View File

@ -1,9 +1,9 @@
use crate::utils::GlobalConfig; use crate::utils::PlayoutConfig;
/// Loudnorm Audio Filter /// Loudnorm Audio Filter
/// ///
/// Add loudness normalization. /// Add loudness normalization.
pub fn filter_node(config: &GlobalConfig) -> String { pub fn filter_node(config: &PlayoutConfig) -> String {
format!( format!(
"loudnorm=I={}:TP={}:LRA={}", "loudnorm=I={}:TP={}:LRA={}",
config.processing.loud_i, config.processing.loud_tp, config.processing.loud_lra config.processing.loud_i, config.processing.loud_tp, config.processing.loud_lra

View File

@ -1,10 +1,10 @@
use crate::filter::{a_loudnorm, v_overlay}; use crate::filter::{a_loudnorm, v_overlay};
use crate::utils::GlobalConfig; use crate::utils::PlayoutConfig;
/// Audio Filter /// Audio Filter
/// ///
/// If needed we add audio filters to the server instance. /// 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(); let mut audio_chain = ";[0:a]afade=in:st=0:d=0.5".to_string();
if config.processing.loudnorm_ingest { if config.processing.loudnorm_ingest {
@ -22,7 +22,7 @@ fn audio_filter(config: &GlobalConfig) -> String {
} }
/// Create filter nodes for ingest live stream. /// Create filter nodes for ingest live stream.
pub fn filter_cmd(config: &GlobalConfig) -> Vec<String> { pub fn filter_cmd(config: &PlayoutConfig) -> Vec<String> {
let mut filter = format!( let mut filter = format!(
"[0:v]fps={},scale={}:{},setdar=dar={},fade=in:st=0:d=0.5", "[0:v]fps={},scale={}:{},setdar=dar={},fade=in:st=0:d=0.5",
config.processing.fps, config.processing.fps,

View File

@ -7,7 +7,7 @@ pub mod ingest_filter;
pub mod v_drawtext; pub mod v_drawtext;
pub mod v_overlay; 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)] #[derive(Debug, Clone)]
struct Filters { struct Filters {
@ -72,7 +72,7 @@ fn deinterlace(field_order: &Option<String>, 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) { if !is_close(aspect, config.processing.aspect, 0.03) {
chain.add_filter( chain.add_filter(
&format!( &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 { if fps != config.processing.fps {
chain.add_filter(&format!("fps={}", config.processing.fps), "video") 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 // width: i64, height: i64
if let (Some(w), Some(h)) = (v_stream.width, v_stream.height) { if let (Some(w), Some(h)) = (v_stream.width, v_stream.height) {
if w != config.processing.width || h != config.processing.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 if config.processing.add_logo
&& Path::new(&config.processing.logo).is_file() && Path::new(&config.processing.logo).is_file()
&& &node.category.clone().unwrap_or_default() != "advertisement" && &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 /// 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 { if config.text.add_text && config.text.over_pre {
let filter = v_drawtext::filter_node(config, node); 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. /// 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 if config.processing.add_loudnorm
&& !node && !node
.probe .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 { if config.processing.volume != 1.0 {
chain.add_filter(&format!("volume={}", config.processing.volume), "audio") chain.add_filter(&format!("volume={}", config.processing.volume), "audio")
} }
} }
fn aspect_calc(aspect_string: &Option<String>, config: &GlobalConfig) -> f64 { fn aspect_calc(aspect_string: &Option<String>, config: &PlayoutConfig) -> f64 {
let mut source_aspect = config.processing.aspect; let mut source_aspect = config.processing.aspect;
if let Some(aspect) = aspect_string { 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. /// 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 = ""; let mut t = "";
if codec_type == "audio" { 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<String> { pub fn filter_chains(config: &PlayoutConfig, node: &mut Media) -> Vec<String> {
let mut filters = Filters::new(); let mut filters = Filters::new();
if let Some(probe) = node.probe.as_ref() { if let Some(probe) = node.probe.as_ref() {

View File

@ -2,9 +2,9 @@ use std::path::Path;
use regex::Regex; 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 filter = String::new();
let mut font = String::new(); let mut font = String::new();

View File

@ -1,11 +1,11 @@
use std::path::Path; use std::path::Path;
use crate::utils::GlobalConfig; use crate::utils::PlayoutConfig;
/// Overlay Filter /// Overlay Filter
/// ///
/// When a logo is set, we create here the filter for the server. /// 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(); let mut logo_chain = String::new();
if config.processing.add_logo && Path::new(&config.processing.logo).is_file() { if config.processing.add_logo && Path::new(&config.processing.logo).is_file() {

View File

@ -19,14 +19,14 @@ use rand::{seq::SliceRandom, thread_rng};
use simplelog::*; use simplelog::*;
use walkdir::WalkDir; use walkdir::WalkDir;
use crate::utils::{get_sec, GlobalConfig, Media}; use crate::utils::{get_sec, Media, PlayoutConfig};
/// Folder Sources /// Folder Sources
/// ///
/// Like playlist source, we create here a folder list for iterate over it. /// Like playlist source, we create here a folder list for iterate over it.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct FolderSource { pub struct FolderSource {
config: GlobalConfig, config: PlayoutConfig,
pub nodes: Arc<Mutex<Vec<Media>>>, pub nodes: Arc<Mutex<Vec<Media>>>,
current_node: Media, current_node: Media,
index: Arc<AtomicUsize>, index: Arc<AtomicUsize>,
@ -34,7 +34,7 @@ pub struct FolderSource {
impl FolderSource { impl FolderSource {
pub fn new( pub fn new(
config: &GlobalConfig, config: &PlayoutConfig,
current_list: Arc<Mutex<Vec<Media>>>, current_list: Arc<Mutex<Vec<Media>>>,
global_index: Arc<AtomicUsize>, global_index: Arc<AtomicUsize>,
) -> Self { ) -> Self {
@ -163,7 +163,7 @@ fn file_extension(filename: &Path) -> Option<&str> {
/// When a change is register, update the current file list. /// When a change is register, update the current file list.
/// This makes it possible, to play infinitely and and always new files to it. /// This makes it possible, to play infinitely and and always new files to it.
pub fn watchman( pub fn watchman(
config: GlobalConfig, config: PlayoutConfig,
is_terminated: Arc<AtomicBool>, is_terminated: Arc<AtomicBool>,
sources: Arc<Mutex<Vec<Media>>>, sources: Arc<Mutex<Vec<Media>>>,
) { ) {

View File

@ -9,7 +9,7 @@ use crossbeam_channel::Sender;
use simplelog::*; use simplelog::*;
use crate::filter::ingest_filter::filter_cmd; 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; use crate::vec_strings;
pub fn log_line(line: String, level: &str) { 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); log_line(line, level);
} }
@ -65,7 +69,7 @@ fn server_monitor(
/// ///
/// Start ffmpeg in listen mode, and wait for input. /// Start ffmpeg in listen mode, and wait for input.
pub fn ingest_server( pub fn ingest_server(
config: GlobalConfig, config: PlayoutConfig,
ingest_sender: Sender<(usize, [u8; 65088])>, ingest_sender: Sender<(usize, [u8; 65088])>,
mut proc_control: ProcessControl, mut proc_control: ProcessControl,
) -> Result<(), Error> { ) -> Result<(), Error> {

View File

@ -9,7 +9,7 @@ use std::{
use simplelog::*; use simplelog::*;
use crate::utils::{GlobalConfig, Media, PlayoutStatus}; use crate::utils::{Media, PlayoutConfig, PlayoutStatus};
pub mod folder; pub mod folder;
pub mod ingest; pub mod ingest;
@ -21,7 +21,7 @@ pub use playlist::CurrentProgram;
/// Create a source iterator from playlist, or from folder. /// Create a source iterator from playlist, or from folder.
pub fn source_generator( pub fn source_generator(
config: GlobalConfig, config: PlayoutConfig,
current_list: Arc<Mutex<Vec<Media>>>, current_list: Arc<Mutex<Vec<Media>>>,
index: Arc<AtomicUsize>, index: Arc<AtomicUsize>,
playout_stat: PlayoutStatus, playout_stat: PlayoutStatus,

View File

@ -12,7 +12,7 @@ use simplelog::*;
use crate::utils::{ use crate::utils::{
check_sync, gen_dummy, get_delta, get_sec, is_close, is_remote, json_serializer::read_json, 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. /// 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. /// Here we prepare the init clip and build a iterator where we pull our clips.
#[derive(Debug)] #[derive(Debug)]
pub struct CurrentProgram { pub struct CurrentProgram {
config: GlobalConfig, config: PlayoutConfig,
start_sec: f64, start_sec: f64,
json_mod: Option<String>, json_mod: Option<String>,
json_path: Option<String>, json_path: Option<String>,
@ -34,7 +34,7 @@ pub struct CurrentProgram {
impl CurrentProgram { impl CurrentProgram {
pub fn new( pub fn new(
config: &GlobalConfig, config: &PlayoutConfig,
playout_stat: PlayoutStatus, playout_stat: PlayoutStatus,
is_terminated: Arc<AtomicBool>, is_terminated: Arc<AtomicBool>,
current_list: Arc<Mutex<Vec<Media>>>, current_list: Arc<Mutex<Vec<Media>>>,
@ -390,7 +390,7 @@ impl Iterator for CurrentProgram {
/// - return clip only if we are in 24 hours time range /// - return clip only if we are in 24 hours time range
fn timed_source( fn timed_source(
node: Media, node: Media,
config: &GlobalConfig, config: &PlayoutConfig,
last: bool, last: bool,
playout_stat: &PlayoutStatus, playout_stat: &PlayoutStatus,
) -> Media { ) -> Media {
@ -440,7 +440,7 @@ fn timed_source(
} }
/// Generate the source CMD, or when clip not exist, get a dummy. /// Generate the source CMD, or when clip not exist, get a dummy.
fn gen_source(config: &GlobalConfig, mut node: Media) -> Media { fn gen_source(config: &PlayoutConfig, mut node: Media) -> Media {
if valid_source(&node.source) { if valid_source(&node.source) {
node.add_probe(); node.add_probe();
node.cmd = Some(seek_and_length( node.cmd = Some(seek_and_length(
@ -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, /// Handle init clip, but this clip can be the last one in playlist,
/// this we have to figure out and calculate the right length. /// this we have to figure out and calculate the right length.
fn handle_list_init(config: &GlobalConfig, mut node: Media) -> Media { fn handle_list_init(config: &PlayoutConfig, mut node: Media) -> Media {
debug!("Playlist init"); debug!("Playlist init");
let (_, total_delta) = get_delta(config, &node.begin.unwrap()); let (_, total_delta) = get_delta(config, &node.begin.unwrap());
let mut out = node.out; let mut out = node.out;

View File

@ -14,8 +14,8 @@ use ffplayout_engine::{
output::{player, write_hls}, output::{player, write_hls},
rpc::json_rpc_server, rpc::json_rpc_server,
utils::{ utils::{
generate_playlist, get_args, init_logging, send_mail, validate_ffmpeg, GlobalConfig, generate_playlist, get_args, init_logging, send_mail, validate_ffmpeg, PlayerControl,
PlayerControl, PlayoutStatus, ProcessControl, PlayoutConfig, PlayoutStatus, ProcessControl,
}, },
}; };
@ -57,7 +57,7 @@ fn status_file(stat_file: &str, playout_stat: &PlayoutStatus) {
fn main() { fn main() {
let args = get_args(); let args = get_args();
let config = GlobalConfig::new(Some(args)); let config = PlayoutConfig::new(Some(args));
let config_clone = config.clone(); let config_clone = config.clone();
let play_control = PlayerControl::new(); let play_control = PlayerControl::new();
let playout_stat = PlayoutStatus::new(); let playout_stat = PlayoutStatus::new();

View File

@ -3,13 +3,13 @@ use std::process::{self, Command, Stdio};
use simplelog::*; use simplelog::*;
use crate::filter::v_drawtext; use crate::filter::v_drawtext;
use crate::utils::{GlobalConfig, Media}; use crate::utils::{Media, PlayoutConfig};
use crate::vec_strings; use crate::vec_strings;
/// Desktop Output /// Desktop Output
/// ///
/// Instead of streaming, we run a ffplay instance and play on desktop. /// 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<String> = vec![]; let mut enc_filter: Vec<String> = vec![];
let mut enc_cmd = vec_strings!["-hide_banner", "-nostats", "-v", log_format, "-i", "pipe:0"]; let mut enc_cmd = vec_strings!["-hide_banner", "-nostats", "-v", log_format, "-i", "pipe:0"];

View File

@ -30,14 +30,14 @@ use simplelog::*;
use crate::filter::ingest_filter::filter_cmd; use crate::filter::ingest_filter::filter_cmd;
use crate::input::{ingest::log_line, source_generator}; use crate::input::{ingest::log_line, source_generator};
use crate::utils::{ 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, PlayoutStatus, ProcessControl,
}; };
use crate::vec_strings; use crate::vec_strings;
/// Ingest Server for HLS /// Ingest Server for HLS
fn ingest_to_hls_server( fn ingest_to_hls_server(
config: GlobalConfig, config: PlayoutConfig,
playout_stat: PlayoutStatus, playout_stat: PlayoutStatus,
mut proc_control: ProcessControl, mut proc_control: ProcessControl,
) -> Result<(), Error> { ) -> Result<(), Error> {
@ -131,7 +131,7 @@ fn ingest_to_hls_server(
/// ///
/// Write with single ffmpeg instance directly to a HLS playlist. /// Write with single ffmpeg instance directly to a HLS playlist.
pub fn write_hls( pub fn write_hls(
config: &GlobalConfig, config: &PlayoutConfig,
play_control: PlayerControl, play_control: PlayerControl,
playout_stat: PlayoutStatus, playout_stat: PlayoutStatus,
mut proc_control: ProcessControl, mut proc_control: ProcessControl,

View File

@ -17,7 +17,8 @@ pub use hls::write_hls;
use crate::input::{ingest_server, source_generator}; use crate::input::{ingest_server, source_generator};
use crate::utils::{ 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; 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 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. /// When ingest stops, it switch back to playlist/folder mode.
pub fn player( pub fn player(
config: &GlobalConfig, config: &PlayoutConfig,
play_control: PlayerControl, play_control: PlayerControl,
playout_stat: PlayoutStatus, playout_stat: PlayoutStatus,
mut proc_control: ProcessControl, mut proc_control: ProcessControl,

View File

@ -3,13 +3,13 @@ use std::process::{self, Command, Stdio};
use simplelog::*; use simplelog::*;
use crate::filter::v_drawtext; 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; use crate::vec_strings;
/// Streaming Output /// Streaming Output
/// ///
/// Prepare the ffmpeg command for 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_cmd = vec![];
let mut enc_filter = vec![]; let mut enc_filter = vec![];
let mut preview_cmd = config.out.preview_cmd.as_ref().unwrap().clone(); let mut preview_cmd = config.out.preview_cmd.as_ref().unwrap().clone();

View File

@ -9,7 +9,7 @@ use serde_json::{json, Map};
use simplelog::*; use simplelog::*;
use crate::utils::{ 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, PlayoutStatus, ProcessControl,
}; };
@ -25,7 +25,7 @@ fn get_media_map(media: Media) -> Value {
} }
/// prepare json object for response /// prepare json object for response
fn get_data_map(config: &GlobalConfig, media: Media) -> Map<String, Value> { fn get_data_map(config: &PlayoutConfig, media: Media) -> Map<String, Value> {
let mut data_map = Map::new(); let mut data_map = Map::new();
let begin = media.begin.unwrap_or(0.0); let begin = media.begin.unwrap_or(0.0);
@ -56,7 +56,7 @@ fn get_data_map(config: &GlobalConfig, media: Media) -> Map<String, Value> {
/// - get last clip /// - get last clip
/// - reset player state to original clip /// - reset player state to original clip
pub fn json_rpc_server( pub fn json_rpc_server(
config: GlobalConfig, config: PlayoutConfig,
play_control: PlayerControl, play_control: PlayerControl,
playout_stat: PlayoutStatus, playout_stat: PlayoutStatus,
proc_control: ProcessControl, proc_control: ProcessControl,

View File

@ -21,7 +21,7 @@ fn timed_kill(sec: u64, mut proc_ctl: ProcessControl) {
#[test] #[test]
#[ignore] #[ignore]
fn playlist_change_at_midnight() { fn playlist_change_at_midnight() {
let mut config = GlobalConfig::new(None); let mut config = PlayoutConfig::new(None);
config.mail.recipient = "".into(); config.mail.recipient = "".into();
config.processing.mode = "playlist".into(); config.processing.mode = "playlist".into();
config.playlist.day_start = "00:00:00".into(); config.playlist.day_start = "00:00:00".into();
@ -46,7 +46,7 @@ fn playlist_change_at_midnight() {
#[test] #[test]
#[ignore] #[ignore]
fn playlist_change_at_six() { fn playlist_change_at_six() {
let mut config = GlobalConfig::new(None); let mut config = PlayoutConfig::new(None);
config.mail.recipient = "".into(); config.mail.recipient = "".into();
config.processing.mode = "playlist".into(); config.processing.mode = "playlist".into();
config.playlist.day_start = "06:00:00".into(); config.playlist.day_start = "06:00:00".into();

View File

@ -39,7 +39,7 @@ fn get_date_tomorrow() {
#[test] #[test]
fn test_delta() { fn test_delta() {
let mut config = GlobalConfig::new(None); let mut config = PlayoutConfig::new(None);
config.mail.recipient = "".into(); config.mail.recipient = "".into();
config.processing.mode = "playlist".into(); config.processing.mode = "playlist".into();
config.playlist.day_start = "00:00:00".into(); config.playlist.day_start = "00:00:00".into();

View File

@ -15,7 +15,7 @@ use crate::vec_strings;
/// ///
/// This we init ones, when ffplayout is starting and use them globally in the hole program. /// This we init ones, when ffplayout is starting and use them globally in the hole program.
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct GlobalConfig { pub struct PlayoutConfig {
pub general: General, pub general: General,
pub rpc_server: RpcServer, pub rpc_server: RpcServer,
pub mail: Mail, pub mail: Mail,
@ -134,7 +134,7 @@ pub struct Out {
pub output_cmd: Option<Vec<String>>, pub output_cmd: Option<Vec<String>>,
} }
impl GlobalConfig { impl PlayoutConfig {
/// Read config from YAML file, and set some extra config values. /// Read config from YAML file, and set some extra config values.
pub fn new(args: Option<Args>) -> Self { pub fn new(args: Option<Args>) -> Self {
let mut config_path = PathBuf::from("/etc/ffplayout/ffplayout.yml"); 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."); serde_yaml::from_reader(f).expect("Could not read config file.");
config.general.generate = None; config.general.generate = None;
config.general.stat_file = env::temp_dir() config.general.stat_file = env::temp_dir()
@ -275,7 +275,7 @@ impl GlobalConfig {
} }
} }
impl Default for GlobalConfig { impl Default for PlayoutConfig {
fn default() -> Self { fn default() -> Self {
Self::new(None) Self::new(None)
} }

View File

@ -17,7 +17,7 @@ use chrono::{Duration, NaiveDate};
use simplelog::*; use simplelog::*;
use crate::input::FolderSource; 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. /// Generate a vector with dates, from given range.
fn get_date_range(date_range: &[String]) -> Vec<String> { fn get_date_range(date_range: &[String]) -> Vec<String> {
@ -50,7 +50,7 @@ fn get_date_range(date_range: &[String]) -> Vec<String> {
} }
/// Generate playlists /// Generate playlists
pub fn generate_playlist(config: &GlobalConfig, mut date_range: Vec<String>) { pub fn generate_playlist(config: &PlayoutConfig, mut date_range: Vec<String>) {
let total_length = config.playlist.length_sec.unwrap(); let total_length = config.playlist.length_sec.unwrap();
let current_list = Arc::new(Mutex::new(vec![Media::new(0, "".to_string(), false)])); let current_list = Arc::new(Mutex::new(vec![Media::new(0, "".to_string(), false)]));
let index = Arc::new(AtomicUsize::new(0)); let index = Arc::new(AtomicUsize::new(0));
@ -103,7 +103,7 @@ pub fn generate_playlist(config: &GlobalConfig, mut date_range: Vec<String>) {
let mut length = 0.0; let mut length = 0.0;
let mut round = 0; let mut round = 0;
let mut playlist = Playlist { let mut playlist = JsonPlaylist {
date, date,
current_file: None, current_file: None,
start_sec: None, start_sec: None,

View File

@ -9,14 +9,14 @@ use std::{
use simplelog::*; use simplelog::*;
use crate::utils::{ 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; pub const DUMMY_LEN: f64 = 60.0;
/// This is our main playlist object, it holds all necessary information for the current day. /// This is our main playlist object, it holds all necessary information for the current day.
#[derive(Debug, Serialize, Deserialize, Clone)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Playlist { pub struct JsonPlaylist {
pub date: String, pub date: String,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
@ -31,7 +31,7 @@ pub struct Playlist {
pub program: Vec<Media>, pub program: Vec<Media>,
} }
impl Playlist { impl JsonPlaylist {
fn new(date: String, start: f64) -> Self { fn new(date: String, start: f64) -> Self {
let mut media = Media::new(0, String::new(), false); let mut media = Media::new(0, String::new(), false);
media.begin = Some(start); 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.current_file = Some(current_file);
playlist.start_sec = Some(start_sec); playlist.start_sec = Some(start_sec);
@ -66,15 +70,15 @@ fn set_defaults(mut playlist: Playlist, current_file: String, mut start_sec: f64
playlist 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. /// which we need to process.
pub fn read_json( pub fn read_json(
config: &GlobalConfig, config: &PlayoutConfig,
path: Option<String>, path: Option<String>,
is_terminated: Arc<AtomicBool>, is_terminated: Arc<AtomicBool>,
seek: bool, seek: bool,
next_start: f64, next_start: f64,
) -> Playlist { ) -> JsonPlaylist {
let config_clone = config.clone(); let config_clone = config.clone();
let mut playlist_path = Path::new(&config.playlist.path).to_owned(); let mut playlist_path = Path::new(&config.playlist.path).to_owned();
let start_sec = config.playlist.start_sec.unwrap(); let start_sec = config.playlist.start_sec.unwrap();
@ -104,7 +108,7 @@ pub fn read_json(
let headers = resp.headers().clone(); let headers = resp.headers().clone();
if let Ok(body) = resp.text() { 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."); serde_json::from_str(&body).expect("Could't read remote json playlist.");
if let Some(time) = time_from_header(&headers) { if let Some(time) = time_from_header(&headers) {
@ -127,7 +131,7 @@ pub fn read_json(
.write(false) .write(false)
.open(&current_file) .open(&current_file)
.expect("Could not open json playlist 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."); serde_json::from_reader(f).expect("Could't read json playlist file.");
playlist.modified = modified_time(&current_file); playlist.modified = modified_time(&current_file);
@ -140,5 +144,5 @@ pub fn read_json(
error!("Read playlist error, on: <b><magenta>{current_file}</></b>!"); error!("Read playlist error, on: <b><magenta>{current_file}</></b>!");
Playlist::new(date, start_sec) JsonPlaylist::new(date, start_sec)
} }

View File

@ -5,7 +5,7 @@ use std::sync::{
use simplelog::*; 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: /// 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 /// - total playtime fits target length from config
/// ///
/// This function we run in a thread, to don't block the main function. /// This function we run in a thread, to don't block the main function.
pub fn validate_playlist(playlist: Playlist, is_terminated: Arc<AtomicBool>, config: GlobalConfig) { pub fn validate_playlist(
playlist: JsonPlaylist,
is_terminated: Arc<AtomicBool>,
config: PlayoutConfig,
) {
let date = playlist.date; let date = playlist.date;
let mut length = config.playlist.length_sec.unwrap(); let mut length = config.playlist.length_sec.unwrap();
let mut begin = config.playlist.start_sec.unwrap(); let mut begin = config.playlist.start_sec.unwrap();

View File

@ -16,7 +16,7 @@ use serde_json::json;
use simplelog::*; use simplelog::*;
mod arg_parse; mod arg_parse;
mod config; pub mod config;
pub mod controller; pub mod controller;
mod generator; mod generator;
pub mod json_serializer; pub mod json_serializer;
@ -24,10 +24,10 @@ mod json_validate;
mod logging; mod logging;
pub use arg_parse::{get_args, Args}; 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 controller::{PlayerControl, PlayoutStatus, ProcessControl, ProcessUnit::*};
pub use generator::generate_playlist; 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 json_validate::validate_playlist;
pub use logging::{init_logging, send_mail}; 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(); let mut node = self.clone();
self.filter = Some(filter_chains(config, &mut node)) self.filter = Some(filter_chains(config, &mut node))
} }
@ -191,7 +191,7 @@ impl MediaProbe {
/// Write current status to status file in temp folder. /// Write current status to status file in temp folder.
/// ///
/// The status file is init in main function and mostly modified in RPC server. /// 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!({ let data = json!({
"time_shift": shift, "time_shift": shift,
"date": date, "date": date,
@ -308,7 +308,7 @@ pub fn is_close(a: f64, b: f64, to: f64) -> bool {
/// if we still in sync. /// if we still in sync.
/// ///
/// We also get here the global delta between clip start and time when a new playlist should start. /// 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 mut current_time = get_sec();
let start = config.playlist.start_sec.unwrap(); let start = config.playlist.start_sec.unwrap();
let length = time_to_sec(&config.playlist.length); 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. /// 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 { if delta.abs() > config.general.stop_threshold && config.general.stop_threshold > 0.0 {
error!("Clip begin out of sync for <yellow>{delta:.3}</> seconds. Stop playout!"); error!("Clip begin out of sync for <yellow>{delta:.3}</> seconds. Stop playout!");
return false; 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. /// Create a dummy clip as a placeholder for missing video files.
pub fn gen_dummy(config: &GlobalConfig, duration: f64) -> (String, Vec<String>) { pub fn gen_dummy(config: &PlayoutConfig, duration: f64) -> (String, Vec<String>) {
let color = "#121212"; let color = "#121212";
let source = format!( let source = format!(
"color=c={color}:s={}x{}:d={duration}", "color=c={color}:s={}x{}:d={duration}",
@ -567,7 +567,7 @@ fn ffmpeg_libs_and_filter() -> (Vec<String>, Vec<String>) {
/// Validate ffmpeg/ffprobe/ffplay. /// Validate ffmpeg/ffprobe/ffplay.
/// ///
/// Check if they are in system and has all filters and codecs we need. /// 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("ffmpeg");
is_in_system("ffprobe"); is_in_system("ffprobe");