rename GlobalConfig and Playlist struct, get playlist config
This commit is contained in:
parent
74f968e284
commit
b97f30c2b4
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<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> {
|
||||
let conn = db_connection().await?;
|
||||
let query = "PRAGMA foreign_keys = ON;
|
||||
|
@ -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<T> {
|
||||
message: String,
|
||||
@ -24,6 +26,37 @@ struct ResponseObj<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>"
|
||||
#[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: <TOKEN>'
|
||||
#[get("/playout/config/{id}")]
|
||||
#[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 {
|
||||
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)
|
||||
|
@ -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<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> {
|
||||
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<PlayoutConfig, Box<dyn Error>> {
|
||||
let file = File::open(path)?;
|
||||
let config: PlayoutConfig = serde_yaml::from_reader(file)?;
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
|
@ -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<ServiceRequest, Error> {
|
||||
@ -35,7 +35,7 @@ async fn validator(req: ServiceRequest, credentials: BearerAuth) -> Result<Servi
|
||||
async fn main() -> 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::<Vec<&str>>();
|
||||
let addr = ip_port[0];
|
||||
|
@ -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
|
||||
|
@ -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<String> {
|
||||
pub fn filter_cmd(config: &PlayoutConfig) -> Vec<String> {
|
||||
let mut filter = format!(
|
||||
"[0:v]fps={},scale={}:{},setdar=dar={},fade=in:st=0:d=0.5",
|
||||
config.processing.fps,
|
||||
|
@ -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<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) {
|
||||
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<String>, config: &GlobalConfig) -> f64 {
|
||||
fn aspect_calc(aspect_string: &Option<String>, 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<String> {
|
||||
pub fn filter_chains(config: &PlayoutConfig, node: &mut Media) -> Vec<String> {
|
||||
let mut filters = Filters::new();
|
||||
|
||||
if let Some(probe) = node.probe.as_ref() {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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<Mutex<Vec<Media>>>,
|
||||
current_node: Media,
|
||||
index: Arc<AtomicUsize>,
|
||||
@ -34,7 +34,7 @@ pub struct FolderSource {
|
||||
|
||||
impl FolderSource {
|
||||
pub fn new(
|
||||
config: &GlobalConfig,
|
||||
config: &PlayoutConfig,
|
||||
current_list: Arc<Mutex<Vec<Media>>>,
|
||||
global_index: Arc<AtomicUsize>,
|
||||
) -> 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<AtomicBool>,
|
||||
sources: Arc<Mutex<Vec<Media>>>,
|
||||
) {
|
||||
|
@ -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> {
|
||||
|
@ -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<Mutex<Vec<Media>>>,
|
||||
index: Arc<AtomicUsize>,
|
||||
playout_stat: PlayoutStatus,
|
||||
|
@ -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<String>,
|
||||
json_path: Option<String>,
|
||||
@ -34,7 +34,7 @@ pub struct CurrentProgram {
|
||||
|
||||
impl CurrentProgram {
|
||||
pub fn new(
|
||||
config: &GlobalConfig,
|
||||
config: &PlayoutConfig,
|
||||
playout_stat: PlayoutStatus,
|
||||
is_terminated: Arc<AtomicBool>,
|
||||
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
|
||||
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;
|
||||
|
@ -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();
|
||||
|
@ -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<String> = vec![];
|
||||
|
||||
let mut enc_cmd = vec_strings!["-hide_banner", "-nostats", "-v", log_format, "-i", "pipe:0"];
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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<String, Value> {
|
||||
fn get_data_map(config: &PlayoutConfig, media: Media) -> Map<String, Value> {
|
||||
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<String, Value> {
|
||||
/// - 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,
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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<Vec<String>>,
|
||||
}
|
||||
|
||||
impl GlobalConfig {
|
||||
impl PlayoutConfig {
|
||||
/// Read config from YAML file, and set some extra config values.
|
||||
pub fn new(args: Option<Args>) -> 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)
|
||||
}
|
||||
|
@ -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<String> {
|
||||
@ -50,7 +50,7 @@ fn get_date_range(date_range: &[String]) -> Vec<String> {
|
||||
}
|
||||
|
||||
/// 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 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<String>) {
|
||||
let mut length = 0.0;
|
||||
let mut round = 0;
|
||||
|
||||
let mut playlist = Playlist {
|
||||
let mut playlist = JsonPlaylist {
|
||||
date,
|
||||
current_file: None,
|
||||
start_sec: None,
|
||||
|
@ -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<Media>,
|
||||
}
|
||||
|
||||
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<String>,
|
||||
is_terminated: Arc<AtomicBool>,
|
||||
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: <b><magenta>{current_file}</></b>!");
|
||||
|
||||
Playlist::new(date, start_sec)
|
||||
JsonPlaylist::new(date, start_sec)
|
||||
}
|
||||
|
@ -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<AtomicBool>, config: GlobalConfig) {
|
||||
pub fn validate_playlist(
|
||||
playlist: JsonPlaylist,
|
||||
is_terminated: Arc<AtomicBool>,
|
||||
config: PlayoutConfig,
|
||||
) {
|
||||
let date = playlist.date;
|
||||
let mut length = config.playlist.length_sec.unwrap();
|
||||
let mut begin = config.playlist.start_sec.unwrap();
|
||||
|
@ -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 <yellow>{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<String>) {
|
||||
pub fn gen_dummy(config: &PlayoutConfig, duration: f64) -> (String, Vec<String>) {
|
||||
let color = "#121212";
|
||||
let source = format!(
|
||||
"color=c={color}:s={}x{}:d={duration}",
|
||||
@ -567,7 +567,7 @@ fn ffmpeg_libs_and_filter() -> (Vec<String>, Vec<String>) {
|
||||
/// 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");
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user