move storage paths to channel

This commit is contained in:
jb-alvarado 2024-08-22 11:01:02 +02:00
parent aabc2b8df5
commit 568fcab859
23 changed files with 225 additions and 125 deletions

49
Cargo.lock generated
View File

@ -3256,15 +3256,15 @@ dependencies = [
[[package]] [[package]]
name = "stacker" name = "stacker"
version = "0.1.15" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" checksum = "95a5daa25ea337c85ed954c0496e3bdd2c7308cc3b24cf7b50d04876654c579f"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
"libc", "libc",
"psm", "psm",
"winapi", "windows-sys 0.36.1",
] ]
[[package]] [[package]]
@ -4076,6 +4076,19 @@ dependencies = [
"windows-targets 0.52.6", "windows-targets 0.52.6",
] ]
[[package]]
name = "windows-sys"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
dependencies = [
"windows_aarch64_msvc 0.36.1",
"windows_i686_gnu 0.36.1",
"windows_i686_msvc 0.36.1",
"windows_x86_64_gnu 0.36.1",
"windows_x86_64_msvc 0.36.1",
]
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.48.0" version = "0.48.0"
@ -4146,6 +4159,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.5" version = "0.48.5"
@ -4158,6 +4177,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.5" version = "0.48.5"
@ -4176,6 +4201,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.5" version = "0.48.5"
@ -4188,6 +4219,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.5" version = "0.48.5"
@ -4212,6 +4249,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.5" version = "0.48.5"

View File

@ -478,10 +478,17 @@ async fn patch_channel(
role: AuthDetails<Role>, role: AuthDetails<Role>,
user: web::ReqData<UserMeta>, user: web::ReqData<UserMeta>,
) -> Result<impl Responder, ServiceError> { ) -> Result<impl Responder, ServiceError> {
if handles::update_channel(&pool, *id, data.into_inner()) let mut data = data.into_inner();
.await
.is_ok() if !role.has_authority(&Role::GlobalAdmin) {
{ let channel = handles::select_channel(&pool, &id).await?;
data.hls_path = channel.hls_path;
data.playlist_path = channel.playlist_path;
data.storage_path = channel.storage_path;
}
if handles::update_channel(&pool, *id, data).await.is_ok() {
return Ok("Update Success"); return Ok("Update Success");
}; };
@ -1022,7 +1029,7 @@ pub async fn gen_playlist(
) -> Result<impl Responder, ServiceError> { ) -> Result<impl Responder, ServiceError> {
let manager = controllers.lock().unwrap().get(params.0).unwrap(); let manager = controllers.lock().unwrap().get(params.0).unwrap();
manager.config.lock().unwrap().general.generate = Some(vec![params.1.clone()]); manager.config.lock().unwrap().general.generate = Some(vec![params.1.clone()]);
let storage_path = manager.config.lock().unwrap().global.storage_path.clone(); let storage_path = manager.config.lock().unwrap().channel.storage_path.clone();
if let Some(obj) = data { if let Some(obj) = data {
if let Some(paths) = &obj.paths { if let Some(paths) = &obj.paths {
@ -1264,7 +1271,7 @@ async fn get_file(
let id: i32 = req.match_info().query("id").parse()?; let id: i32 = req.match_info().query("id").parse()?;
let manager = controllers.lock().unwrap().get(id).unwrap(); let manager = controllers.lock().unwrap().get(id).unwrap();
let config = manager.config.lock().unwrap(); let config = manager.config.lock().unwrap();
let storage_path = config.global.storage_path.clone(); let storage_path = config.channel.storage_path.clone();
let file_path = req.match_info().query("filename"); let file_path = req.match_info().query("filename");
let (path, _, _) = norm_abs_path(&storage_path, file_path)?; let (path, _, _) = norm_abs_path(&storage_path, file_path)?;
let file = actix_files::NamedFile::open(path)?; let file = actix_files::NamedFile::open(path)?;
@ -1295,7 +1302,7 @@ async fn get_public(
let absolute_path = if file_stem.ends_with(".ts") || file_stem.ends_with(".m3u8") { let absolute_path = if file_stem.ends_with(".ts") || file_stem.ends_with(".m3u8") {
let manager = controllers.lock().unwrap().get(id).unwrap(); let manager = controllers.lock().unwrap().get(id).unwrap();
let config = manager.config.lock().unwrap(); let config = manager.config.lock().unwrap();
config.global.hls_path.join(public) config.channel.hls_path.join(public)
} else if public_path.is_absolute() { } else if public_path.is_absolute() {
public_path.to_path_buf() public_path.to_path_buf()
} else { } else {

View File

@ -40,7 +40,7 @@ pub async fn db_migrate(conn: &Pool<Sqlite>) -> Result<&'static str, Box<dyn std
} }
pub async fn select_global(conn: &Pool<Sqlite>) -> Result<GlobalSettings, sqlx::Error> { pub async fn select_global(conn: &Pool<Sqlite>) -> Result<GlobalSettings, sqlx::Error> {
let query = "SELECT id, secret, hls_path, logging_path, playlist_path, storage_path, shared_storage FROM global WHERE id = 1"; let query = "SELECT id, secret, logging_path, playlist_root, public_root, storage_root, shared_storage FROM global WHERE id = 1";
sqlx::query_as(query).fetch_one(conn).await sqlx::query_as(query).fetch_one(conn).await
} }
@ -49,14 +49,14 @@ pub async fn update_global(
conn: &Pool<Sqlite>, conn: &Pool<Sqlite>,
global: GlobalSettings, global: GlobalSettings,
) -> Result<SqliteQueryResult, sqlx::Error> { ) -> Result<SqliteQueryResult, sqlx::Error> {
let query = "UPDATE global SET hls_path = $2, playlist_path = $3, storage_path = $4, logging_path = $5, shared_storage = $6 WHERE id = 1"; let query = "UPDATE global SET logging_path = $2, playlist_root = $3, public_root = $4, storage_root = $5, shared_storage = $6 WHERE id = 1";
sqlx::query(query) sqlx::query(query)
.bind(global.id) .bind(global.id)
.bind(global.hls_path)
.bind(global.playlist_path)
.bind(global.storage_path)
.bind(global.logging_path) .bind(global.logging_path)
.bind(global.playlist_root)
.bind(global.public_root)
.bind(global.storage_root)
.bind(global.shared_storage) .bind(global.shared_storage)
.execute(conn) .execute(conn)
.await .await
@ -77,7 +77,7 @@ pub async fn select_related_channels(
) -> Result<Vec<Channel>, sqlx::Error> { ) -> Result<Vec<Channel>, sqlx::Error> {
let query = match user_id { let query = match user_id {
Some(id) => format!( Some(id) => format!(
"SELECT c.id, c.name, c.preview_url, c.extra_extensions, c.active, c.last_date, c.time_shift FROM channels c "SELECT c.id, c.name, c.preview_url, c.extra_extensions, c.active, c.hls_path, c.playlist_path, c.storage_path, c.last_date, c.time_shift FROM channels c
left join user_channels uc on uc.channel_id = c.id left join user_channels uc on uc.channel_id = c.id
left join user u on u.id = uc.user_id left join user u on u.id = uc.user_id
WHERE u.id = {id} ORDER BY c.id ASC;" WHERE u.id = {id} ORDER BY c.id ASC;"
@ -114,13 +114,16 @@ pub async fn update_channel(
channel: Channel, channel: Channel,
) -> Result<SqliteQueryResult, sqlx::Error> { ) -> Result<SqliteQueryResult, sqlx::Error> {
let query = let query =
"UPDATE channels SET name = $2, preview_url = $3, extra_extensions = $4 WHERE id = $1"; "UPDATE channels SET name = $2, preview_url = $3, extra_extensions = $4, hls_path = $5, playlist_path = $6, storage_path = $7 WHERE id = $1";
sqlx::query(query) sqlx::query(query)
.bind(id) .bind(id)
.bind(channel.name) .bind(channel.name)
.bind(channel.preview_url) .bind(channel.preview_url)
.bind(channel.extra_extensions) .bind(channel.extra_extensions)
.bind(channel.hls_path)
.bind(channel.playlist_path)
.bind(channel.storage_path)
.execute(conn) .execute(conn)
.await .await
} }
@ -152,11 +155,14 @@ pub async fn update_player(
} }
pub async fn insert_channel(conn: &Pool<Sqlite>, channel: Channel) -> Result<Channel, sqlx::Error> { pub async fn insert_channel(conn: &Pool<Sqlite>, channel: Channel) -> Result<Channel, sqlx::Error> {
let query = "INSERT INTO channels (name, preview_url, extra_extensions) VALUES($1, $2, $3)"; let query = "INSERT INTO channels (name, preview_url, extra_extensions, hls_path, playlist_path, storage_path) VALUES($1, $2, $3, $4, $5, $6)";
let result = sqlx::query(query) let result = sqlx::query(query)
.bind(channel.name) .bind(channel.name)
.bind(channel.preview_url) .bind(channel.preview_url)
.bind(channel.extra_extensions) .bind(channel.extra_extensions)
.bind(channel.hls_path)
.bind(channel.playlist_path)
.bind(channel.storage_path)
.execute(conn) .execute(conn)
.await?; .await?;

View File

@ -16,10 +16,10 @@ use crate::utils::config::PlayoutConfig;
pub struct GlobalSettings { pub struct GlobalSettings {
pub id: i32, pub id: i32,
pub secret: Option<String>, pub secret: Option<String>,
pub hls_path: String,
pub logging_path: String, pub logging_path: String,
pub playlist_path: String, pub playlist_root: String,
pub storage_path: String, pub public_root: String,
pub storage_root: String,
pub shared_storage: bool, pub shared_storage: bool,
} }
@ -32,10 +32,10 @@ impl GlobalSettings {
Err(_) => GlobalSettings { Err(_) => GlobalSettings {
id: 0, id: 0,
secret: None, secret: None,
hls_path: String::new(),
logging_path: String::new(), logging_path: String::new(),
playlist_path: String::new(), playlist_root: String::new(),
storage_path: String::new(), public_root: String::new(),
storage_root: String::new(),
shared_storage: false, shared_storage: false,
}, },
} }
@ -240,6 +240,9 @@ pub struct Channel {
pub preview_url: String, pub preview_url: String,
pub extra_extensions: String, pub extra_extensions: String,
pub active: bool, pub active: bool,
pub hls_path: String,
pub playlist_path: String,
pub storage_path: String,
pub last_date: Option<String>, pub last_date: Option<String>,
pub time_shift: f64, pub time_shift: f64,

View File

@ -263,7 +263,7 @@ async fn main() -> std::io::Result<()> {
exit(1); exit(1);
}; };
} else if ARGS.validate { } else if ARGS.validate {
let mut playlist_path = config.global.playlist_path.clone(); let mut playlist_path = config.channel.playlist_path.clone();
let start_sec = config.playlist.start_sec.unwrap(); let start_sec = config.playlist.start_sec.unwrap();
let date = get_date(false, start_sec, false); let date = get_date(false, start_sec, false);

View File

@ -337,7 +337,7 @@ pub fn start_channel(manager: ChannelManager) -> Result<(), ProcessError> {
let channel_id = config.general.channel_id; let channel_id = config.general.channel_id;
drain_hls_path( drain_hls_path(
&config.global.hls_path, &config.channel.hls_path,
&config.output.output_cmd.clone().unwrap_or_default(), &config.output.output_cmd.clone().unwrap_or_default(),
channel_id, channel_id,
)?; )?;

View File

@ -29,7 +29,7 @@ pub fn watchman(
sources: Arc<Mutex<Vec<Media>>>, sources: Arc<Mutex<Vec<Media>>>,
) { ) {
let id = config.general.channel_id; let id = config.general.channel_id;
let path = Path::new(&config.global.storage_path); let path = Path::new(&config.channel.storage_path);
if !path.exists() { if !path.exists() {
error!("Folder path not exists: '{path:?}'"); error!("Folder path not exists: '{path:?}'");

View File

@ -28,7 +28,7 @@ pub fn source_generator(manager: ChannelManager) -> Box<dyn Iterator<Item = Medi
info!(target: Target::file_mail(), channel = id; "Playout in folder mode"); info!(target: Target::file_mail(), channel = id; "Playout in folder mode");
debug!(target: Target::file_mail(), channel = id; debug!(target: Target::file_mail(), channel = id;
"Monitor folder: <b><magenta>{:?}</></b>", "Monitor folder: <b><magenta>{:?}</></b>",
config.global.storage_path config.channel.storage_path
); );
let config_clone = config.clone(); let config_clone = config.clone();

View File

@ -317,7 +317,7 @@ impl CurrentProgram {
if self.current_node.source.contains( if self.current_node.source.contains(
&self &self
.config .config
.global .channel
.storage_path .storage_path
.to_string_lossy() .to_string_lossy()
.to_string(), .to_string(),

View File

@ -40,7 +40,7 @@ impl FolderSource {
path_list.push(path) path_list.push(path)
} }
} else { } else {
path_list.push(&config.global.storage_path) path_list.push(&config.channel.storage_path)
} }
for path in &path_list { for path in &path_list {

View File

@ -28,13 +28,13 @@ pub fn import_file(
program: vec![], program: vec![],
}; };
let playlist_root = &config.global.playlist_path; let playlist_root = &config.channel.playlist_path;
if !playlist_root.is_dir() { if !playlist_root.is_dir() {
return Err(Error::new( return Err(Error::new(
ErrorKind::Other, ErrorKind::Other,
format!( format!(
"Playlist folder <b><magenta>{:?}</></b> not exists!", "Playlist folder <b><magenta>{:?}</></b> not exists!",
config.global.playlist_path, config.channel.playlist_path,
), ),
)); ));
} }

View File

@ -100,11 +100,11 @@ pub fn read_json(
) -> JsonPlaylist { ) -> JsonPlaylist {
let id = config.general.channel_id; let id = config.general.channel_id;
let config_clone = config.clone(); let config_clone = config.clone();
let mut playlist_path = config.global.playlist_path.clone(); let mut playlist_path = config.channel.playlist_path.clone();
let start_sec = config.playlist.start_sec.unwrap(); let start_sec = config.playlist.start_sec.unwrap();
let date = get_date(seek, start_sec, get_next); let date = get_date(seek, start_sec, get_next);
if playlist_path.is_dir() || is_remote(&config.global.playlist_path.to_string_lossy()) { if playlist_path.is_dir() || is_remote(&config.channel.playlist_path.to_string_lossy()) {
let d: Vec<&str> = date.split('-').collect(); let d: Vec<&str> = date.split('-').collect();
playlist_path = playlist_path playlist_path = playlist_path
.join(d[0]) .join(d[0])

View File

@ -113,16 +113,20 @@ pub struct Args {
#[clap(long, env, help = "Log to console")] #[clap(long, env, help = "Log to console")]
pub log_to_console: bool, pub log_to_console: bool,
#[clap(long, env, help = "HLS output path")] #[clap(long, env, help = "Public (HLS) output path")]
pub hls_path: Option<String>, pub public_root: Option<String>,
#[clap(long, env, help = "Playlist root path")] #[clap(long, env, help = "Playlist root path")]
pub playlist_path: Option<String>, pub playlist_root: Option<String>,
#[clap(long, env, help = "Storage root path")] #[clap(long, env, help = "Storage root path")]
pub storage_path: Option<String>, pub storage_root: Option<String>,
#[clap(long, env, help = "Share storage across channels")] #[clap(
long,
env,
help = "Share storage root across channels, important for running in Container"
)]
pub shared_storage: bool, pub shared_storage: bool,
#[clap(short, long, help = "Create admin user")] #[clap(short, long, help = "Create admin user")]
@ -216,7 +220,7 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
let mut fix_perm = String::new(); let mut fix_perm = String::new();
println!( println!(
"\nYou run the initialization as user {}. Fix permissions after initialization?", "\nYou run the initialization as user {}.\nFix permissions after initialization?\n",
user_name user_name
); );
@ -230,7 +234,7 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
fix_permission = fix_perm.trim().to_lowercase().starts_with('y'); fix_permission = fix_perm.trim().to_lowercase().starts_with('y');
if fix_permission && user_name != "root" { if fix_permission && user_name != "root" {
println!("You do not have permission to change DB file ownership! Run as proper process user or root."); println!("\nYou do not have permission to change DB file ownership!\nRun as proper process user or root.");
exit(1); exit(1);
} }
@ -246,10 +250,10 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
let mut global = GlobalSettings { let mut global = GlobalSettings {
id: 0, id: 0,
secret: None, secret: None,
hls_path: String::new(),
playlist_path: String::new(),
storage_path: String::new(),
logging_path: String::new(), logging_path: String::new(),
playlist_root: String::new(),
public_root: String::new(),
storage_root: String::new(),
shared_storage: false, shared_storage: false,
}; };
@ -265,9 +269,9 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.expect("Did not enter a correct path?"); .expect("Did not enter a correct path?");
if storage.trim().is_empty() { if storage.trim().is_empty() {
global.storage_path = "/var/lib/ffplayout/tv-media".to_string(); global.storage_root = "/var/lib/ffplayout/tv-media".to_string();
} else { } else {
global.storage_path = storage global.storage_root = storage
.trim() .trim()
.trim_matches(|c| c == '"' || c == '\'') .trim_matches(|c| c == '"' || c == '\'')
.to_string(); .to_string();
@ -281,9 +285,9 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.expect("Did not enter a correct path?"); .expect("Did not enter a correct path?");
if playlist.trim().is_empty() { if playlist.trim().is_empty() {
global.playlist_path = "/var/lib/ffplayout/playlists".to_string(); global.playlist_root = "/var/lib/ffplayout/playlists".to_string();
} else { } else {
global.playlist_path = playlist global.playlist_root = playlist
.trim() .trim()
.trim_matches(|c| c == '"' || c == '\'') .trim_matches(|c| c == '"' || c == '\'')
.to_string(); .to_string();
@ -305,7 +309,7 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.to_string(); .to_string();
} }
print!("HLS path [/usr/share/ffplayout/public]: "); print!("Public (HLS) path [/usr/share/ffplayout/public]: ");
stdout().flush().unwrap(); stdout().flush().unwrap();
stdin() stdin()
@ -313,9 +317,9 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.expect("Did not enter a correct path?"); .expect("Did not enter a correct path?");
if hls.trim().is_empty() { if hls.trim().is_empty() {
global.hls_path = "/usr/share/ffplayout/public".to_string(); global.public_root = "/usr/share/ffplayout/public".to_string();
} else { } else {
global.hls_path = hls global.public_root = hls
.trim() .trim()
.trim_matches(|c| c == '"' || c == '\'') .trim_matches(|c| c == '"' || c == '\'')
.to_string(); .to_string();
@ -335,13 +339,29 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
error_code = 1; error_code = 1;
}; };
if !global.shared_storage { let mut channel = handles::select_channel(pool, &1).await.unwrap();
let mut channel = handles::select_channel(pool, &1).await.unwrap(); channel.hls_path = global.public_root;
channel.preview_url = "http://127.0.0.1:8787/1/stream.m3u8".to_string(); channel.playlist_path = global.playlist_root;
channel.storage_path = global.storage_root;
handles::update_channel(pool, 1, channel).await.unwrap(); if global.shared_storage {
channel.preview_url = "http://127.0.0.1:8787/1/stream.m3u8".to_string();
channel.hls_path = Path::new(&channel.hls_path)
.join("1")
.to_string_lossy()
.to_string();
channel.playlist_path = Path::new(&channel.playlist_path)
.join("1")
.to_string_lossy()
.to_string();
channel.storage_path = Path::new(&channel.storage_path)
.join("1")
.to_string_lossy()
.to_string();
}; };
handles::update_channel(pool, 1, channel).await.unwrap();
if fix_permission { if fix_permission {
let db_path = Path::new(db_path().unwrap()).with_extension(""); let db_path = Path::new(db_path().unwrap()).with_extension("");
let user = process_user.unwrap(); let user = process_user.unwrap();
@ -399,9 +419,9 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
} }
if !args.init if !args.init
&& args.storage_path.is_some() && args.storage_root.is_some()
&& args.playlist_path.is_some() && args.playlist_root.is_some()
&& args.hls_path.is_some() && args.public_root.is_some()
&& args.log_path.is_some() && args.log_path.is_some()
{ {
error_code = 0; error_code = 0;
@ -409,18 +429,19 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
let global = GlobalSettings { let global = GlobalSettings {
id: 0, id: 0,
secret: None, secret: None,
hls_path: args.hls_path.unwrap(),
playlist_path: args.playlist_path.unwrap(),
storage_path: args.storage_path.unwrap(),
logging_path: args.log_path.unwrap().to_string_lossy().to_string(), logging_path: args.log_path.unwrap().to_string_lossy().to_string(),
playlist_root: args.playlist_root.unwrap(),
public_root: args.public_root.unwrap(),
storage_root: args.storage_root.unwrap(),
shared_storage: args.shared_storage, shared_storage: args.shared_storage,
}; };
if let Err(e) = handles::update_global(pool, global.clone()).await { match handles::update_global(pool, global.clone()).await {
eprintln!("{e}"); Ok(_) => println!("Update global paths..."),
error_code = 1; Err(e) => {
} else { eprintln!("{e}");
println!("Update global paths..."); error_code = 1;
}
}; };
} }

View File

@ -61,10 +61,26 @@ pub async fn create_channel(
queue: Arc<Mutex<Vec<Arc<Mutex<MailQueue>>>>>, queue: Arc<Mutex<Vec<Arc<Mutex<MailQueue>>>>>,
target_channel: Channel, target_channel: Channel,
) -> Result<Channel, ServiceError> { ) -> Result<Channel, ServiceError> {
let global = handles::select_global(conn).await?;
let mut channel = handles::insert_channel(conn, target_channel).await?; let mut channel = handles::insert_channel(conn, target_channel).await?;
channel.preview_url = preview_url(&channel.preview_url, channel.id); channel.preview_url = preview_url(&channel.preview_url, channel.id);
if global.shared_storage {
channel.hls_path = Path::new(&channel.hls_path)
.join(channel.id.to_string())
.to_string_lossy()
.to_string();
channel.playlist_path = Path::new(&channel.playlist_path)
.join(channel.id.to_string())
.to_string_lossy()
.to_string();
channel.storage_path = Path::new(&channel.storage_path)
.join(channel.id.to_string())
.to_string_lossy()
.to_string();
}
handles::update_channel(conn, channel.id, channel.clone()).await?; handles::update_channel(conn, channel.id, channel.clone()).await?;
let output_param = "-c:v libx264 -crf 23 -x264-params keyint=50:min-keyint=25:scenecut=-1 -maxrate 1300k -bufsize 2600k -preset faster -tune zerolatency -profile:v Main -level 3.1 -c:a aac -ar 44100 -b:a 128k -flags +cgop -f hls -hls_time 6 -hls_list_size 600 -hls_flags append_list+delete_segments+omit_endlist -hls_segment_filename live/stream-%d.ts live/stream.m3u8".to_string(); let output_param = "-c:v libx264 -crf 23 -x264-params keyint=50:min-keyint=25:scenecut=-1 -maxrate 1300k -bufsize 2600k -preset faster -tune zerolatency -profile:v Main -level 3.1 -c:a aac -ar 44100 -b:a 128k -flags +cgop -f hls -hls_time 6 -hls_list_size 600 -hls_flags append_list+delete_segments+omit_endlist -hls_segment_filename live/stream-%d.ts live/stream.m3u8".to_string();

View File

@ -26,12 +26,13 @@ pub const IMAGE_FORMAT: [&str; 21] = [
]; ];
// Some well known errors can be safely ignore // Some well known errors can be safely ignore
pub const FFMPEG_IGNORE_ERRORS: [&str; 12] = [ pub const FFMPEG_IGNORE_ERRORS: [&str; 13] = [
"ac-tex damaged", "ac-tex damaged",
"codec s302m, is muxed as a private data stream", "codec s302m, is muxed as a private data stream",
"corrupt decoded frame in stream", "corrupt decoded frame in stream",
"corrupt input packet in stream", "corrupt input packet in stream",
"end mismatch left", "end mismatch left",
"Invalid mb type in I-frame at",
"Packet corrupt", "Packet corrupt",
"Referenced QT chapter track not found", "Referenced QT chapter track not found",
"skipped MB in I-frame at", "skipped MB in I-frame at",
@ -151,13 +152,13 @@ pub struct Source {
pub paths: Vec<PathBuf>, pub paths: Vec<PathBuf>,
} }
/// Global Config /// Channel Config
/// ///
/// 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, Default, Clone, Deserialize, Serialize)] #[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct PlayoutConfig { pub struct PlayoutConfig {
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
pub global: Global, pub channel: Channel,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
pub advanced: AdvancedConfig, pub advanced: AdvancedConfig,
pub general: General, pub general: General,
@ -174,21 +175,21 @@ pub struct PlayoutConfig {
} }
#[derive(Debug, Default, Clone, Deserialize, Serialize)] #[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct Global { pub struct Channel {
pub logging_path: PathBuf,
pub hls_path: PathBuf, pub hls_path: PathBuf,
pub playlist_path: PathBuf, pub playlist_path: PathBuf,
pub storage_path: PathBuf, pub storage_path: PathBuf,
pub logging_path: PathBuf,
pub shared_storage: bool, pub shared_storage: bool,
} }
impl Global { impl Channel {
pub fn new(config: &models::GlobalSettings) -> Self { pub fn new(config: &models::GlobalSettings) -> Self {
Self { Self {
hls_path: PathBuf::from(config.hls_path.clone()),
playlist_path: PathBuf::from(config.playlist_path.clone()),
storage_path: PathBuf::from(config.storage_path.clone()),
logging_path: PathBuf::from(config.logging_path.clone()), logging_path: PathBuf::from(config.logging_path.clone()),
hls_path: PathBuf::from(config.public_root.clone()),
playlist_path: PathBuf::from(config.playlist_root.clone()),
storage_path: PathBuf::from(config.storage_root.clone()),
shared_storage: config.shared_storage, shared_storage: config.shared_storage,
} }
} }
@ -559,7 +560,7 @@ impl PlayoutConfig {
.await .await
.expect("Can't read advanced config"); .expect("Can't read advanced config");
let mut global = Global::new(&global); let mut channel = Channel::new(&global);
let advanced = AdvancedConfig::new(adv_config); let advanced = AdvancedConfig::new(adv_config);
let general = General::new(&config); let general = General::new(&config);
let mail = Mail::new(&config); let mail = Mail::new(&config);
@ -571,42 +572,42 @@ impl PlayoutConfig {
let task = Task::new(&config); let task = Task::new(&config);
let mut output = Output::new(&config); let mut output = Output::new(&config);
if !global.shared_storage { if global.shared_storage {
global.storage_path = global.storage_path.join(channel_id.to_string()); channel.storage_path = channel.storage_path.join(channel_id.to_string());
} }
if !global.storage_path.is_dir() { if !channel.storage_path.is_dir() {
tokio::fs::create_dir_all(&global.storage_path) tokio::fs::create_dir_all(&channel.storage_path)
.await .await
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
panic!("Can't create storage folder: {:#?}", global.storage_path) panic!("Can't create storage folder: {:#?}", channel.storage_path)
}); });
} }
let mut storage = Storage::new(&config, global.storage_path.clone()); let mut storage = Storage::new(&config, channel.storage_path.clone());
if channel_id > 1 || !global.shared_storage { if channel_id > 1 || !global.shared_storage {
global.playlist_path = global.playlist_path.join(channel_id.to_string()); channel.playlist_path = channel.playlist_path.join(channel_id.to_string());
global.hls_path = global.hls_path.join(channel_id.to_string()); channel.hls_path = channel.hls_path.join(channel_id.to_string());
} }
if !global.playlist_path.is_dir() { if !channel.playlist_path.is_dir() {
tokio::fs::create_dir_all(&global.playlist_path) tokio::fs::create_dir_all(&channel.playlist_path)
.await .await
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
panic!("Can't create playlist folder: {:#?}", global.playlist_path) panic!("Can't create playlist folder: {:#?}", channel.playlist_path)
}); });
} }
if !global.logging_path.is_dir() { if !channel.logging_path.is_dir() {
tokio::fs::create_dir_all(&global.logging_path) tokio::fs::create_dir_all(&channel.logging_path)
.await .await
.unwrap_or_else(|_| { .unwrap_or_else(|_| {
panic!("Can't create logging folder: {:#?}", global.logging_path) panic!("Can't create logging folder: {:#?}", channel.logging_path)
}); });
} }
let (filler_path, _, _) = norm_abs_path(&global.storage_path, &config.storage_filler) let (filler_path, _, _) = norm_abs_path(&channel.storage_path, &config.storage_filler)
.expect("Can't get filler path"); .expect("Can't get filler path");
storage.filler = filler_path; storage.filler = filler_path;
@ -700,7 +701,7 @@ impl PlayoutConfig {
for item in cmd.iter_mut() { for item in cmd.iter_mut() {
if item.ends_with(".ts") || (item.ends_with(".m3u8") && item != "master.m3u8") { if item.ends_with(".ts") || (item.ends_with(".m3u8") && item != "master.m3u8") {
if let Ok((hls_path, _, _)) = norm_abs_path(&global.hls_path, item) { if let Ok((hls_path, _, _)) = norm_abs_path(&channel.hls_path, item) {
let parent = hls_path.parent().expect("HLS parent path"); let parent = hls_path.parent().expect("HLS parent path");
if !parent.is_dir() { if !parent.is_dir() {
@ -728,7 +729,7 @@ impl PlayoutConfig {
} }
Self { Self {
global, channel,
advanced, advanced,
general, general,
mail, mail,
@ -749,7 +750,7 @@ impl PlayoutConfig {
&config &config
.storage .storage
.filler .filler
.strip_prefix(config.global.storage_path.clone()) .strip_prefix(config.channel.storage_path.clone())
.unwrap_or(&config.storage.filler) .unwrap_or(&config.storage.filler)
.to_path_buf(), .to_path_buf(),
); );
@ -849,11 +850,11 @@ pub async fn get_config(pool: &Pool<Sqlite>, channel_id: i32) -> Result<PlayoutC
} }
if let Some(playlist) = args.playlist { if let Some(playlist) = args.playlist {
config.global.playlist_path = playlist; config.channel.playlist_path = playlist;
} }
if let Some(folder) = args.folder { if let Some(folder) = args.folder {
config.global.storage_path = folder; config.channel.storage_path = folder;
config.processing.mode = ProcessMode::Folder; config.processing.mode = ProcessMode::Folder;
} }

View File

@ -116,12 +116,12 @@ pub async fn browser(
extensions.append(&mut channel_extensions); extensions.append(&mut channel_extensions);
let (path, parent, path_component) = let (path, parent, path_component) =
norm_abs_path(&config.global.storage_path, &path_obj.source)?; norm_abs_path(&config.channel.storage_path, &path_obj.source)?;
let parent_path = if !path_component.is_empty() { let parent_path = if !path_component.is_empty() {
path.parent().unwrap() path.parent().unwrap()
} else { } else {
&config.global.storage_path &config.channel.storage_path
}; };
let mut obj = PathObject::new(path_component, Some(parent)); let mut obj = PathObject::new(path_component, Some(parent));
@ -212,7 +212,7 @@ pub async fn create_directory(
config: &PlayoutConfig, config: &PlayoutConfig,
path_obj: &PathObject, path_obj: &PathObject,
) -> Result<HttpResponse, ServiceError> { ) -> Result<HttpResponse, ServiceError> {
let (path, _, _) = norm_abs_path(&config.global.storage_path, &path_obj.source)?; let (path, _, _) = norm_abs_path(&config.channel.storage_path, &path_obj.source)?;
if let Err(e) = fs::create_dir_all(&path).await { if let Err(e) = fs::create_dir_all(&path).await {
return Err(ServiceError::BadRequest(e.to_string())); return Err(ServiceError::BadRequest(e.to_string()));
@ -281,8 +281,8 @@ pub async fn rename_file(
config: &PlayoutConfig, config: &PlayoutConfig,
move_object: &MoveObject, move_object: &MoveObject,
) -> Result<MoveObject, ServiceError> { ) -> Result<MoveObject, ServiceError> {
let (source_path, _, _) = norm_abs_path(&config.global.storage_path, &move_object.source)?; let (source_path, _, _) = norm_abs_path(&config.channel.storage_path, &move_object.source)?;
let (mut target_path, _, _) = norm_abs_path(&config.global.storage_path, &move_object.target)?; let (mut target_path, _, _) = norm_abs_path(&config.channel.storage_path, &move_object.target)?;
if !source_path.exists() { if !source_path.exists() {
return Err(ServiceError::BadRequest("Source file not exist!".into())); return Err(ServiceError::BadRequest("Source file not exist!".into()));
@ -314,7 +314,7 @@ pub async fn remove_file_or_folder(
config: &PlayoutConfig, config: &PlayoutConfig,
source_path: &str, source_path: &str,
) -> Result<(), ServiceError> { ) -> Result<(), ServiceError> {
let (source, _, _) = norm_abs_path(&config.global.storage_path, source_path)?; let (source, _, _) = norm_abs_path(&config.channel.storage_path, source_path)?;
if !source.exists() { if !source.exists() {
return Err(ServiceError::BadRequest("Source does not exists!".into())); return Err(ServiceError::BadRequest("Source does not exists!".into()));
@ -346,7 +346,7 @@ pub async fn remove_file_or_folder(
} }
async fn valid_path(config: &PlayoutConfig, path: &str) -> Result<PathBuf, ServiceError> { async fn valid_path(config: &PlayoutConfig, path: &str) -> Result<PathBuf, ServiceError> {
let (test_path, _, _) = norm_abs_path(&config.global.storage_path, path)?; let (test_path, _, _) = norm_abs_path(&config.channel.storage_path, path)?;
if !test_path.is_dir() { if !test_path.is_dir() {
return Err(ServiceError::BadRequest("Target folder not exists!".into())); return Err(ServiceError::BadRequest("Target folder not exists!".into()));

View File

@ -215,7 +215,7 @@ pub fn playlist_generator(manager: &ChannelManager) -> Result<Vec<JsonPlaylist>,
} }
} }
}; };
let playlist_root = &config.global.playlist_path; let playlist_root = &config.channel.playlist_path;
let mut playlists = vec![]; let mut playlists = vec![];
let mut date_range = vec![]; let mut date_range = vec![];
let mut from_template = false; let mut from_template = false;
@ -224,7 +224,7 @@ pub fn playlist_generator(manager: &ChannelManager) -> Result<Vec<JsonPlaylist>,
error!( error!(
target: Target::all(), channel = id; target: Target::all(), channel = id;
"Playlist folder <b><magenta>{:?}</></b> not exists!", "Playlist folder <b><magenta>{:?}</></b> not exists!",
config.global.playlist_path config.channel.playlist_path
); );
} }

View File

@ -14,7 +14,7 @@ pub async fn read_playlist(
date: String, date: String,
) -> Result<JsonPlaylist, ServiceError> { ) -> Result<JsonPlaylist, ServiceError> {
let d: Vec<&str> = date.split('-').collect(); let d: Vec<&str> = date.split('-').collect();
let mut playlist_path = config.global.playlist_path.clone(); let mut playlist_path = config.channel.playlist_path.clone();
playlist_path = playlist_path playlist_path = playlist_path
.join(d[0]) .join(d[0])
@ -34,7 +34,7 @@ pub async fn write_playlist(
) -> Result<String, ServiceError> { ) -> Result<String, ServiceError> {
let date = json_data.date.clone(); let date = json_data.date.clone();
let d: Vec<&str> = date.split('-').collect(); let d: Vec<&str> = date.split('-').collect();
let mut playlist_path = config.global.playlist_path.clone(); let mut playlist_path = config.channel.playlist_path.clone();
if !playlist_path if !playlist_path
.extension() .extension()
@ -93,7 +93,7 @@ pub fn generate_playlist(manager: ChannelManager) -> Result<JsonPlaylist, Servic
for path in &source.paths { for path in &source.paths {
let (safe_path, _, _) = let (safe_path, _, _) =
norm_abs_path(&config.global.storage_path, &path.to_string_lossy())?; norm_abs_path(&config.channel.storage_path, &path.to_string_lossy())?;
paths.push(safe_path); paths.push(safe_path);
} }
@ -124,7 +124,7 @@ pub fn generate_playlist(manager: ChannelManager) -> Result<JsonPlaylist, Servic
pub async fn delete_playlist(config: &PlayoutConfig, date: &str) -> Result<String, ServiceError> { pub async fn delete_playlist(config: &PlayoutConfig, date: &str) -> Result<String, ServiceError> {
let d: Vec<&str> = date.split('-').collect(); let d: Vec<&str> = date.split('-').collect();
let mut playlist_path = PathBuf::from(&config.global.playlist_path); let mut playlist_path = PathBuf::from(&config.channel.playlist_path);
playlist_path = playlist_path playlist_path = playlist_path
.join(d[0]) .join(d[0])

View File

@ -118,7 +118,7 @@ pub fn stat(config: PlayoutConfig) -> SystemStat {
for disk in &*disks { for disk in &*disks {
if disk.mount_point().to_string_lossy().len() > 1 if disk.mount_point().to_string_lossy().len() > 1
&& config.global.storage_path.starts_with(disk.mount_point()) && config.channel.storage_path.starts_with(disk.mount_point())
{ {
storage.path = disk.name().to_string_lossy().to_string(); storage.path = disk.name().to_string_lossy().to_string();
storage.total = disk.total_space(); storage.total = disk.total_space();

@ -1 +1 @@
Subproject commit 86b2b00e0e121f72ab7c2cc2fb28f2a110c4c762 Subproject commit e183320e13bd972d632b841bde722fa7bbed70e5

View File

@ -5,11 +5,11 @@ CREATE TABLE
global ( global (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
secret TEXT NOT NULL, secret TEXT NOT NULL,
hls_path TEXT NOT NULL DEFAULT "/usr/share/ffplayout/public",
logging_path TEXT NOT NULL DEFAULT "/var/log/ffplayout", logging_path TEXT NOT NULL DEFAULT "/var/log/ffplayout",
playlist_path TEXT NOT NULL DEFAULT "/var/lib/ffplayout/playlists", playlist_root TEXT NOT NULL DEFAULT "/var/lib/ffplayout/playlists",
storage_path TEXT NOT NULL DEFAULT "/var/lib/ffplayout/tv-media", public_root TEXT NOT NULL DEFAULT "/usr/share/ffplayout/public",
shared_storage INTEGER NOT NULL DEFAULT 1, storage_root TEXT NOT NULL DEFAULT "/var/lib/ffplayout/tv-media",
shared_storage INTEGER NOT NULL DEFAULT 0,
UNIQUE (secret) UNIQUE (secret)
); );
@ -27,6 +27,9 @@ CREATE TABLE
preview_url TEXT NOT NULL, preview_url TEXT NOT NULL,
extra_extensions TEXT NOT NULL DEFAULT 'jpg,jpeg,png', extra_extensions TEXT NOT NULL DEFAULT 'jpg,jpeg,png',
active INTEGER NOT NULL DEFAULT 0, active INTEGER NOT NULL DEFAULT 0,
hls_path TEXT NOT NULL DEFAULT "/usr/share/ffplayout/public",
playlist_path TEXT NOT NULL DEFAULT "/var/lib/ffplayout/playlists",
storage_path TEXT NOT NULL DEFAULT "/var/lib/ffplayout/tv-media",
last_date TEXT, last_date TEXT,
time_shift REAL NOT NULL DEFAULT 0 time_shift REAL NOT NULL DEFAULT 0
); );

View File

@ -104,7 +104,7 @@ fn test_generate_playlist_from_folder() {
config.processing.mode = Playlist; config.processing.mode = Playlist;
config.storage.filler = "assets/".into(); config.storage.filler = "assets/".into();
config.playlist.length_sec = Some(86400.0); config.playlist.length_sec = Some(86400.0);
config.global.playlist_path = "assets/playlists".into(); config.channel.playlist_path = "assets/playlists".into();
let playlist = generate_playlist(manager); let playlist = generate_playlist(manager);
@ -149,7 +149,7 @@ fn test_generate_playlist_from_template() {
config.processing.mode = Playlist; config.processing.mode = Playlist;
config.storage.filler = "assets/".into(); config.storage.filler = "assets/".into();
config.playlist.length_sec = Some(86400.0); config.playlist.length_sec = Some(86400.0);
config.global.playlist_path = "assets/playlists".into(); config.channel.playlist_path = "assets/playlists".into();
let playlist = generate_playlist(manager); let playlist = generate_playlist(manager);

View File

@ -66,7 +66,7 @@ fn test_gen_source() {
config.playlist.start_sec = Some(0.0); config.playlist.start_sec = Some(0.0);
config.playlist.length = "24:00:00".into(); config.playlist.length = "24:00:00".into();
config.playlist.length_sec = Some(86400.0); config.playlist.length_sec = Some(86400.0);
config.global.playlist_path = "assets/playlists".into(); config.channel.playlist_path = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into();
let mut valid_source_with_probe = Media::new(0, "assets/media_mix/av_sync.mp4", true); let mut valid_source_with_probe = Media::new(0, "assets/media_mix/av_sync.mp4", true);
@ -113,7 +113,7 @@ fn playlist_missing() {
config.playlist.start_sec = Some(0.0); config.playlist.start_sec = Some(0.0);
config.playlist.length = "24:00:00".into(); config.playlist.length = "24:00:00".into();
config.playlist.length_sec = Some(86400.0); config.playlist.length_sec = Some(86400.0);
config.global.playlist_path = "assets/playlists".into(); config.channel.playlist_path = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into();
config.output.mode = Null; config.output.mode = Null;
config.output.output_count = 1; config.output.output_count = 1;
@ -147,7 +147,7 @@ fn playlist_next_missing() {
config.playlist.start_sec = Some(0.0); config.playlist.start_sec = Some(0.0);
config.playlist.length = "24:00:00".into(); config.playlist.length = "24:00:00".into();
config.playlist.length_sec = Some(86400.0); config.playlist.length_sec = Some(86400.0);
config.global.playlist_path = "assets/playlists".into(); config.channel.playlist_path = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into();
config.output.mode = Null; config.output.mode = Null;
config.output.output_count = 1; config.output.output_count = 1;
@ -181,7 +181,7 @@ fn playlist_to_short() {
config.playlist.start_sec = Some(21600.0); config.playlist.start_sec = Some(21600.0);
config.playlist.length = "24:00:00".into(); config.playlist.length = "24:00:00".into();
config.playlist.length_sec = Some(86400.0); config.playlist.length_sec = Some(86400.0);
config.global.playlist_path = "assets/playlists".into(); config.channel.playlist_path = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into();
config.output.mode = Null; config.output.mode = Null;
config.output.output_count = 1; config.output.output_count = 1;
@ -215,7 +215,7 @@ fn playlist_init_after_list_end() {
config.playlist.start_sec = Some(21600.0); config.playlist.start_sec = Some(21600.0);
config.playlist.length = "24:00:00".into(); config.playlist.length = "24:00:00".into();
config.playlist.length_sec = Some(86400.0); config.playlist.length_sec = Some(86400.0);
config.global.playlist_path = "assets/playlists".into(); config.channel.playlist_path = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into();
config.output.mode = Null; config.output.mode = Null;
config.output.output_count = 1; config.output.output_count = 1;
@ -249,7 +249,7 @@ fn playlist_change_at_midnight() {
config.playlist.start_sec = Some(0.0); config.playlist.start_sec = Some(0.0);
config.playlist.length = "24:00:00".into(); config.playlist.length = "24:00:00".into();
config.playlist.length_sec = Some(86400.0); config.playlist.length_sec = Some(86400.0);
config.global.playlist_path = "assets/playlists".into(); config.channel.playlist_path = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into();
config.output.mode = Null; config.output.mode = Null;
config.output.output_count = 1; config.output.output_count = 1;
@ -283,7 +283,7 @@ fn playlist_change_before_midnight() {
config.playlist.start_sec = Some(0.0); config.playlist.start_sec = Some(0.0);
config.playlist.length = "24:00:00".into(); config.playlist.length = "24:00:00".into();
config.playlist.length_sec = Some(86400.0); config.playlist.length_sec = Some(86400.0);
config.global.playlist_path = "assets/playlists".into(); config.channel.playlist_path = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into();
config.output.mode = Null; config.output.mode = Null;
config.output.output_count = 1; config.output.output_count = 1;
@ -317,7 +317,7 @@ fn playlist_change_at_six() {
config.playlist.start_sec = Some(21600.0); config.playlist.start_sec = Some(21600.0);
config.playlist.length = "24:00:00".into(); config.playlist.length = "24:00:00".into();
config.playlist.length_sec = Some(86400.0); config.playlist.length_sec = Some(86400.0);
config.global.playlist_path = "assets/playlists".into(); config.channel.playlist_path = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into(); config.storage.filler = "assets/media_filler/filler_0.mp4".into();
config.output.mode = Null; config.output.mode = Null;
config.output.output_count = 1; config.output.output_count = 1;