From 33e7bed1c2d16922c63bc6bc89008a7195f2b8a0 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 4 Jul 2024 15:08:05 +0200 Subject: [PATCH 1/9] use noise source for dummy clips --- ffplayout/src/player/filter/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ffplayout/src/player/filter/mod.rs b/ffplayout/src/player/filter/mod.rs index ff066dd9..2aa7a1a5 100644 --- a/ffplayout/src/player/filter/mod.rs +++ b/ffplayout/src/player/filter/mod.rs @@ -592,6 +592,10 @@ pub fn filter_chains( ) -> Filters { let mut filters = Filters::new(config.clone(), 0); + if node.source.contains("color=c=") { + filters.audio_position = 1; + } + if node.unit == Encoder { if !config.processing.audio_only { add_text(node, &mut filters, config, filter_chain); @@ -678,9 +682,9 @@ pub fn filter_chains( "Missing audio track (id {i}) from {}", node.source ); - } - add_audio(node, &mut filters, i, config); + add_audio(node, &mut filters, i, config); + } } // add at least anull filter, for correct filter construction, From 04955e14c63aca479b69dd8a40b4f9f0af0b016b Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 4 Jul 2024 15:08:32 +0200 Subject: [PATCH 2/9] move migration to run_args --- ffplayout/src/main.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ffplayout/src/main.rs b/ffplayout/src/main.rs index 1cec61ec..20db55e3 100644 --- a/ffplayout/src/main.rs +++ b/ffplayout/src/main.rs @@ -81,12 +81,6 @@ async fn main() -> std::io::Result<()> { .await .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; - if ARGS.dump_advanced.is_none() && ARGS.dump_config.is_none() { - if let Err(e) = handles::db_migrate(&pool).await { - panic!("{e}"); - }; - } - if let Err(c) = run_args(&pool).await { exit(c); } From 31011a130dabb2752a3b615608bab610caa087a1 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 4 Jul 2024 15:09:05 +0200 Subject: [PATCH 3/9] update global paths --- ffplayout/src/utils/args_parse.rs | 132 +++++++++++++++++------------- 1 file changed, 77 insertions(+), 55 deletions(-) diff --git a/ffplayout/src/utils/args_parse.rs b/ffplayout/src/utils/args_parse.rs index 387c50c2..25d2b764 100644 --- a/ffplayout/src/utils/args_parse.rs +++ b/ffplayout/src/utils/args_parse.rs @@ -1,7 +1,6 @@ use std::{ io::{stdin, stdout, Write}, path::PathBuf, - process::exit, }; use clap::Parser; @@ -9,7 +8,7 @@ use rpassword::read_password; use sqlx::{Pool, Sqlite}; use crate::db::{ - handles::{self, insert_user}, + handles, models::{Channel, GlobalSettings, User}, }; use crate::utils::{ @@ -112,13 +111,13 @@ pub struct Args { pub log_to_console: bool, #[clap(long, env, help = "HLS output path")] - pub hls_path: Option, + pub hls_path: Option, #[clap(long, env, help = "Playlist root path")] - pub playlist_path: Option, + pub playlist_path: Option, #[clap(long, env, help = "Storage root path")] - pub storage_path: Option, + pub storage_path: Option, #[clap(long, env, help = "Share storage across channels")] pub shared_storage: bool, @@ -188,9 +187,20 @@ fn global_user(args: &mut Args) { } pub async fn run_args(pool: &Pool) -> Result<(), i32> { - let channels = handles::select_related_channels(pool, None).await; let mut args = ARGS.clone(); + if args.dump_advanced.is_none() && args.dump_config.is_none() { + if let Err(e) = handles::db_migrate(&pool).await { + panic!("{e}"); + }; + } + + let channels = handles::select_related_channels(pool, None) + .await + .unwrap_or(vec![Channel::default()]); + + let mut error_code = -1; + if args.init { let check_user = handles::select_users(pool).await; @@ -288,7 +298,7 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { if let Err(e) = handles::update_global(pool, global.clone()).await { eprintln!("{e}"); - return Err(1); + error_code = 1; }; if !global.shared_storage { @@ -306,73 +316,85 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { } if let Some(username) = args.username { + error_code = 0; + if args.mail.is_none() || args.password.is_none() { eprintln!("Mail/password missing!"); - return Err(1); + error_code = 1; } + let chl: Vec = channels.clone().iter().map(|c| c.id).collect(); + let user = User { id: 0, mail: Some(args.mail.unwrap()), username: username.clone(), password: args.password.unwrap(), role_id: Some(1), - channel_ids: Some( - channels - .unwrap_or(vec![Channel::default()]) - .iter() - .map(|c| c.id) - .collect(), - ), + channel_ids: Some(chl.clone()), token: None, }; - if let Err(e) = insert_user(pool, user).await { + if let Err(e) = handles::insert_user(pool, user).await { eprintln!("{e}"); - return Err(1); + error_code = 1; }; println!("Create global admin user \"{username}\" done..."); + } - return Err(0); + if !args.init + && args.storage_path.is_some() + && args.playlist_path.is_some() + && args.hls_path.is_some() + && args.log_path.is_some() + { + error_code = 0; + + 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(), + 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..."); + }; } if ARGS.list_channels { - match channels { - Ok(channels) => { - let chl = channels - .iter() - .map(|c| (c.id, c.name.clone())) - .collect::>(); + let chl = channels + .iter() + .map(|c| (c.id, c.name.clone())) + .collect::>(); - println!( - "Available channels:\n{}", - chl.iter() - .map(|(i, t)| format!(" {i}: '{t}'")) - .collect::>() - .join("\n") - ); + println!( + "Available channels:\n{}", + chl.iter() + .map(|(i, t)| format!(" {i}: '{t}'")) + .collect::>() + .join("\n") + ); - return Err(0); - } - Err(e) => { - eprintln!("List channels: {e}"); - - exit(1); - } - } + error_code = 0; } if let Some(id) = ARGS.dump_config { match PlayoutConfig::dump(pool, id).await { Ok(_) => { println!("Dump config to: ffplayout_{id}.toml"); - exit(0); + error_code = 0; } Err(e) => { eprintln!("Dump config: {e}"); - - exit(1); + error_code = 1; } }; } @@ -381,12 +403,11 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { match PlayoutConfig::dump(pool, id).await { Ok(_) => { println!("Dump config to: ffplayout_{id}.toml"); - exit(0); + error_code = 0; } Err(e) => { eprintln!("Dump config: {e}"); - - exit(1); + error_code = 1; } }; } @@ -395,12 +416,11 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { match AdvancedConfig::dump(pool, id).await { Ok(_) => { println!("Dump config to: advanced_{id}.toml"); - exit(0); + error_code = 0; } Err(e) => { eprintln!("Dump config: {e}"); - - exit(1); + error_code = 1; } }; } @@ -409,12 +429,11 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { match PlayoutConfig::import(pool, import.clone()).await { Ok(_) => { println!("Import config done..."); - exit(0); + error_code = 0; } Err(e) => { eprintln!("{e}"); - - exit(1); + error_code = 1; } }; } @@ -423,15 +442,18 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { match AdvancedConfig::import(pool, import.clone()).await { Ok(_) => { println!("Import config done..."); - exit(0); + error_code = 0; } Err(e) => { eprintln!("{e}"); - - exit(1); + error_code = 1; } }; } - Ok(()) + if error_code > -1 { + Err(error_code) + } else { + Ok(()) + } } From 7a3d3103e3ea95077c8b69f4b4212efe910bf343 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 4 Jul 2024 15:09:20 +0200 Subject: [PATCH 4/9] create logging path --- ffplayout/src/utils/config.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ffplayout/src/utils/config.rs b/ffplayout/src/utils/config.rs index 9fe3d7b5..70e4a152 100644 --- a/ffplayout/src/utils/config.rs +++ b/ffplayout/src/utils/config.rs @@ -590,6 +590,12 @@ impl PlayoutConfig { .expect("Can't create playlist folder"); } + if !global.logging_path.is_dir() { + tokio::fs::create_dir_all(&global.logging_path) + .await + .expect("Can't create logging folder"); + } + let (filler_path, _, _) = norm_abs_path(&global.storage_path, &config.storage_filler) .expect("Can't get filler path"); From 9e522c5fb29042a0735e9d1e6d81f801b09ce4cb Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 4 Jul 2024 15:09:50 +0200 Subject: [PATCH 5/9] support custom db folder --- ffplayout/src/utils/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ffplayout/src/utils/mod.rs b/ffplayout/src/utils/mod.rs index 9a81a8fd..a5fcd26b 100644 --- a/ffplayout/src/utils/mod.rs +++ b/ffplayout/src/utils/mod.rs @@ -156,13 +156,17 @@ impl fmt::Display for TextFilter { pub fn db_path() -> Result<&'static str, Box> { if let Some(path) = ARGS.db.clone() { - let absolute_path = if path.is_absolute() { + let mut absolute_path = if path.is_absolute() { path } else { env::current_dir()?.join(path) } .clean(); + if absolute_path.is_dir() { + absolute_path = absolute_path.join("ffplayout.db"); + } + if let Some(abs_path) = absolute_path.parent() { if abs_path.writable() { return Ok(Box::leak( From 200455f14fcb7231e206284f43e859531a85caa3 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 4 Jul 2024 15:10:33 +0200 Subject: [PATCH 6/9] docker support new playout --- docker/Dockerfile | 51 ++++++++++++++------------------------- docker/README.md | 9 ++++--- docker/docker-compose.yml | 3 --- 3 files changed, 24 insertions(+), 39 deletions(-) diff --git a/docker/Dockerfile b/docker/Dockerfile index 96e9d4a2..1d0042c5 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,42 +1,27 @@ -FROM almalinux:9 AS base +FROM alpine:latest -ENV container docker +ARG FFPLAYOUT_VERSION=0.24.0-alpha1 +ARG SHARED_STORAGE=false -RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == \ - systemd-tmpfiles-setup.service ] || rm -f $i; done); \ - rm -f /lib/systemd/system/multi-user.target.wants/*; \ - rm -f /etc/systemd/system/*.wants/*; \ - rm -f /lib/systemd/system/local-fs.target.wants/*; \ - rm -f /lib/systemd/system/sockets.target.wants/*udev*; \ - rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \ - rm -f /lib/systemd/system/basic.target.wants/*; \ - rm -f /lib/systemd/system/anaconda.target.wants/* +ENV DB=/db +ENV SHARED_STORAGE=${SHARED_STORAGE} -FROM base +COPY README.md ffplayout-v${FFPLAYOUT_VERSION}_x86_64-unknown-linux-musl.tar.* /tmp/ -ARG FFPLAYOUT_VERSION=0.22.0 -COPY README.md *.rpm /tmp/ +RUN apk update && \ + apk upgrade && \ + apk add --no-cache ffmpeg sqlite font-dejavu -RUN dnf update -y && \ - dnf install -y epel-release && \ - dnf install -y 'dnf-command(config-manager)' && \ - dnf config-manager --set-enabled crb && \ - dnf install -y --nogpgcheck https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-$(rpm -E %rhel).noarch.rpm && \ - dnf install -y --nogpgcheck https://mirrors.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-$(rpm -E %rhel).noarch.rpm && \ - dnf install -y ffmpeg wget dejavu-sans-fonts sudo && \ - dnf clean all +RUN [[ -f "/tmp/ffplayout-v${FFPLAYOUT_VERSION}_x86_64-unknown-linux-musl.tar.gz" ]] || \ + wget -q "https://github.com/ffplayout/ffplayout/releases/download/v${FFPLAYOUT_VERSION}/ffplayout-v${FFPLAYOUT_VERSION}_x86_64-unknown-linux-musl.tar.gz" -P /tmp/ && \ + cd /tmp && \ + tar xf "ffplayout-v${FFPLAYOUT_VERSION}_x86_64-unknown-linux-musl.tar.gz" && \ + cp ffplayout /usr/bin/ && \ + rm -rf /tmp/* && \ + mkdir ${DB} -RUN [[ -f /tmp/ffplayout-${FFPLAYOUT_VERSION}-1.x86_64.rpm ]] || wget -q "https://github.com/ffplayout/ffplayout/releases/download/v${FFPLAYOUT_VERSION}/ffplayout-${FFPLAYOUT_VERSION}-1.x86_64.rpm" -P /tmp/ && \ - dnf install -y /tmp/ffplayout-${FFPLAYOUT_VERSION}-1.x86_64.rpm && \ - rm /tmp/ffplayout-${FFPLAYOUT_VERSION}-1.x86_64.rpm && \ - sed -i "s/User=ffpu/User=root/g" /usr/lib/systemd/system/ffplayout.service && \ - systemctl enable ffplayout && \ - ffplayout -u admin -p admin -m contact@example.com +RUN ffplayout -u admin -p admin -m contact@example.com --storage-path "/tv-media" --playlist-path "/playlists" --hls-path "/hls" --log-path "/logging" EXPOSE 8787 -# Maybe on some systems is needed, combined with run parameters: --tmpfs /tmp --tmpfs /run --tmpfs /run/lock -# More infos: https://serverfault.com/a/1087467/387878 -#VOLUME [ "/tmp", "/run", "/run/lock" ] - -CMD ["/usr/sbin/init"] +CMD ["/usr/bin/ffplayout", "-l", "0.0.0.0:8787"] diff --git a/docker/README.md b/docker/README.md index cdb45f8e..1b0005c2 100644 --- a/docker/README.md +++ b/docker/README.md @@ -40,8 +40,11 @@ How to build the image:\ # build default docker build -t ffplayout-image . +# build with shared storage (same storage for all channels) +docker build --build-arg SHARED_STORAGE=true . + # build from root folder, to copy local *.rpm package -docker build -f docker/Dockerfile -t ffplayout-image:alma . +docker build -f docker/Dockerfile -t ffplayout-image . # build ffmpeg from source docker build -f fromSource.Dockerfile -t ffplayout-image:from-source . @@ -53,10 +56,10 @@ docker build -f nvidia-centos7.Dockerfile -t ffplayout-image:nvidia . example of command to start the container: ```BASH -docker run -it --name ffplayout --privileged -p 8787:8787 ffplayout-image +docker run -it -v /path/to/db:/db -v /path/to/storage:/tv-media -v /path/to/playlists:/playlists -v /path/to/hls:/hls -v /path/to/logging:/logging --name ffplayout -p 8787:8787 ffplayout-image # run in daemon mode -docker run -d --name ffplayout --privileged -p 8787:8787 ffplayout-image +docker run -d --name ffplayout -p 8787:8787 ffplayout-image # run with docker-compose docker-compose up -d diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 608b4c15..296ce389 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -2,10 +2,7 @@ version: '3' services: ffplayout: - cap_add: - - SYS_ADMIN container_name: ffplayout - privileged: true build: context: . dockerfile: ./Dockerfile From eb25c1f3fc1d96a712b607e43bb1baa99cdb22fe Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 4 Jul 2024 15:12:38 +0200 Subject: [PATCH 7/9] fix clippy --- ffplayout/src/player/filter/mod.rs | 14 ++++++-------- ffplayout/src/utils/args_parse.rs | 2 +- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/ffplayout/src/player/filter/mod.rs b/ffplayout/src/player/filter/mod.rs index 2aa7a1a5..e6413307 100644 --- a/ffplayout/src/player/filter/mod.rs +++ b/ffplayout/src/player/filter/mod.rs @@ -676,15 +676,13 @@ pub fn filter_chains( || Path::new(&node.audio).is_file() { extend_audio(node, &mut filters, i, config); - } else if node.unit == Decoder { - if !node.source.contains("color=c=") { - warn!(target: Target::file_mail(), channel = config.general.channel_id; - "Missing audio track (id {i}) from {}", - node.source - ); + } else if node.unit == Decoder && !node.source.contains("color=c=") { + warn!(target: Target::file_mail(), channel = config.general.channel_id; + "Missing audio track (id {i}) from {}", + node.source + ); - add_audio(node, &mut filters, i, config); - } + add_audio(node, &mut filters, i, config); } // add at least anull filter, for correct filter construction, diff --git a/ffplayout/src/utils/args_parse.rs b/ffplayout/src/utils/args_parse.rs index 25d2b764..75cc0783 100644 --- a/ffplayout/src/utils/args_parse.rs +++ b/ffplayout/src/utils/args_parse.rs @@ -190,7 +190,7 @@ pub async fn run_args(pool: &Pool) -> Result<(), i32> { let mut args = ARGS.clone(); if args.dump_advanced.is_none() && args.dump_config.is_none() { - if let Err(e) = handles::db_migrate(&pool).await { + if let Err(e) = handles::db_migrate(pool).await { panic!("{e}"); }; } From e763f82edcc9862988f4e6409f992c7044822d3c Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 4 Jul 2024 15:37:44 +0200 Subject: [PATCH 8/9] fix hls path on channels --- ffplayout/src/utils/channels.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/ffplayout/src/utils/channels.rs b/ffplayout/src/utils/channels.rs index 1d61a728..de71fc65 100644 --- a/ffplayout/src/utils/channels.rs +++ b/ffplayout/src/utils/channels.rs @@ -1,4 +1,5 @@ use std::{ + ffi::OsStr, io, path::Path, sync::{Arc, Mutex}, @@ -33,7 +34,18 @@ fn preview_url(url: &str, id: i32) -> String { if let Some(parent) = url_path.parent() { if let Some(filename) = url_path.file_name() { - let new_path = parent.join(id.to_string()).join(filename); + let new_path = if parent + .file_name() + .unwrap_or_else(|| OsStr::new("0")) + .to_string_lossy() + .to_string() + .parse::() + .is_ok() + { + parent.join(filename) + } else { + parent.join(id.to_string()).join(filename) + }; if let Some(new_url) = new_path.to_str() { return new_url.to_string(); @@ -55,7 +67,7 @@ pub async fn create_channel( handles::update_channel(conn, channel.id, channel.clone()).await?; - let output_param = format!("-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 {0}/stream-%d.ts {0}/stream.m3u8", channel.id); + 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(); handles::insert_advanced_configuration(conn, channel.id).await?; handles::insert_configuration(conn, channel.id, output_param).await?; From 250a2fc427ded3915c76a51254d826e15cb12321 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Thu, 4 Jul 2024 16:41:55 +0200 Subject: [PATCH 9/9] reload player on error --- frontend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend b/frontend index e8fd6f65..18518dd5 160000 --- a/frontend +++ b/frontend @@ -1 +1 @@ -Subproject commit e8fd6f65bf255c55c1ca509f9fe59c2c7875c394 +Subproject commit 18518dd53acb2b78c2bf7f3ac199f038fc225fd9