unify init process, simplify argument names and model values

This commit is contained in:
Jonathan Baecker 2024-09-30 17:56:48 +02:00
parent 7463512fa6
commit 2f5911fd35
36 changed files with 274 additions and 336 deletions

View File

@ -61,6 +61,7 @@
"reqwest",
"rsplit",
"rustls",
"sqlite",
"sqlx",
"starttls",
"tokio",

View File

@ -12,7 +12,7 @@ COPY <<-EOT /run.sh
#!/bin/sh
if [ ! -f /db/ffplayout.db ]; then
ffplayout -u admin -p admin -m contact@example.com --storage "/tv-media" --playlist "/playlists" --public "/public" --log-path "/logging" --shared-storage
ffplayout -i -u admin -p admin -m contact@example.com --storage "/tv-media" --playlist "/playlists" --public "/public" --log-path "/logging" --shared
fi
/usr/bin/ffplayout -l "0.0.0.0:8787"

View File

@ -14,7 +14,7 @@ COPY <<-EOT /run.sh
#!/bin/sh
if [ ! -f /db/ffplayout.db ]; then
ffplayout -u admin -p admin -m contact@example.com --storage "/tv-media" --playlist "/playlists" --public "/public" --log-path "/logging" --shared-storage
ffplayout -i -u admin -p admin -m contact@example.com --storage "/tv-media" --playlist "/playlists" --public "/public" --log-path "/logging" --shared
fi
/usr/bin/ffplayout -l "0.0.0.0:8787"

View File

@ -204,7 +204,7 @@ COPY <<-EOT /run.sh
#!/bin/sh
if [ ! -f /db/ffplayout.db ]; then
ffplayout -u admin -p admin -m contact@example.com --storage "/tv-media" --playlist "/playlists" --public "/public" --log-path "/logging" --shared-storage
ffplayout -i -u admin -p admin -m contact@example.com --storage "/tv-media" --playlist "/playlists" --public "/public" --log-path "/logging" --shared
fi
/usr/bin/ffplayout -l "0.0.0.0:8787"

View File

@ -1,23 +1,19 @@
### Install ffplayout
**Note:** This is the official and supported way.
ffplayout provides ***.deb** and ***.rpm** packages, which makes it more easy to install and use, but there is still some steps to do.
1. download the latest ffplayout from [release](https://github.com/ffplayout/ffplayout/releases/latest) page and place the package in the **/tmp** folder.
2. install it with `apt install /tmp/ffplayout_<VERSION>_amd64.deb`
3. install ffmpeg/ffprobe, or compile and copy it to **/usr/local/bin/**
4. activate systemd services:
- `systemctl enable ffplayout`
5. initial defaults and add global admin user:
- `sudo -u ffpu ffplayout -i`
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/**.
4. initial defaults and add global admin user: `sudo -u ffpu ffplayout -i`
5. use a revers proxy for SSL, Port is **8787**.
6. login with your browser, address without proxy would be: **http://[IP ADDRESS]:8787**
### Manual Install
-----
**Note:** This is for advanced user only.
- install ffmpeg/ffprobe, or compile and copy it to **/usr/local/bin/**
- download the latest archive from [release](https://github.com/ffplayout/ffplayout/releases/latest) page

View File

@ -135,7 +135,7 @@ assets = [
],
]
maintainer-scripts = "../debian/"
systemd-units = { enable = false, unit-scripts = "../assets" }
systemd-units = { enable = true, unit-scripts = "../assets" }
[package.metadata.deb.variants.arm64]
assets = [

View File

@ -490,9 +490,9 @@ async fn patch_channel(
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;
data.public = channel.public;
data.playlists = channel.playlists;
data.storage = channel.storage;
}
handles::update_channel(&pool, *id, data).await?;
@ -669,13 +669,13 @@ async fn update_playout_config(
user: web::ReqData<UserMeta>,
) -> Result<impl Responder, ServiceError> {
let manager = controllers.lock().unwrap().get(*id).unwrap();
let p = manager.channel.lock().unwrap().storage_path.clone();
let storage_path = Path::new(&p);
let p = manager.channel.lock().unwrap().storage.clone();
let storage = Path::new(&p);
let config_id = manager.config.lock().unwrap().general.id;
let (_, _, logo) = norm_abs_path(storage_path, &data.processing.logo)?;
let (_, _, filler) = norm_abs_path(storage_path, &data.storage.filler)?;
let (_, _, font) = norm_abs_path(storage_path, &data.text.font)?;
let (_, _, logo) = norm_abs_path(storage, &data.processing.logo)?;
let (_, _, filler) = norm_abs_path(storage, &data.storage.filler)?;
let (_, _, font) = norm_abs_path(storage, &data.text.font)?;
data.processing.logo = logo;
data.storage.filler = filler;
@ -1064,14 +1064,14 @@ 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().channel.storage_path.clone();
let storage = manager.config.lock().unwrap().channel.storage.clone();
if let Some(obj) = data {
if let Some(paths) = &obj.paths {
let mut path_list = vec![];
for path in paths {
let (p, _, _) = norm_abs_path(&storage_path, path)?;
let (p, _, _) = norm_abs_path(&storage, path)?;
path_list.push(p);
}
@ -1306,9 +1306,9 @@ 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.channel.storage_path.clone();
let storage = config.channel.storage.clone();
let file_path = req.match_info().query("filename");
let (path, _, _) = norm_abs_path(&storage_path, file_path)?;
let (path, _, _) = norm_abs_path(&storage, file_path)?;
let file = actix_files::NamedFile::open(path)?;
Ok(file
@ -1339,7 +1339,7 @@ async fn get_public(
{
let manager = controllers.lock().unwrap().get(id).unwrap();
let config = manager.config.lock().unwrap();
config.channel.hls_path.join(public)
config.channel.public.join(public)
} else {
public_path()
}

View File

@ -36,7 +36,8 @@ pub async fn db_migrate(conn: &Pool<Sqlite>) -> Result<(), Box<dyn std::error::E
}
pub async fn select_global(conn: &Pool<Sqlite>) -> Result<GlobalSettings, sqlx::Error> {
let query = "SELECT id, secret, logging_path, playlist_root, public_root, storage_root, shared_storage FROM global WHERE id = 1";
let query =
"SELECT id, secret, logs, playlists, public, storage, shared FROM global WHERE id = 1";
sqlx::query_as(query).fetch_one(conn).await
}
@ -45,15 +46,15 @@ pub async fn update_global(
conn: &Pool<Sqlite>,
global: GlobalSettings,
) -> Result<SqliteQueryResult, sqlx::Error> {
let query = "UPDATE global SET logging_path = $2, playlist_root = $3, public_root = $4, storage_root = $5, shared_storage = $6 WHERE id = 1";
let query = "UPDATE global SET logs = $2, playlists = $3, public = $4, storage = $5, shared = $6 WHERE id = 1";
sqlx::query(query)
.bind(global.id)
.bind(global.logging_path)
.bind(global.playlist_root)
.bind(global.public_root)
.bind(global.storage_root)
.bind(global.shared_storage)
.bind(global.logs)
.bind(global.playlists)
.bind(global.public)
.bind(global.storage)
.bind(global.shared)
.execute(conn)
.await
}
@ -73,7 +74,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.hls_path, c.playlist_path, c.storage_path, c.last_date, c.time_shift FROM channels c
"SELECT c.id, c.name, c.preview_url, c.extra_extensions, c.active, c.public, c.playlists, c.storage, 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;"
@ -110,16 +111,16 @@ pub async fn update_channel(
channel: Channel,
) -> Result<SqliteQueryResult, sqlx::Error> {
let query =
"UPDATE channels SET name = $2, preview_url = $3, extra_extensions = $4, hls_path = $5, playlist_path = $6, storage_path = $7 WHERE id = $1";
"UPDATE channels SET name = $2, preview_url = $3, extra_extensions = $4, public = $5, playlists = $6, storage = $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)
.bind(channel.public)
.bind(channel.playlists)
.bind(channel.storage)
.execute(conn)
.await
}
@ -151,14 +152,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, hls_path, playlist_path, storage_path) VALUES($1, $2, $3, $4, $5, $6)";
let query = "INSERT INTO channels (name, preview_url, extra_extensions, public, playlists, storage) 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)
.bind(channel.public)
.bind(channel.playlists)
.bind(channel.storage)
.execute(conn)
.await?;

View File

@ -16,11 +16,11 @@ use crate::utils::config::PlayoutConfig;
pub struct GlobalSettings {
pub id: i32,
pub secret: Option<String>,
pub logging_path: String,
pub playlist_root: String,
pub public_root: String,
pub storage_root: String,
pub shared_storage: bool,
pub logs: String,
pub playlists: String,
pub public: String,
pub storage: String,
pub shared: bool,
}
impl GlobalSettings {
@ -32,11 +32,11 @@ impl GlobalSettings {
Err(_) => GlobalSettings {
id: 0,
secret: None,
logging_path: String::new(),
playlist_root: String::new(),
public_root: String::new(),
storage_root: String::new(),
shared_storage: false,
logs: String::new(),
playlists: String::new(),
public: String::new(),
storage: String::new(),
shared: false,
},
}
}
@ -61,9 +61,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 public: String,
pub playlists: String,
pub storage: String,
pub last_date: Option<String>,
pub time_shift: f64,

View File

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

View File

@ -349,7 +349,7 @@ pub fn start_channel(manager: ChannelManager) -> Result<(), ProcessError> {
let channel_id = config.general.channel_id;
drain_hls_path(
&config.channel.hls_path,
&config.channel.public,
&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.channel.storage_path);
let path = Path::new(&config.channel.storage);
if !path.exists() {
error!(target: Target::file_mail(), channel = id; "Folder path not exists: '{path:?}'");

View File

@ -75,7 +75,7 @@ pub fn ingest_server(
let ingest_is_running = channel_mgr.ingest_is_running.clone();
let vtt_dummy = config
.channel
.storage_path
.storage
.join(config.processing.vtt_dummy.clone().unwrap_or_default());
if let Some(ingest_input_cmd) = config.advanced.ingest.input_cmd {

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.channel.storage_path
config.channel.storage
);
let config_clone = config.clone();

View File

@ -314,14 +314,11 @@ impl CurrentProgram {
self.current_node =
handle_list_init(&self.config, node_clone, &self.manager, last_index);
if self.current_node.source.contains(
&self
.config
.channel
.storage_path
.to_string_lossy()
.to_string(),
) || self.current_node.source.contains("color=c=#121212")
if self
.current_node
.source
.contains(&self.config.channel.storage.to_string_lossy().to_string())
|| self.current_node.source.contains("color=c=#121212")
{
is_filler = true;
}

View File

@ -65,7 +65,7 @@ fn ingest_to_hls_server(manager: ChannelManager) -> Result<(), ProcessError> {
if config.processing.vtt_enable {
let vtt_dummy = config
.channel
.storage_path
.storage
.join(config.processing.vtt_dummy.clone().unwrap_or_default());
if vtt_dummy.is_file() {

View File

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

View File

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

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.channel.playlist_path.clone();
let mut playlist_path = config.channel.playlists.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.channel.playlist_path.to_string_lossy()) {
if playlist_path.is_dir() || is_remote(&config.channel.playlists.to_string_lossy()) {
let d: Vec<&str> = date.split('-').collect();
playlist_path = playlist_path
.join(d[0])

View File

@ -69,7 +69,7 @@ pub fn prepare_output_cmd(
let re_v = Regex::new(r"\[?0:v(:0)?\]?").unwrap();
let vtt_dummy = config
.channel
.storage_path
.storage
.join(config.processing.vtt_dummy.clone().unwrap_or_default());
if let Some(mut filter) = filters.clone() {
@ -622,7 +622,7 @@ pub fn loop_image(config: &PlayoutConfig, node: &Media) -> Vec<String> {
let vtt_file = Path::new(&node.source).with_extension("vtt");
let vtt_dummy = config
.channel
.storage_path
.storage
.join(config.processing.vtt_dummy.clone().unwrap_or_default());
if node.seek > 0.5 {
@ -663,7 +663,7 @@ pub fn loop_filler(config: &PlayoutConfig, node: &Media) -> Vec<String> {
let vtt_file = Path::new(&node.source).with_extension("vtt");
let vtt_dummy = config
.channel
.storage_path
.storage
.join(config.processing.vtt_dummy.clone().unwrap_or_default());
if vtt_file.is_file() {
@ -737,7 +737,7 @@ pub fn seek_and_length(config: &PlayoutConfig, node: &mut Media) -> Vec<String>
let vtt_file = Path::new(&node.source).with_extension("vtt");
let vtt_dummy = config
.channel
.storage_path
.storage
.join(config.processing.vtt_dummy.clone().unwrap_or_default());
if node.seek > 0.5 {
@ -788,7 +788,7 @@ pub fn gen_dummy(config: &PlayoutConfig, duration: f64) -> (String, Vec<String>)
if config.processing.vtt_enable {
let vtt_dummy = config
.channel
.storage_path
.storage
.join(config.processing.vtt_dummy.clone().unwrap_or_default());
if vtt_dummy.is_file() {

View File

@ -45,7 +45,7 @@ pub struct Args {
)]
pub init: bool,
#[clap(short, long, help_heading = Some("Initial Setup"), help = "Add a global admin user")]
#[clap(short, long, help_heading = Some("Initial Setup"), help = "Add a global admin")]
pub add: bool,
#[clap(short, long, help_heading = Some("Initial Setup"), help = "Create admin user")]
@ -66,16 +66,16 @@ pub struct Args {
help_heading = Some("Initial Setup"),
help = "Share storage across channels, important for running in Containers"
)]
pub shared_storage: bool,
pub shared: bool,
#[clap(long, env, help_heading = Some("Initial Setup / General"), help = "Logging path")]
pub log_path: Option<PathBuf>,
pub logs: Option<String>,
#[clap(long, env, help_heading = Some("Initial Setup / General"), help = "Path to public files, also HLS playlists")]
pub public: Option<String>,
#[clap(long, help_heading = Some("Initial Setup / Playlist"), help = "Path to playlist, or playlist root folder.")]
pub playlist: Option<String>,
pub playlists: Option<String>,
#[clap(long, env, help_heading = Some("General"), help = "Path to database file")]
pub db: Option<PathBuf>,
@ -129,7 +129,7 @@ pub struct Args {
long,
env,
help_heading = Some("General / Playout"),
help = "Channels by ids to process (for export config, foreground running, etc.)",
help = "Channels by ids to process (for export config, generate playlist, foreground running, etc.)",
num_args = 1..,
)]
pub channels: Option<Vec<i32>>,
@ -184,6 +184,7 @@ fn global_user(args: &mut Args) {
let mut user = String::new();
let mut mail = String::new();
if args.username.is_none() {
print!("Global admin: ");
stdout().flush().unwrap();
@ -192,13 +193,17 @@ fn global_user(args: &mut Args) {
.expect("Did not enter a correct name?");
args.username = Some(user.trim().to_string());
}
if args.password.is_none() {
print!("Password: ");
stdout().flush().unwrap();
let password = read_password();
args.password = password.ok();
}
if args.mail.is_none() {
print!("Mail: ");
stdout().flush().unwrap();
@ -207,6 +212,7 @@ fn global_user(args: &mut Args) {
.expect("Did not enter a correct name?");
args.mail = Some(mail.trim().to_string());
}
}
pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
@ -230,22 +236,25 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
let mut storage = String::new();
let mut playlist = String::new();
let mut logging = String::new();
let mut hls = String::new();
let mut public = String::new();
let mut shared_store = String::new();
let mut global = GlobalSettings {
id: 0,
secret: None,
logging_path: String::new(),
playlist_root: String::new(),
public_root: String::new(),
storage_root: String::new(),
shared_storage: false,
logs: String::new(),
playlists: String::new(),
public: String::new(),
storage: String::new(),
shared: false,
};
if check_user.unwrap_or_default().is_empty() {
global_user(&mut args);
}
if let Some(st) = args.storage {
global.storage = st;
} else {
print!("Storage path [/var/lib/ffplayout/tv-media]: ");
stdout().flush().unwrap();
@ -253,15 +262,19 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.read_line(&mut storage)
.expect("Did not enter a correct path?");
if storage.trim().is_empty() {
global.storage_root = "/var/lib/ffplayout/tv-media".to_string();
global.storage = if storage.trim().is_empty() {
"/var/lib/ffplayout/tv-media".to_string()
} else {
global.storage_root = storage
storage
.trim()
.trim_matches(|c| c == '"' || c == '\'')
.to_string();
.to_string()
};
}
if let Some(pl) = args.playlists {
global.playlists = pl
} else {
print!("Playlist path [/var/lib/ffplayout/playlists]: ");
stdout().flush().unwrap();
@ -269,15 +282,19 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.read_line(&mut playlist)
.expect("Did not enter a correct path?");
if playlist.trim().is_empty() {
global.playlist_root = "/var/lib/ffplayout/playlists".to_string();
global.playlists = if playlist.trim().is_empty() {
"/var/lib/ffplayout/playlists".to_string()
} else {
global.playlist_root = playlist
playlist
.trim()
.trim_matches(|c| c == '"' || c == '\'')
.to_string();
.to_string()
};
}
if let Some(lp) = args.logs {
global.logs = lp;
} else {
print!("Logging path [/var/log/ffplayout]: ");
stdout().flush().unwrap();
@ -285,31 +302,39 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.read_line(&mut logging)
.expect("Did not enter a correct path?");
if logging.trim().is_empty() {
global.logging_path = "/var/log/ffplayout".to_string();
global.logs = if logging.trim().is_empty() {
"/var/log/ffplayout".to_string()
} else {
global.logging_path = logging
logging
.trim()
.trim_matches(|c| c == '"' || c == '\'')
.to_string();
.to_string()
}
}
if let Some(p) = args.public {
global.public = p;
} else {
print!("Public (HLS) path [/usr/share/ffplayout/public]: ");
stdout().flush().unwrap();
stdin()
.read_line(&mut hls)
.read_line(&mut public)
.expect("Did not enter a correct path?");
if hls.trim().is_empty() {
global.public_root = "/usr/share/ffplayout/public".to_string();
global.public = if public.trim().is_empty() {
"/usr/share/ffplayout/public".to_string()
} else {
global.public_root = hls
public
.trim()
.trim_matches(|c| c == '"' || c == '\'')
.to_string();
.to_string()
};
}
if args.shared {
global.shared = true;
} else {
print!("Shared storage [Y/n]: ");
stdout().flush().unwrap();
@ -317,7 +342,8 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.read_line(&mut shared_store)
.expect("Did not enter a yes or no?");
global.shared_storage = shared_store.trim().to_lowercase().starts_with('y');
global.shared = shared_store.trim().to_lowercase().starts_with('y');
}
if let Err(e) = handles::update_global(pool, global.clone()).await {
eprintln!("{e}");
@ -325,25 +351,25 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
};
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;
channel.public = global.public;
channel.playlists = global.playlists;
channel.storage = global.storage;
let mut storage_path = PathBuf::from(channel.storage_path.clone());
let mut storage_path = PathBuf::from(channel.storage.clone());
if global.shared_storage {
if global.shared {
storage_path = storage_path.join("1");
channel.preview_url = "http://127.0.0.1:8787/1/stream.m3u8".to_string();
channel.hls_path = Path::new(&channel.hls_path)
channel.public = Path::new(&channel.public)
.join("1")
.to_string_lossy()
.to_string();
channel.playlist_path = Path::new(&channel.playlist_path)
channel.playlists = Path::new(&channel.playlists)
.join("1")
.to_string_lossy()
.to_string();
channel.storage_path = storage_path.to_string_lossy().to_string();
channel.storage = storage_path.to_string_lossy().to_string();
};
if let Err(e) = copy_assets(&storage_path).await {
@ -358,20 +384,13 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
}
println!("\nSet global settings done...");
}
if args.add {
} else if args.add {
global_user(&mut args);
}
if let Some(username) = args.username {
error_code = 0;
if args.mail.is_none() || args.password.is_none() {
eprintln!("Mail/password missing!");
error_code = 1;
}
let chl: Vec<i32> = channels.clone().iter().map(|c| c.id).collect();
let ff_user = User {
@ -392,73 +411,6 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
println!("Create global admin user \"{username}\" done...");
}
if !args.init
&& args.storage.is_some()
&& args.playlist.is_some()
&& args.public.is_some()
&& args.log_path.is_some()
{
error_code = 0;
let global = GlobalSettings {
id: 0,
secret: None,
logging_path: args.log_path.unwrap().to_string_lossy().to_string(),
playlist_root: args.playlist.unwrap(),
public_root: args.public.unwrap(),
storage_root: args.storage.unwrap(),
shared_storage: args.shared_storage,
};
let mut channel = handles::select_channel(pool, &1)
.await
.expect("Select Channel 1");
let mut storage_path = PathBuf::from(global.storage_root.clone());
if args.shared_storage {
storage_path = storage_path.join("1");
channel.hls_path = Path::new(&global.public_root)
.join("1")
.to_string_lossy()
.to_string();
channel.playlist_path = Path::new(&global.playlist_root)
.join("1")
.to_string_lossy()
.to_string();
channel.storage_path = storage_path.to_string_lossy().to_string();
} else {
channel.hls_path = global.public_root.clone();
channel.playlist_path = global.playlist_root.clone();
channel.storage_path = global.storage_root.clone();
}
if let Err(e) = copy_assets(&storage_path).await {
eprintln!("{e}");
};
match handles::update_global(pool, global.clone()).await {
Ok(_) => println!("Update globals done..."),
Err(e) => {
eprintln!("{e}");
error_code = 1;
}
};
match handles::update_channel(pool, 1, channel).await {
Ok(_) => println!("Update channel done..."),
Err(e) => {
eprintln!("{e}");
error_code = 1;
}
};
#[cfg(target_family = "unix")]
{
update_permissions().await;
}
}
if ARGS.list_channels {
let chl = channels
.iter()

View File

@ -35,7 +35,7 @@ pub async fn create_channel(
target_channel: Channel,
) -> Result<Channel, ServiceError> {
let channel = handles::insert_channel(conn, target_channel).await?;
let storage_path = PathBuf::from(channel.storage_path.clone());
let storage_path = PathBuf::from(channel.storage.clone());
handles::new_channel_presets(conn, channel.id).await?;

View File

@ -176,21 +176,21 @@ pub struct PlayoutConfig {
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct Channel {
pub logging_path: PathBuf,
pub hls_path: PathBuf,
pub playlist_path: PathBuf,
pub storage_path: PathBuf,
pub shared_storage: bool,
pub logs: PathBuf,
pub public: PathBuf,
pub playlists: PathBuf,
pub storage: PathBuf,
pub shared: bool,
}
impl Channel {
pub fn new(config: &models::GlobalSettings, channel: models::Channel) -> Self {
Self {
logging_path: PathBuf::from(config.logging_path.clone()),
hls_path: PathBuf::from(channel.hls_path.clone()),
playlist_path: PathBuf::from(channel.playlist_path.clone()),
storage_path: PathBuf::from(channel.storage_path.clone()),
shared_storage: config.shared_storage,
logs: PathBuf::from(config.logs.clone()),
public: PathBuf::from(channel.public.clone()),
playlists: PathBuf::from(channel.playlists.clone()),
storage: PathBuf::from(channel.storage.clone()),
shared: config.shared,
}
}
}
@ -588,27 +588,23 @@ impl PlayoutConfig {
let task = Task::new(&config);
let mut output = Output::new(&config);
if !channel.storage_path.is_dir() {
tokio::fs::create_dir_all(&channel.storage_path)
if !channel.storage.is_dir() {
tokio::fs::create_dir_all(&channel.storage)
.await
.unwrap_or_else(|_| {
panic!("Can't create storage folder: {:#?}", channel.storage_path)
});
.unwrap_or_else(|_| panic!("Can't create storage folder: {:#?}", channel.storage));
}
let mut storage =
Storage::new(&config, channel.storage_path.clone(), global.shared_storage);
let mut storage = Storage::new(&config, channel.storage.clone(), global.shared);
if !channel.playlist_path.is_dir() {
tokio::fs::create_dir_all(&channel.playlist_path).await?;
if !channel.playlists.is_dir() {
tokio::fs::create_dir_all(&channel.playlists).await?;
}
if !channel.logging_path.is_dir() {
tokio::fs::create_dir_all(&channel.logging_path).await?;
if !channel.logs.is_dir() {
tokio::fs::create_dir_all(&channel.logs).await?;
}
let (filler_path, _, filler) =
norm_abs_path(&channel.storage_path, &config.storage_filler)?;
let (filler_path, _, filler) = norm_abs_path(&channel.storage, &config.storage_filler)?;
storage.filler = filler;
storage.filler_path = filler_path;
@ -621,7 +617,7 @@ impl PlayoutConfig {
playlist.length_sec = Some(86400.0);
}
let (logo_path, _, logo) = norm_abs_path(&channel.storage_path, &processing.logo)?;
let (logo_path, _, logo) = norm_abs_path(&channel.storage, &processing.logo)?;
if processing.add_logo && !logo_path.is_file() {
processing.add_logo = false;
@ -709,13 +705,13 @@ 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(&channel.hls_path, item) {
let parent = hls_path.parent().ok_or("HLS parent path")?;
if let Ok((public, _, _)) = norm_abs_path(&channel.public, item) {
let parent = public.parent().ok_or("HLS parent path")?;
if !parent.is_dir() {
fs::create_dir_all(parent).await?;
}
item.clone_from(&hls_path.to_string_lossy().to_string());
item.clone_from(&public.to_string_lossy().to_string());
};
}
}
@ -736,7 +732,7 @@ impl PlayoutConfig {
text.node_pos = None;
}
let (font_path, _, font) = norm_abs_path(&channel.storage_path, &text.font)?;
let (font_path, _, font) = norm_abs_path(&channel.storage, &text.font)?;
text.font = font;
text.font_path = font_path.to_string_lossy().to_string();
@ -853,12 +849,12 @@ pub async fn get_config(
config.storage.paths = paths;
}
if let Some(playlist) = args.playlist {
config.channel.playlist_path = PathBuf::from(&playlist);
if let Some(playlist) = args.playlists {
config.channel.playlists = PathBuf::from(&playlist);
}
if let Some(folder) = args.folder {
config.channel.storage_path = folder;
config.channel.storage = folder;
config.processing.mode = ProcessMode::Folder;
}
@ -877,10 +873,10 @@ pub async fn get_config(
}
}
if args.shared_storage {
// config.channel.shared_storage could be true already,
// so should not be overridden with false when args.shared_storage is not set
config.channel.shared_storage = args.shared_storage
if args.shared {
// config.channel.shared could be true already,
// so should not be overridden with false when args.shared is not set
config.channel.shared = args.shared
}
if let Some(volume) = args.volume {

View File

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

View File

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

View File

@ -299,11 +299,7 @@ fn file_formatter(
pub fn log_file_path() -> PathBuf {
let config = GlobalSettings::global();
let mut log_path = ARGS
.log_path
.clone()
.unwrap_or(PathBuf::from(&config.logging_path));
let mut log_path = PathBuf::from(&ARGS.logs.as_ref().unwrap_or(&config.logs));
if !log_path.is_absolute() {
log_path = env::current_dir().unwrap().join(log_path);

View File

@ -203,7 +203,7 @@ pub fn public_path() -> PathBuf {
let dev_path = env::current_dir()
.unwrap_or_default()
.join("frontend/.output/public/");
let mut public_path = PathBuf::from(&config.public_root);
let mut public_path = PathBuf::from(&config.public);
if let Some(p) = &ARGS.public {
// When public path is set as argument use this path for serving static files.

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.channel.playlist_path.clone();
let mut playlist_path = config.channel.playlists.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.channel.playlist_path.clone();
let mut playlist_path = config.channel.playlists.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.channel.storage_path, &path.to_string_lossy())?;
norm_abs_path(&config.channel.storage, &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.channel.playlist_path);
let mut playlist_path = PathBuf::from(&config.channel.playlists);
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.channel.storage_path.starts_with(disk.mount_point())
&& config.channel.storage.starts_with(disk.mount_point())
{
storage.path = disk.name().to_string_lossy().to_string();
storage.total = disk.total_space();

View File

@ -5,11 +5,11 @@ CREATE TABLE
global (
id INTEGER PRIMARY KEY,
secret TEXT NOT NULL,
logging_path TEXT NOT NULL DEFAULT "/var/log/ffplayout",
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,
logs TEXT NOT NULL DEFAULT "/var/log/ffplayout",
playlists TEXT NOT NULL DEFAULT "/var/lib/ffplayout/playlists",
public TEXT NOT NULL DEFAULT "/usr/share/ffplayout/public",
storage TEXT NOT NULL DEFAULT "/var/lib/ffplayout/tv-media",
shared INTEGER NOT NULL DEFAULT 0,
UNIQUE (secret)
);
@ -27,9 +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",
public TEXT NOT NULL DEFAULT "/usr/share/ffplayout/public",
playlists TEXT NOT NULL DEFAULT "/var/lib/ffplayout/playlists",
storage TEXT NOT NULL DEFAULT "/var/lib/ffplayout/tv-media",
last_date TEXT,
time_shift REAL NOT NULL DEFAULT 0
);

View File

@ -39,8 +39,8 @@ name = "api_routes"
path = "src/api_routes.rs"
[[test]]
name = "lib_utils"
path = "src/lib_utils.rs"
name = "utils"
path = "src/utils.rs"
[[test]]
name = "engine_playlist"

View File

@ -22,8 +22,8 @@ async fn prepare_config() -> (PlayoutConfig, ChannelManager, Pool<Sqlite>) {
sqlx::query(
r#"
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 global SET public = "assets/hls", logs = "assets/log", playlists = "assets/playlists", storage = "assets/storage";
UPDATE channels SET public = "assets/hls", playlists = "assets/playlists", storage = "assets/storage";
"#,
)
.execute(&pool)

View File

@ -22,8 +22,8 @@ async fn prepare_config() -> (PlayoutConfig, ChannelManager) {
sqlx::query(
r#"
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 global SET public = "assets/hls", logs = "assets/log", playlists = "assets/playlists", storage = "assets/storage";
UPDATE channels SET public = "assets/hls", playlists = "assets/playlists", storage = "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 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 global SET public = "assets/hls", logs = "assets/log", playlists = "assets/playlists", storages = "assets/storage";
UPDATE channels SET public = "assets/hls", playlists = "assets/playlists", storage = "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.channel.playlist_path = "assets/playlists".into();
config.channel.playlists = "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.channel.playlist_path = "assets/playlists".into();
config.channel.playlists = "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 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 global SET public = "assets/hls", logs = "assets/log", playlists = "assets/playlists", storage = "assets/storage";
UPDATE channels SET public = "assets/hls", playlists = "assets/playlists", storage = "assets/storage";
UPDATE configurations SET processing_width = 1024, processing_height = 576;
"#,
)
@ -67,7 +67,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.channel.playlist_path = "assets/playlists".into();
config.channel.playlists = "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);
@ -114,7 +114,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.channel.playlist_path = "assets/playlists".into();
config.channel.playlists = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
config.output.mode = Null;
config.output.output_count = 1;
@ -148,7 +148,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.channel.playlist_path = "assets/playlists".into();
config.channel.playlists = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
config.output.mode = Null;
config.output.output_count = 1;
@ -182,7 +182,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.channel.playlist_path = "assets/playlists".into();
config.channel.playlists = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
config.output.mode = Null;
config.output.output_count = 1;
@ -216,7 +216,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.channel.playlist_path = "assets/playlists".into();
config.channel.playlists = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
config.output.mode = Null;
config.output.output_count = 1;
@ -250,7 +250,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.channel.playlist_path = "assets/playlists".into();
config.channel.playlists = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
config.output.mode = Null;
config.output.output_count = 1;
@ -284,7 +284,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.channel.playlist_path = "assets/playlists".into();
config.channel.playlists = "assets/playlists".into();
config.storage.filler = "assets/media_filler/filler_0.mp4".into();
config.output.mode = Null;
config.output.output_count = 1;
@ -318,7 +318,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.channel.playlist_path = "assets/playlists".into();
config.channel.playlists = "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 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 global SET public = "assets/hls", logs = "assets/log", playlists = "assets/playlists", storage = "assets/storage";
UPDATE channels SET public = "assets/hls", playlists = "assets/playlists", storage = "assets/storage";
UPDATE configurations SET processing_width = 1024, processing_height = 576;
"#,
)