Merge pull request #722 from jb-alvarado/master

move storage paths to channel
This commit is contained in:
jb-alvarado 2024-08-22 12:11:47 +02:00 committed by GitHub
commit 1e8adcc777
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 446 additions and 188 deletions

158
Cargo.lock generated
View File

@ -348,6 +348,12 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "ahash"
version = "0.8.11"
@ -528,7 +534,7 @@ dependencies = [
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"miniz_oxide 0.7.4",
"object",
"rustc-demangle",
]
@ -649,6 +655,12 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "change-detection"
version = "1.2.0"
@ -1097,7 +1109,7 @@ checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a"
[[package]]
name = "ffplayout"
version = "0.24.0-beta1"
version = "0.24.0-beta2"
dependencies = [
"actix-files",
"actix-multipart",
@ -1122,6 +1134,7 @@ dependencies = [
"lexical-sort",
"local-ip-address",
"log",
"nix",
"notify",
"notify-debouncer-full",
"num-traits",
@ -1195,12 +1208,12 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.31"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920"
checksum = "9c0596c1eac1f9e04ed902702e9878208b336edc9d6fddc8a48387349bab3666"
dependencies = [
"crc32fast",
"miniz_oxide",
"miniz_oxide 0.8.0",
]
[[package]]
@ -1818,9 +1831,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.157"
version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "libm"
@ -1960,6 +1973,15 @@ dependencies = [
"adler",
]
[[package]]
name = "miniz_oxide"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
version = "0.8.11"
@ -2010,6 +2032,18 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "nix"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
dependencies = [
"bitflags 2.6.0",
"cfg-if",
"cfg_aliases",
"libc",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -2530,9 +2564,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2"
[[package]]
name = "reqwest"
version = "0.12.5"
version = "0.12.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37"
checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63"
dependencies = [
"base64 0.22.1",
"bytes",
@ -2568,7 +2602,7 @@ dependencies = [
"wasm-bindgen-futures",
"web-sys",
"webpki-roots",
"winreg",
"windows-registry",
]
[[package]]
@ -3222,15 +3256,15 @@ dependencies = [
[[package]]
name = "stacker"
version = "0.1.15"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce"
checksum = "95a5daa25ea337c85ed954c0496e3bdd2c7308cc3b24cf7b50d04876654c579f"
dependencies = [
"cc",
"cfg-if",
"libc",
"psm",
"winapi",
"windows-sys 0.36.1",
]
[[package]]
@ -3372,6 +3406,9 @@ name = "sync_wrapper"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
dependencies = [
"futures-core",
]
[[package]]
name = "sysinfo"
@ -3402,7 +3439,7 @@ dependencies = [
[[package]]
name = "tests"
version = "0.24.0-beta1"
version = "0.24.0-beta2"
dependencies = [
"chrono",
"crossbeam-channel",
@ -3687,15 +3724,15 @@ dependencies = [
[[package]]
name = "unicode-properties"
version = "0.1.1"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291"
checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524"
[[package]]
name = "unicode-xid"
version = "0.2.4"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a"
[[package]]
name = "unicode_categories"
@ -3974,7 +4011,7 @@ checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
dependencies = [
"windows-implement",
"windows-interface",
"windows-result",
"windows-result 0.1.2",
"windows-targets 0.52.6",
]
@ -4000,6 +4037,17 @@ dependencies = [
"syn 2.0.75",
]
[[package]]
name = "windows-registry"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
dependencies = [
"windows-result 0.2.0",
"windows-strings",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.1.2"
@ -4009,6 +4057,38 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-result"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-strings"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
dependencies = [
"windows-result 0.2.0",
"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]]
name = "windows-sys"
version = "0.48.0"
@ -4079,6 +4159,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
@ -4091,6 +4177,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
@ -4109,6 +4201,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
@ -4121,6 +4219,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "windows_x86_64_gnu"
version = "0.48.5"
@ -4145,6 +4249,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "windows_x86_64_msvc"
version = "0.48.5"
@ -4166,16 +4276,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "zerocopy"
version = "0.7.35"

View File

@ -4,7 +4,7 @@ default-members = ["ffplayout", "tests"]
resolver = "2"
[workspace.package]
version = "0.24.0-beta1"
version = "0.24.0-beta2"
license = "GPL-3.0"
repository = "https://github.com/ffplayout/ffplayout"
authors = ["Jonathan Baecker <jonbae77@gmail.com>"]

View File

@ -9,13 +9,13 @@ ffplayout provides ***.deb** and ***.rpm** packages, which makes it more easy to
- `systemctl enable ffplayout`
5. initial defaults and add global admin user:
- `sudo -u ffpu ffplayout -i`
6. use a revers proxy for SSL, Port is **8787**.
7. login with your browser, address without proxy would be: **http://[IP ADDRESS]:8787**
6. start ffplayout:
- `systemctl start ffplayout`
7. use a revers proxy for SSL, Port is **8787**.
8. login with your browser, address without proxy would be: **http://[IP ADDRESS]:8787**
Default location for playlists and media files are: **/var/lib/ffplayout/**.
When playlists are created and the ffplayout output is configured, you can start the process: `systemctl start ffplayout`, or click start in frontend.
### Manual Install
-----

View File

@ -24,7 +24,7 @@ argon2 = "0.5"
chrono = { version = "0.4", default-features = false, features = ["clock", "std", "serde"] }
clap = { version = "4.3", features = ["derive", "env"] }
crossbeam-channel = "0.5"
derive_more = "1"
derive_more = { version = "1", features = ["display"] }
faccess = "0.2"
ffprobe = "0.4"
flexi_logger = { version = "0.28", features = ["kv", "colors"] }
@ -36,6 +36,7 @@ lettre = { version = "0.11", features = ["builder", "rustls-tls", "smtp-transpor
lexical-sort = "0.3"
local-ip-address = "0.6"
log = { version = "0.4", features = ["std", "serde", "kv", "kv_std", "kv_sval", "kv_serde"] }
nix = { version = "0.29", features = ["user", "fs"] }
notify = "6.0"
notify-debouncer-full = { version = "*", default-features = false }
num-traits = "0.2"

View File

@ -309,6 +309,7 @@ async fn update_user(
role: AuthDetails<Role>,
user: web::ReqData<UserMeta>,
) -> Result<impl Responder, ServiceError> {
let channel_ids = data.channel_ids.clone().unwrap_or_default();
let mut fields = String::new();
if let Some(mail) = data.mail.clone() {
@ -319,21 +320,6 @@ async fn update_user(
fields.push_str(&format!("mail = '{mail}'"));
}
if let Some(channel_ids) = &data.channel_ids {
if !fields.is_empty() {
fields.push_str(", ");
}
fields.push_str(&format!(
"channel_ids = '{}'",
channel_ids
.iter()
.map(|i| i.to_string())
.collect::<Vec<String>>()
.join(",")
));
}
if !data.password.is_empty() {
if !fields.is_empty() {
fields.push_str(", ");
@ -354,11 +340,19 @@ async fn update_user(
fields.push_str(&format!("password = '{password_hash}'"));
}
if handles::update_user(&pool, *id, fields).await.is_ok() {
return Ok("Update Success");
};
handles::update_user(&pool, *id, fields).await?;
Err(ServiceError::InternalServerError)
let related_channels = handles::select_related_channels(&pool, Some(*id)).await?;
for channel in related_channels {
if !channel_ids.contains(&channel.id) {
handles::delete_user_channel(&pool, *id, channel.id).await?;
}
}
handles::insert_user_channel(&pool, *id, channel_ids).await?;
Ok("Update Success")
}
/// **Add User**
@ -484,10 +478,17 @@ async fn patch_channel(
role: AuthDetails<Role>,
user: web::ReqData<UserMeta>,
) -> Result<impl Responder, ServiceError> {
if handles::update_channel(&pool, *id, data.into_inner())
.await
.is_ok()
{
let mut data = data.into_inner();
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");
};
@ -1028,7 +1029,7 @@ pub async fn gen_playlist(
) -> Result<impl Responder, ServiceError> {
let manager = controllers.lock().unwrap().get(params.0).unwrap();
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(paths) = &obj.paths {
@ -1270,7 +1271,7 @@ async fn get_file(
let id: i32 = req.match_info().query("id").parse()?;
let manager = controllers.lock().unwrap().get(id).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 (path, _, _) = norm_abs_path(&storage_path, file_path)?;
let file = actix_files::NamedFile::open(path)?;
@ -1301,7 +1302,7 @@ async fn get_public(
let absolute_path = if file_stem.ends_with(".ts") || file_stem.ends_with(".m3u8") {
let manager = controllers.lock().unwrap().get(id).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() {
public_path.to_path_buf()
} 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> {
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
}
@ -49,14 +49,14 @@ pub async fn update_global(
conn: &Pool<Sqlite>,
global: GlobalSettings,
) -> 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)
.bind(global.id)
.bind(global.hls_path)
.bind(global.playlist_path)
.bind(global.storage_path)
.bind(global.logging_path)
.bind(global.playlist_root)
.bind(global.public_root)
.bind(global.storage_root)
.bind(global.shared_storage)
.execute(conn)
.await
@ -77,7 +77,7 @@ pub async fn select_related_channels(
) -> Result<Vec<Channel>, sqlx::Error> {
let query = match user_id {
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 u on u.id = uc.user_id
WHERE u.id = {id} ORDER BY c.id ASC;"
@ -94,19 +94,36 @@ pub async fn select_related_channels(
Ok(results)
}
pub async fn delete_user_channel(
conn: &Pool<Sqlite>,
user_id: i32,
channel_id: i32,
) -> Result<SqliteQueryResult, sqlx::Error> {
let query = "DELETE FROM user_channels WHERE user_id = $1 AND channel_id = $2";
sqlx::query(query)
.bind(user_id)
.bind(channel_id)
.execute(conn)
.await
}
pub async fn update_channel(
conn: &Pool<Sqlite>,
id: i32,
channel: Channel,
) -> Result<SqliteQueryResult, sqlx::Error> {
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)
.bind(id)
.bind(channel.name)
.bind(channel.preview_url)
.bind(channel.extra_extensions)
.bind(channel.hls_path)
.bind(channel.playlist_path)
.bind(channel.storage_path)
.execute(conn)
.await
}
@ -138,11 +155,14 @@ pub async fn update_player(
}
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)
.bind(channel.name)
.bind(channel.preview_url)
.bind(channel.extra_extensions)
.bind(channel.hls_path)
.bind(channel.playlist_path)
.bind(channel.storage_path)
.execute(conn)
.await?;

View File

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

View File

@ -2,7 +2,7 @@ use std::{
collections::HashSet,
env,
fs::File,
io,
io::{self, stdin, stdout, Write},
process::exit,
sync::{atomic::AtomicBool, Arc, Mutex},
thread,
@ -14,6 +14,7 @@ use actix_web::{
};
use actix_web_grants::authorities::AttachAuthorities;
use actix_web_httpauth::{extractors::bearer::BearerAuth, middleware::HttpAuthentication};
use sqlx::{migrate::MigrateDatabase, Sqlite};
#[cfg(all(not(debug_assertions), feature = "embed_frontend"))]
use actix_web_static_files::ResourceFiles;
@ -35,6 +36,7 @@ use ffplayout::{
utils::{
args_parse::run_args,
config::get_config,
db_path,
logging::{init_logging, MailQueue},
playlist::generate_playlist,
},
@ -263,7 +265,7 @@ async fn main() -> std::io::Result<()> {
exit(1);
};
} 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 date = get_date(false, start_sec, false);
@ -289,7 +291,25 @@ async fn main() -> std::io::Result<()> {
playlist,
Arc::new(AtomicBool::new(false)),
);
} else {
} else if ARGS.drop_db {
let mut drop_answer = String::new();
print!("Drop Database [Y/n]: ");
stdout().flush().unwrap();
stdin()
.read_line(&mut drop_answer)
.expect("Did not enter a yes or no?");
let drop = drop_answer.trim().to_lowercase().starts_with('y');
if drop {
match Sqlite::drop_database(db_path().unwrap()).await {
Ok(_) => info!("Successfully dropped DB"),
Err(e) => error!("{e}"),
};
}
} else if !ARGS.init {
error!("Run ffplayout with parameters! Run ffplayout -h for more information.");
}
}

View File

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

View File

@ -29,7 +29,7 @@ pub fn watchman(
sources: Arc<Mutex<Vec<Media>>>,
) {
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() {
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");
debug!(target: Target::file_mail(), channel = id;
"Monitor folder: <b><magenta>{:?}</></b>",
config.global.storage_path
config.channel.storage_path
);
let config_clone = config.clone();

View File

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

View File

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

View File

@ -28,13 +28,13 @@ pub fn import_file(
program: vec![],
};
let playlist_root = &config.global.playlist_path;
let playlist_root = &config.channel.playlist_path;
if !playlist_root.is_dir() {
return Err(Error::new(
ErrorKind::Other,
format!(
"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 {
let id = config.general.channel_id;
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 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();
playlist_path = playlist_path
.join(d[0])

View File

@ -1,8 +1,11 @@
use std::{
io::{stdin, stdout, Write},
path::PathBuf,
path::{Path, PathBuf},
};
#[cfg(target_family = "unix")]
use std::{fs, process::exit};
use clap::Parser;
use rpassword::read_password;
use sqlx::{Pool, Sqlite};
@ -17,6 +20,9 @@ use crate::utils::{
};
use crate::ARGS;
#[cfg(target_family = "unix")]
use crate::utils::db_path;
#[derive(Parser, Debug, Clone)]
#[clap(version,
about = "ffplayout - 24/7 broadcasting solution",
@ -32,9 +38,16 @@ pub struct Args {
#[clap(short, long, help = "Add a global admin user")]
pub add: bool,
#[clap(long, env, help = "path to database file")]
#[clap(long, env, help = "Path to database file")]
pub db: Option<PathBuf>,
#[clap(
long,
env,
help = "Drop database. WARNING: this will delete all configurations!"
)]
pub drop_db: bool,
#[clap(
short,
long,
@ -110,16 +123,20 @@ pub struct Args {
#[clap(long, env, help = "Log to console")]
pub log_to_console: bool,
#[clap(long, env, help = "HLS output path")]
pub hls_path: Option<String>,
#[clap(long, env, help = "Public (HLS) output path")]
pub public_root: Option<String>,
#[clap(long, env, help = "Playlist root path")]
pub playlist_path: Option<String>,
pub playlist_root: Option<String>,
#[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,
#[clap(short, long, help = "Create admin user")]
@ -202,6 +219,43 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
let mut error_code = -1;
if args.init {
#[cfg(target_family = "unix")]
let process_user = nix::unistd::User::from_name("ffpu").unwrap_or_default();
#[cfg(target_family = "unix")]
let mut fix_permission = false;
#[cfg(target_family = "unix")]
{
let uid = nix::unistd::Uid::current();
let current_user = nix::unistd::User::from_uid(uid).unwrap_or_default();
if current_user != process_user {
let user_name = current_user.unwrap().name;
let mut fix_perm = String::new();
println!(
"\nYou run the initialization as user {}.\nFix permissions after initialization?\n",
user_name
);
print!("Fix permission [Y/n]: ");
stdout().flush().unwrap();
stdin()
.read_line(&mut fix_perm)
.expect("Did not enter a yes or no?");
fix_permission = fix_perm.trim().to_lowercase().starts_with('y');
if fix_permission && user_name != "root" {
println!("\nYou do not have permission to change DB file ownership!\nRun as proper process user or root.");
exit(1);
}
}
}
let check_user = handles::select_users(pool).await;
let mut storage = String::new();
@ -212,10 +266,10 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
let mut global = GlobalSettings {
id: 0,
secret: None,
hls_path: String::new(),
playlist_path: String::new(),
storage_path: String::new(),
logging_path: String::new(),
playlist_root: String::new(),
public_root: String::new(),
storage_root: String::new(),
shared_storage: false,
};
@ -231,9 +285,9 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.expect("Did not enter a correct path?");
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 {
global.storage_path = storage
global.storage_root = storage
.trim()
.trim_matches(|c| c == '"' || c == '\'')
.to_string();
@ -247,9 +301,9 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.expect("Did not enter a correct path?");
if playlist.trim().is_empty() {
global.playlist_path = "/var/lib/ffplayout/playlists".to_string();
global.playlist_root = "/var/lib/ffplayout/playlists".to_string();
} else {
global.playlist_path = playlist
global.playlist_root = playlist
.trim()
.trim_matches(|c| c == '"' || c == '\'')
.to_string();
@ -271,7 +325,7 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.to_string();
}
print!("HLS path [/usr/share/ffplayout/public]: ");
print!("Public (HLS) path [/usr/share/ffplayout/public]: ");
stdout().flush().unwrap();
stdin()
@ -279,9 +333,9 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.expect("Did not enter a correct path?");
if hls.trim().is_empty() {
global.hls_path = "/usr/share/ffplayout/public".to_string();
global.public_root = "/usr/share/ffplayout/public".to_string();
} else {
global.hls_path = hls
global.public_root = hls
.trim()
.trim_matches(|c| c == '"' || c == '\'')
.to_string();
@ -292,7 +346,7 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
stdin()
.read_line(&mut shared_store)
.expect("Did not enter a correct path?");
.expect("Did not enter a yes or no?");
global.shared_storage = shared_store.trim().to_lowercase().starts_with('y');
@ -301,14 +355,52 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
error_code = 1;
};
if !global.shared_storage {
let mut channel = handles::select_channel(pool, &1).await.unwrap();
channel.preview_url = "http://127.0.0.1:8787/1/stream.m3u8".to_string();
let mut channel = handles::select_channel(pool, &1).await.unwrap();
channel.hls_path = global.public_root;
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();
};
println!("Set global settings...");
handles::update_channel(pool, 1, channel).await.unwrap();
#[cfg(target_family = "unix")]
if fix_permission {
let db_path = Path::new(db_path().unwrap()).with_extension("");
let user = process_user.unwrap();
let db = fs::canonicalize(db_path.with_extension("db")).unwrap();
let shm = fs::canonicalize(db_path.with_extension("db-shm")).unwrap();
let wal = fs::canonicalize(db_path.with_extension("db-wal")).unwrap();
nix::unistd::chown(&db, Some(user.uid), Some(user.gid)).expect("Change DB owner");
if shm.is_file() {
nix::unistd::chown(&shm, Some(user.uid), Some(user.gid))
.expect("Change DB-SHM owner");
}
if wal.is_file() {
nix::unistd::chown(&wal, Some(user.uid), Some(user.gid))
.expect("Change DB-WAL owner");
}
}
println!("\nSet global settings done...");
}
if args.add {
@ -344,9 +436,9 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
}
if !args.init
&& args.storage_path.is_some()
&& args.playlist_path.is_some()
&& args.hls_path.is_some()
&& args.storage_root.is_some()
&& args.playlist_root.is_some()
&& args.public_root.is_some()
&& args.log_path.is_some()
{
error_code = 0;
@ -354,18 +446,19 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
let global = GlobalSettings {
id: 0,
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(),
playlist_root: args.playlist_root.unwrap(),
public_root: args.public_root.unwrap(),
storage_root: args.storage_root.unwrap(),
shared_storage: args.shared_storage,
};
if let Err(e) = handles::update_global(pool, global.clone()).await {
eprintln!("{e}");
error_code = 1;
} else {
println!("Update global paths...");
match handles::update_global(pool, global.clone()).await {
Ok(_) => println!("Update global paths..."),
Err(e) => {
eprintln!("{e}");
error_code = 1;
}
};
}

View File

@ -61,10 +61,26 @@ pub async fn create_channel(
queue: Arc<Mutex<Vec<Arc<Mutex<MailQueue>>>>>,
target_channel: Channel,
) -> Result<Channel, ServiceError> {
let global = handles::select_global(conn).await?;
let mut channel = handles::insert_channel(conn, target_channel).await?;
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?;
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
pub const FFMPEG_IGNORE_ERRORS: [&str; 12] = [
pub const FFMPEG_IGNORE_ERRORS: [&str; 13] = [
"ac-tex damaged",
"codec s302m, is muxed as a private data stream",
"corrupt decoded frame in stream",
"corrupt input packet in stream",
"end mismatch left",
"Invalid mb type in I-frame at",
"Packet corrupt",
"Referenced QT chapter track not found",
"skipped MB in I-frame at",
@ -151,13 +152,13 @@ pub struct Source {
pub paths: Vec<PathBuf>,
}
/// Global Config
/// Channel Config
///
/// This we init ones, when ffplayout is starting and use them globally in the hole program.
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct PlayoutConfig {
#[serde(skip_serializing, skip_deserializing)]
pub global: Global,
pub channel: Channel,
#[serde(skip_serializing, skip_deserializing)]
pub advanced: AdvancedConfig,
pub general: General,
@ -174,21 +175,21 @@ pub struct PlayoutConfig {
}
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct Global {
pub struct Channel {
pub logging_path: PathBuf,
pub hls_path: PathBuf,
pub playlist_path: PathBuf,
pub storage_path: PathBuf,
pub logging_path: PathBuf,
pub shared_storage: bool,
}
impl Global {
impl Channel {
pub fn new(config: &models::GlobalSettings) -> 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()),
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,
}
}
@ -559,7 +560,7 @@ impl PlayoutConfig {
.await
.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 general = General::new(&config);
let mail = Mail::new(&config);
@ -571,42 +572,42 @@ impl PlayoutConfig {
let task = Task::new(&config);
let mut output = Output::new(&config);
if !global.shared_storage {
global.storage_path = global.storage_path.join(channel_id.to_string());
if global.shared_storage {
channel.storage_path = channel.storage_path.join(channel_id.to_string());
}
if !global.storage_path.is_dir() {
tokio::fs::create_dir_all(&global.storage_path)
if !channel.storage_path.is_dir() {
tokio::fs::create_dir_all(&channel.storage_path)
.await
.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 {
global.playlist_path = global.playlist_path.join(channel_id.to_string());
global.hls_path = global.hls_path.join(channel_id.to_string());
channel.playlist_path = channel.playlist_path.join(channel_id.to_string());
channel.hls_path = channel.hls_path.join(channel_id.to_string());
}
if !global.playlist_path.is_dir() {
tokio::fs::create_dir_all(&global.playlist_path)
if !channel.playlist_path.is_dir() {
tokio::fs::create_dir_all(&channel.playlist_path)
.await
.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() {
tokio::fs::create_dir_all(&global.logging_path)
if !channel.logging_path.is_dir() {
tokio::fs::create_dir_all(&channel.logging_path)
.await
.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");
storage.filler = filler_path;
@ -700,7 +701,7 @@ impl PlayoutConfig {
for item in cmd.iter_mut() {
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");
if !parent.is_dir() {
@ -728,7 +729,7 @@ impl PlayoutConfig {
}
Self {
global,
channel,
advanced,
general,
mail,
@ -749,7 +750,7 @@ impl PlayoutConfig {
&config
.storage
.filler
.strip_prefix(config.global.storage_path.clone())
.strip_prefix(config.channel.storage_path.clone())
.unwrap_or(&config.storage.filler)
.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 {
config.global.playlist_path = playlist;
config.channel.playlist_path = playlist;
}
if let Some(folder) = args.folder {
config.global.storage_path = folder;
config.channel.storage_path = folder;
config.processing.mode = ProcessMode::Folder;
}

View File

@ -116,12 +116,12 @@ pub async fn browser(
extensions.append(&mut channel_extensions);
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() {
path.parent().unwrap()
} else {
&config.global.storage_path
&config.channel.storage_path
};
let mut obj = PathObject::new(path_component, Some(parent));
@ -212,7 +212,7 @@ pub async fn create_directory(
config: &PlayoutConfig,
path_obj: &PathObject,
) -> 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 {
return Err(ServiceError::BadRequest(e.to_string()));
@ -281,8 +281,8 @@ pub async fn rename_file(
config: &PlayoutConfig,
move_object: &MoveObject,
) -> Result<MoveObject, ServiceError> {
let (source_path, _, _) = norm_abs_path(&config.global.storage_path, &move_object.source)?;
let (mut target_path, _, _) = norm_abs_path(&config.global.storage_path, &move_object.target)?;
let (source_path, _, _) = norm_abs_path(&config.channel.storage_path, &move_object.source)?;
let (mut target_path, _, _) = norm_abs_path(&config.channel.storage_path, &move_object.target)?;
if !source_path.exists() {
return Err(ServiceError::BadRequest("Source file not exist!".into()));
@ -314,7 +314,7 @@ pub async fn remove_file_or_folder(
config: &PlayoutConfig,
source_path: &str,
) -> 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() {
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> {
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() {
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 date_range = vec![];
let mut from_template = false;
@ -224,7 +224,7 @@ pub fn playlist_generator(manager: &ChannelManager) -> Result<Vec<JsonPlaylist>,
error!(
target: Target::all(), channel = id;
"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,
) -> Result<JsonPlaylist, ServiceError> {
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
.join(d[0])
@ -34,7 +34,7 @@ pub async fn write_playlist(
) -> Result<String, ServiceError> {
let date = json_data.date.clone();
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
.extension()
@ -93,7 +93,7 @@ pub fn generate_playlist(manager: ChannelManager) -> Result<JsonPlaylist, Servic
for path in &source.paths {
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);
}
@ -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> {
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
.join(d[0])

View File

@ -118,7 +118,7 @@ pub fn stat(config: PlayoutConfig) -> SystemStat {
for disk in &*disks {
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.total = disk.total_space();

@ -1 +1 @@
Subproject commit 2045e47ab5e9f42ff73e03a7c4c604821a73f7ca
Subproject commit e183320e13bd972d632b841bde722fa7bbed70e5

View File

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

View File

@ -22,8 +22,8 @@ async fn prepare_config() -> (PlayoutConfig, ChannelManager) {
sqlx::query(
r#"
UPDATE global SET hls_path = "assets/hls", logging_path = "assets/log",
playlist_path = "assets/playlists", storage_path = "assets/storage";
UPDATE global SET public_root = "assets/hls", logging_path = "assets/log", playlist_root = "assets/playlists", storage_root = "assets/storage";
UPDATE channels SET hls_path = "assets/hls", playlist_path = "assets/playlists", storage_path = "assets/storage";
UPDATE configurations SET processing_width = 1024, processing_height = 576;
"#,
)

View File

@ -25,8 +25,8 @@ async fn prepare_config() -> (PlayoutConfig, ChannelManager) {
sqlx::query(
r#"
UPDATE global SET hls_path = "assets/hls", logging_path = "assets/log",
playlist_path = "assets/playlists", storage_path = "assets/storage";
UPDATE global SET public_root = "assets/hls", logging_path = "assets/log", playlist_root = "assets/playlists", storage_root = "assets/storage";
UPDATE channels SET hls_path = "assets/hls", playlist_path = "assets/playlists", storage_path = "assets/storage";
UPDATE configurations SET processing_width = 1024, processing_height = 576;
"#,
)
@ -104,7 +104,7 @@ fn test_generate_playlist_from_folder() {
config.processing.mode = Playlist;
config.storage.filler = "assets/".into();
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);
@ -149,7 +149,7 @@ fn test_generate_playlist_from_template() {
config.processing.mode = Playlist;
config.storage.filler = "assets/".into();
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);

View File

@ -24,8 +24,8 @@ async fn prepare_config() -> (PlayoutConfig, ChannelManager) {
sqlx::query(
r#"
UPDATE global SET hls_path = "assets/hls", logging_path = "assets/log",
playlist_path = "assets/playlists", storage_path = "assets/storage";
UPDATE global SET public_root = "assets/hls", logging_path = "assets/log", playlist_root = "assets/playlists", storage_root = "assets/storage";
UPDATE channels SET hls_path = "assets/hls", playlist_path = "assets/playlists", storage_path = "assets/storage";
UPDATE configurations SET processing_width = 1024, processing_height = 576;
"#,
)
@ -66,7 +66,7 @@ fn test_gen_source() {
config.playlist.start_sec = Some(0.0);
config.playlist.length = "24:00:00".into();
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();
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.length = "24:00:00".into();
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.output.mode = Null;
config.output.output_count = 1;
@ -147,7 +147,7 @@ fn playlist_next_missing() {
config.playlist.start_sec = Some(0.0);
config.playlist.length = "24:00:00".into();
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.output.mode = Null;
config.output.output_count = 1;
@ -181,7 +181,7 @@ fn playlist_to_short() {
config.playlist.start_sec = Some(21600.0);
config.playlist.length = "24:00:00".into();
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.output.mode = Null;
config.output.output_count = 1;
@ -215,7 +215,7 @@ fn playlist_init_after_list_end() {
config.playlist.start_sec = Some(21600.0);
config.playlist.length = "24:00:00".into();
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.output.mode = Null;
config.output.output_count = 1;
@ -249,7 +249,7 @@ fn playlist_change_at_midnight() {
config.playlist.start_sec = Some(0.0);
config.playlist.length = "24:00:00".into();
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.output.mode = Null;
config.output.output_count = 1;
@ -283,7 +283,7 @@ fn playlist_change_before_midnight() {
config.playlist.start_sec = Some(0.0);
config.playlist.length = "24:00:00".into();
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.output.mode = Null;
config.output.output_count = 1;
@ -317,7 +317,7 @@ fn playlist_change_at_six() {
config.playlist.start_sec = Some(21600.0);
config.playlist.length = "24:00:00".into();
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.output.mode = Null;
config.output.output_count = 1;

View File

@ -18,8 +18,8 @@ async fn prepare_config() -> (PlayoutConfig, ChannelManager) {
sqlx::query(
r#"
UPDATE global SET hls_path = "assets/hls", logging_path = "assets/log",
playlist_path = "assets/playlists", storage_path = "assets/storage";
UPDATE global SET public_root = "assets/hls", logging_path = "assets/log", playlist_root = "assets/playlists", storage_root = "assets/storage";
UPDATE channels SET hls_path = "assets/hls", playlist_path = "assets/playlists", storage_path = "assets/storage";
UPDATE configurations SET processing_width = 1024, processing_height = 576;
"#,
)