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;
|
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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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];
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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() {
|
||||||
|
@ -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();
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
@ -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>>>,
|
||||||
) {
|
) {
|
||||||
|
@ -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> {
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
@ -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();
|
||||||
|
@ -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"];
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
||||||
|
@ -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,
|
||||||
|
@ -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();
|
||||||
|
@ -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();
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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(¤t_file)
|
.open(¤t_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(¤t_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>!");
|
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 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();
|
||||||
|
@ -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");
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user