Merge pull request #745 from jb-alvarado/master

This commit is contained in:
jb-alvarado 2024-09-10 17:47:37 +02:00 committed by GitHub
commit b582186bff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 87 additions and 76 deletions

8
Cargo.lock generated
View File

@ -2924,9 +2924,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.35" version = "0.38.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36"
dependencies = [ dependencies = [
"bitflags 2.6.0", "bitflags 2.6.0",
"errno", "errno",
@ -3842,9 +3842,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-stream" name = "tokio-stream"
version = "0.1.15" version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"pin-project-lite", "pin-project-lite",

View File

@ -1,6 +1,6 @@
FROM alpine:latest FROM alpine:latest
ARG FFPLAYOUT_VERSION=0.24.0-beta2 ARG FFPLAYOUT_VERSION=0.24.0-beta4
ARG SHARED_STORAGE=false ARG SHARED_STORAGE=false
ENV DB=/db ENV DB=/db
@ -12,7 +12,7 @@ COPY <<-EOT /run.sh
#!/bin/sh #!/bin/sh
if [ ! -f /db/ffplayout.db ]; then if [ ! -f /db/ffplayout.db ]; then
ffplayout -u admin -p admin -m contact@example.com --storage-root "/tv-media" --playlist-root "/playlists" --public-root "/public" --log-path "/logging" --shared-storage ffplayout -u admin -p admin -m contact@example.com --storage-root "/tv-media" --playlist-root "/playlists" --public "/public" --log-path "/logging" --shared-storage
fi fi
/usr/bin/ffplayout -l "0.0.0.0:8787" /usr/bin/ffplayout -l "0.0.0.0:8787"

View File

@ -1,6 +1,6 @@
FROM alpine:latest FROM alpine:latest
ARG FFPLAYOUT_VERSION=0.24.0-beta2 ARG FFPLAYOUT_VERSION=0.24.0-beta4
ARG SHARED_STORAGE=false ARG SHARED_STORAGE=false
ENV DB=/db ENV DB=/db
@ -14,7 +14,7 @@ COPY <<-EOT /run.sh
#!/bin/sh #!/bin/sh
if [ ! -f /db/ffplayout.db ]; then if [ ! -f /db/ffplayout.db ]; then
ffplayout -u admin -p admin -m contact@example.com --storage-root "/tv-media" --playlist-root "/playlists" --public-root "/public" --log-path "/logging" --shared-storage ffplayout -u admin -p admin -m contact@example.com --storage-root "/tv-media" --playlist-root "/playlists" --public "/public" --log-path "/logging" --shared-storage
fi fi
/usr/bin/ffplayout -l "0.0.0.0:8787" /usr/bin/ffplayout -l "0.0.0.0:8787"

View File

@ -1,6 +1,6 @@
FROM nvidia/cuda:12.5.0-runtime-rockylinux9 FROM nvidia/cuda:12.5.0-runtime-rockylinux9
ARG FFPLAYOUT_VERSION=0.24.0-beta2 ARG FFPLAYOUT_VERSION=0.24.0-beta4
ARG SHARED_STORAGE=false ARG SHARED_STORAGE=false
ENV DB=/db ENV DB=/db
@ -204,7 +204,7 @@ COPY <<-EOT /run.sh
#!/bin/sh #!/bin/sh
if [ ! -f /db/ffplayout.db ]; then if [ ! -f /db/ffplayout.db ]; then
ffplayout -u admin -p admin -m contact@example.com --storage-root "/tv-media" --playlist-root "/playlists" --public-root "/public" --log-path "/logging" --shared-storage ffplayout -u admin -p admin -m contact@example.com --storage-root "/tv-media" --playlist-root "/playlists" --public "/public" --log-path "/logging" --shared-storage
fi fi
/usr/bin/ffplayout -l "0.0.0.0:8787" /usr/bin/ffplayout -l "0.0.0.0:8787"

View File

@ -52,10 +52,10 @@ use crate::utils::{
playlist::{delete_playlist, generate_playlist, read_playlist, write_playlist}, playlist::{delete_playlist, generate_playlist, read_playlist, write_playlist},
public_path, read_log_file, system, TextFilter, public_path, read_log_file, system, TextFilter,
}; };
use crate::vec_strings;
use crate::{ use crate::{
api::auth::{create_jwt, Claims}, api::auth::{create_jwt, Claims},
utils::advanced_config::AdvancedConfig, utils::advanced_config::AdvancedConfig,
vec_strings,
}; };
use crate::{ use crate::{
db::{ db::{
@ -1309,24 +1309,21 @@ async fn get_file(
/// Can be used for HLS Playlist and other static files in public folder /// Can be used for HLS Playlist and other static files in public folder
/// ///
/// ```BASH /// ```BASH
/// curl -X GET http://127.0.0.1:8787/live/1/stream.m3u8 /// curl -X GET http://127.0.0.1:8787/1/live/stream.m3u8
/// ``` /// ```
#[get("/{public:live|preview|public}/{id}/{file_stem:.*}")] #[get("/{id}/{public:live|preview|public}/{file_stem:.*}")]
async fn get_public( async fn get_public(
path: web::Path<(String, i32, String)>, path: web::Path<(i32, String, String)>,
controllers: web::Data<Mutex<ChannelController>>, controllers: web::Data<Mutex<ChannelController>>,
) -> Result<actix_files::NamedFile, ServiceError> { ) -> Result<actix_files::NamedFile, ServiceError> {
let (public, id, file_stem) = path.into_inner(); let (id, public, file_stem) = path.into_inner();
let public_path = public_path();
let absolute_path = if file_stem.ends_with(".ts") || file_stem.ends_with(".m3u8") { let absolute_path = if file_stem.ends_with(".ts") || file_stem.ends_with(".m3u8") {
let manager = controllers.lock().unwrap().get(id).unwrap(); let manager = controllers.lock().unwrap().get(id).unwrap();
let config = manager.config.lock().unwrap(); let config = manager.config.lock().unwrap();
config.channel.hls_path.join(public) config.channel.hls_path.join(public)
} else if public_path.is_absolute() {
public_path.to_path_buf()
} else { } else {
env::current_dir()?.join(public_path) public_path()
} }
.clean(); .clean();

View File

@ -1,6 +1,5 @@
use std::{ use std::{
collections::HashSet, collections::HashSet,
env,
fs::File, fs::File,
io, io,
process::exit, process::exit,
@ -8,16 +7,16 @@ use std::{
thread, thread,
}; };
use actix_files::Files;
use actix_web::{middleware::Logger, web, App, HttpServer}; use actix_web::{middleware::Logger, web, App, HttpServer};
use actix_web_httpauth::middleware::HttpAuthentication; use actix_web_httpauth::middleware::HttpAuthentication;
#[cfg(any(debug_assertions, not(feature = "embed_frontend")))]
use actix_files::Files;
#[cfg(all(not(debug_assertions), feature = "embed_frontend"))] #[cfg(all(not(debug_assertions), feature = "embed_frontend"))]
use actix_web_static_files::ResourceFiles; use actix_web_static_files::ResourceFiles;
use log::*; use log::*;
use path_clean::PathClean;
use ffplayout::{ use ffplayout::{
api::routes::*, api::routes::*,
@ -105,12 +104,14 @@ async fn main() -> std::io::Result<()> {
info!("Running ffplayout API, listen on http://{conn}"); info!("Running ffplayout API, listen on http://{conn}");
let db_clone = pool.clone();
// no 'allow origin' here, give it to the reverse proxy // no 'allow origin' here, give it to the reverse proxy
HttpServer::new(move || { HttpServer::new(move || {
let queues = mail_queues.clone(); let queues = mail_queues.clone();
let auth = HttpAuthentication::bearer(validator); let auth = HttpAuthentication::bearer(validator);
let db_pool = web::Data::new(pool.clone()); let db_pool = web::Data::new(db_clone.clone());
// Customize logging format to get IP though proxies. // Customize logging format to get IP though proxies.
let logger = Logger::new("%{r}a \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\" %T") let logger = Logger::new("%{r}a \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\" %T")
.exclude_regex(r"/_nuxt/*"); .exclude_regex(r"/_nuxt/*");
@ -171,18 +172,7 @@ async fn main() -> std::io::Result<()> {
) )
.service(get_file); .service(get_file);
if let Some(public) = &ARGS.public { if ARGS.public.is_none() {
// When public path is set as argument use this path for serving extra static files,
// is useful for HLS stream etc.
let absolute_path = if public.is_absolute() {
public.to_path_buf()
} else {
env::current_dir().unwrap_or_default().join(public)
}
.clean();
web_app = web_app.service(Files::new("/", absolute_path));
} else {
// When no public path is given as argument, use predefine keywords in path, // When no public path is given as argument, use predefine keywords in path,
// like /live; /preview; /public, or HLS extensions to recognize file should get from public folder // like /live; /preview; /public, or HLS extensions to recognize file should get from public folder
web_app = web_app.service(get_public); web_app = web_app.service(get_public);
@ -283,5 +273,7 @@ async fn main() -> std::io::Result<()> {
channel_ctl.stop_all(); channel_ctl.stop_all();
} }
pool.close().await;
Ok(()) Ok(())
} }

View File

@ -264,7 +264,7 @@ impl ChannelManager {
self.run_count.fetch_sub(1, Ordering::SeqCst); self.run_count.fetch_sub(1, Ordering::SeqCst);
let pool = self.db_pool.clone().unwrap(); let pool = self.db_pool.clone().unwrap();
let channel_id = self.channel.lock().unwrap().id; let channel_id = self.channel.lock().unwrap().id;
debug!(target: Target::all(), channel = channel_id; "Stop all child processes from channel {channel_id}"); debug!(target: Target::all(), channel = channel_id; "Deactivate playout and stop all child processes from channel: <yellow>{channel_id}</>");
if let Err(e) = handles::update_player(&pool, channel_id, false).await { if let Err(e) = handles::update_player(&pool, channel_id, false).await {
error!(target: Target::all(), channel = channel_id; "Unable write to player status: {e}"); error!(target: Target::all(), channel = channel_id; "Unable write to player status: {e}");
@ -273,7 +273,7 @@ impl ChannelManager {
for unit in [Decoder, Encoder, Ingest] { for unit in [Decoder, Encoder, Ingest] {
if let Err(e) = self.stop(unit) { if let Err(e) = self.stop(unit) {
if !e.to_string().contains("exited process") { if !e.to_string().contains("exited process") {
error!("{e}") error!(target: Target::all(), channel = channel_id; "{e}")
} }
} }
} }
@ -286,7 +286,7 @@ impl ChannelManager {
self.ingest_is_running.store(false, Ordering::SeqCst); self.ingest_is_running.store(false, Ordering::SeqCst);
self.run_count.fetch_sub(1, Ordering::SeqCst); self.run_count.fetch_sub(1, Ordering::SeqCst);
let channel_id = self.channel.lock().unwrap().id; let channel_id = self.channel.lock().unwrap().id;
debug!(target: Target::all(), channel = channel_id; "Stop all child processes from channel {channel_id}"); debug!(target: Target::all(), channel = channel_id; "Stop all child processes from channel: <yellow>{channel_id}</>");
for unit in [Decoder, Encoder, Ingest] { for unit in [Decoder, Encoder, Ingest] {
if let Err(e) = self.stop(unit) { if let Err(e) = self.stop(unit) {

View File

@ -356,12 +356,23 @@ impl CurrentProgram {
} }
fn recalculate_begin(&mut self, extend: bool) { fn recalculate_begin(&mut self, extend: bool) {
debug!(target: Target::file_mail(), channel = self.id; "Infinit playlist reaches end, recalculate clip begins."); debug!(target: Target::file_mail(), channel = self.id; "Infinit playlist reaches end, recalculate clip begins. Extend: <yellow>{extend}</>");
let mut time_sec = time_in_seconds(); let mut time_sec = time_in_seconds();
if extend { if extend {
time_sec = self.start_sec + self.json_playlist.length.unwrap(); // Calculate the elapsed time since the playlist start
let elapsed_sec = if time_sec >= self.start_sec {
time_sec - self.start_sec
} else {
time_sec + 86400.0 - self.start_sec
};
// Time passed within the current playlist loop
let time_in_current_loop = elapsed_sec % self.json_playlist.length.unwrap();
// Adjust the start time so that the playlist starts at the correct point in time
time_sec -= time_in_current_loop;
} }
self.json_playlist.start_sec = Some(time_sec); self.json_playlist.start_sec = Some(time_sec);

View File

@ -86,9 +86,6 @@ pub struct Args {
#[clap(long, help = "List available channel ids")] #[clap(long, help = "List available channel ids")]
pub list_channels: bool, pub list_channels: bool,
#[clap(long, env, help = "path to public files")]
pub public: Option<PathBuf>,
#[clap(short, env, long, help = "Listen on IP:PORT, like: 127.0.0.1:8787")] #[clap(short, env, long, help = "Listen on IP:PORT, like: 127.0.0.1:8787")]
pub listen: Option<String>, pub listen: Option<String>,
@ -123,8 +120,8 @@ pub struct Args {
#[clap(long, env, help = "Log to console")] #[clap(long, env, help = "Log to console")]
pub log_to_console: bool, pub log_to_console: bool,
#[clap(long, env, help = "Public (HLS) output path")] #[clap(long, env, help = "Path to public files, also HLS playlists")]
pub public_root: Option<String>, pub public: Option<String>,
#[clap(long, env, help = "Playlist root path")] #[clap(long, env, help = "Playlist root path")]
pub playlist_root: Option<String>, pub playlist_root: Option<String>,
@ -438,7 +435,7 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
if !args.init if !args.init
&& args.storage_root.is_some() && args.storage_root.is_some()
&& args.playlist_root.is_some() && args.playlist_root.is_some()
&& args.public_root.is_some() && args.public.is_some()
&& args.log_path.is_some() && args.log_path.is_some()
{ {
error_code = 0; error_code = 0;
@ -448,7 +445,7 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
secret: None, secret: None,
logging_path: args.log_path.unwrap().to_string_lossy().to_string(), logging_path: args.log_path.unwrap().to_string_lossy().to_string(),
playlist_root: args.playlist_root.unwrap(), playlist_root: args.playlist_root.unwrap(),
public_root: args.public_root.unwrap(), public_root: args.public.unwrap(),
storage_root: args.storage_root.unwrap(), storage_root: args.storage_root.unwrap(),
shared_storage: args.shared_storage, shared_storage: args.shared_storage,
}; };
@ -523,19 +520,6 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
}; };
} }
if let Some(id) = ARGS.dump_config {
match PlayoutConfig::dump(pool, id).await {
Ok(_) => {
println!("Dump config to: ffplayout_{id}.toml");
error_code = 0;
}
Err(e) => {
eprintln!("Dump config: {e}");
error_code = 1;
}
};
}
if let Some(id) = ARGS.dump_advanced { if let Some(id) = ARGS.dump_advanced {
match AdvancedConfig::dump(pool, id).await { match AdvancedConfig::dump(pool, id).await {
Ok(_) => { Ok(_) => {

View File

@ -407,14 +407,14 @@ impl Playlist {
#[derive(Debug, Default, Clone, Deserialize, Serialize)] #[derive(Debug, Default, Clone, Deserialize, Serialize)]
pub struct Storage { pub struct Storage {
pub help_text: String, pub help_text: String,
#[serde(skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
pub path: PathBuf, pub path: PathBuf,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
pub paths: Vec<PathBuf>, pub paths: Vec<PathBuf>,
pub filler: PathBuf, pub filler: PathBuf,
pub extensions: Vec<String>, pub extensions: Vec<String>,
pub shuffle: bool, pub shuffle: bool,
#[serde(skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
pub shared_storage: bool, pub shared_storage: bool,
} }
@ -603,10 +603,14 @@ impl PlayoutConfig {
playlist.length_sec = Some(86400.0); playlist.length_sec = Some(86400.0);
} }
if processing.add_logo && !Path::new(&processing.logo).is_file() { let (logo_path, _, _) = norm_abs_path(&channel.storage_path, &processing.logo)?;
if processing.add_logo && !logo_path.is_file() {
processing.add_logo = false; processing.add_logo = false;
} }
processing.logo = logo_path.to_string_lossy().to_string();
if processing.audio_tracks < 1 { if processing.audio_tracks < 1 {
processing.audio_tracks = 1 processing.audio_tracks = 1
} }
@ -711,6 +715,9 @@ impl PlayoutConfig {
text.node_pos = None; text.node_pos = None;
} }
let (text_path, _, _) = norm_abs_path(&channel.storage_path, &text.fontfile)?;
text.fontfile = text_path.to_string_lossy().to_string();
Ok(Self { Ok(Self {
channel, channel,
advanced, advanced,
@ -859,6 +866,12 @@ 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 let Some(volume) = args.volume { if let Some(volume) = args.volume {
config.processing.volume = volume; config.processing.volume = volume;
} }

View File

@ -305,6 +305,10 @@ pub fn log_file_path() -> PathBuf {
.clone() .clone()
.unwrap_or(PathBuf::from(&config.logging_path)); .unwrap_or(PathBuf::from(&config.logging_path));
if !log_path.is_absolute() {
log_path = env::current_dir().unwrap().join(log_path);
}
if !log_path.is_dir() { if !log_path.is_dir() {
log_path = env::current_dir().unwrap(); log_path = env::current_dir().unwrap();
} }

View File

@ -30,6 +30,7 @@ pub mod playlist;
pub mod system; pub mod system;
pub mod task_runner; pub mod task_runner;
use crate::db::models::GlobalSettings;
use crate::player::utils::time_to_sec; use crate::player::utils::time_to_sec;
use crate::utils::{errors::ServiceError, logging::log_file_path}; use crate::utils::{errors::ServiceError, logging::log_file_path};
use crate::ARGS; use crate::ARGS;
@ -195,19 +196,28 @@ pub fn db_path() -> Result<&'static str, Box<dyn std::error::Error>> {
} }
pub fn public_path() -> PathBuf { pub fn public_path() -> PathBuf {
let path = PathBuf::from("./ffplayout-frontend/.output/public/"); let config = GlobalSettings::global();
let dev_path = env::current_dir()
.unwrap_or_default()
.join("frontend/.output/public/");
let mut public_path = PathBuf::from(&config.public_root);
if cfg!(debug_assertions) && path.is_dir() { if let Some(p) = &ARGS.public {
return path; // When public path is set as argument use this path for serving static files.
// Works only when feature embed_frontend is not set.
let public = PathBuf::from(p);
public_path = if public.is_absolute() {
public.to_path_buf()
} else {
env::current_dir().unwrap_or_default().join(public)
}
.clean();
} else if cfg!(debug_assertions) && dev_path.is_dir() {
public_path = dev_path;
} }
let path = PathBuf::from("/usr/share/ffplayout/public/"); public_path
if path.is_dir() {
return path;
}
PathBuf::from("./public/")
} }
pub async fn read_log_file(channel_id: &i32, date: &str) -> Result<String, ServiceError> { pub async fn read_log_file(channel_id: &i32, date: &str) -> Result<String, ServiceError> {

@ -1 +1 @@
Subproject commit 48f123bf6ad136968495e9e5e22249b8ca5ef192 Subproject commit bb7446850c683c3a4465c336e348476d3c8bb49c

View File

@ -188,7 +188,7 @@ INSERT INTO
VALUES VALUES
( (
'Channel 1', 'Channel 1',
'http://127.0.0.1:8787/live/1/stream.m3u8', 'http://127.0.0.1:8787/1/live/stream.m3u8',
'jpg,jpeg,png', 'jpg,jpeg,png',
0 0
); );