Merge pull request #745 from jb-alvarado/master
This commit is contained in:
commit
b582186bff
8
Cargo.lock
generated
8
Cargo.lock
generated
@ -2924,9 +2924,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.35"
|
||||
version = "0.38.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f"
|
||||
checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"errno",
|
||||
@ -3842,9 +3842,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.15"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af"
|
||||
checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
|
@ -1,6 +1,6 @@
|
||||
FROM alpine:latest
|
||||
|
||||
ARG FFPLAYOUT_VERSION=0.24.0-beta2
|
||||
ARG FFPLAYOUT_VERSION=0.24.0-beta4
|
||||
ARG SHARED_STORAGE=false
|
||||
|
||||
ENV DB=/db
|
||||
@ -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-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
|
||||
|
||||
/usr/bin/ffplayout -l "0.0.0.0:8787"
|
||||
|
@ -1,6 +1,6 @@
|
||||
FROM alpine:latest
|
||||
|
||||
ARG FFPLAYOUT_VERSION=0.24.0-beta2
|
||||
ARG FFPLAYOUT_VERSION=0.24.0-beta4
|
||||
ARG SHARED_STORAGE=false
|
||||
|
||||
ENV DB=/db
|
||||
@ -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-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
|
||||
|
||||
/usr/bin/ffplayout -l "0.0.0.0:8787"
|
||||
|
@ -1,6 +1,6 @@
|
||||
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
|
||||
|
||||
ENV DB=/db
|
||||
@ -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-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
|
||||
|
||||
/usr/bin/ffplayout -l "0.0.0.0:8787"
|
||||
|
@ -52,10 +52,10 @@ use crate::utils::{
|
||||
playlist::{delete_playlist, generate_playlist, read_playlist, write_playlist},
|
||||
public_path, read_log_file, system, TextFilter,
|
||||
};
|
||||
use crate::vec_strings;
|
||||
use crate::{
|
||||
api::auth::{create_jwt, Claims},
|
||||
utils::advanced_config::AdvancedConfig,
|
||||
vec_strings,
|
||||
};
|
||||
use crate::{
|
||||
db::{
|
||||
@ -1309,24 +1309,21 @@ async fn get_file(
|
||||
/// Can be used for HLS Playlist and other static files in public folder
|
||||
///
|
||||
/// ```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(
|
||||
path: web::Path<(String, i32, String)>,
|
||||
path: web::Path<(i32, String, String)>,
|
||||
controllers: web::Data<Mutex<ChannelController>>,
|
||||
) -> Result<actix_files::NamedFile, ServiceError> {
|
||||
let (public, id, file_stem) = path.into_inner();
|
||||
let public_path = public_path();
|
||||
let (id, public, file_stem) = path.into_inner();
|
||||
|
||||
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.channel.hls_path.join(public)
|
||||
} else if public_path.is_absolute() {
|
||||
public_path.to_path_buf()
|
||||
} else {
|
||||
env::current_dir()?.join(public_path)
|
||||
public_path()
|
||||
}
|
||||
.clean();
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
env,
|
||||
fs::File,
|
||||
io,
|
||||
process::exit,
|
||||
@ -8,16 +7,16 @@ use std::{
|
||||
thread,
|
||||
};
|
||||
|
||||
use actix_files::Files;
|
||||
use actix_web::{middleware::Logger, web, App, HttpServer};
|
||||
|
||||
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"))]
|
||||
use actix_web_static_files::ResourceFiles;
|
||||
|
||||
use log::*;
|
||||
use path_clean::PathClean;
|
||||
|
||||
use ffplayout::{
|
||||
api::routes::*,
|
||||
@ -105,12 +104,14 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
info!("Running ffplayout API, listen on http://{conn}");
|
||||
|
||||
let db_clone = pool.clone();
|
||||
|
||||
// no 'allow origin' here, give it to the reverse proxy
|
||||
HttpServer::new(move || {
|
||||
let queues = mail_queues.clone();
|
||||
|
||||
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.
|
||||
let logger = Logger::new("%{r}a \"%r\" %s %b \"%{Referer}i\" \"%{User-Agent}i\" %T")
|
||||
.exclude_regex(r"/_nuxt/*");
|
||||
@ -171,18 +172,7 @@ async fn main() -> std::io::Result<()> {
|
||||
)
|
||||
.service(get_file);
|
||||
|
||||
if let Some(public) = &ARGS.public {
|
||||
// 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 {
|
||||
if ARGS.public.is_none() {
|
||||
// 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
|
||||
web_app = web_app.service(get_public);
|
||||
@ -283,5 +273,7 @@ async fn main() -> std::io::Result<()> {
|
||||
channel_ctl.stop_all();
|
||||
}
|
||||
|
||||
pool.close().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -264,7 +264,7 @@ impl ChannelManager {
|
||||
self.run_count.fetch_sub(1, Ordering::SeqCst);
|
||||
let pool = self.db_pool.clone().unwrap();
|
||||
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 {
|
||||
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] {
|
||||
if let Err(e) = self.stop(unit) {
|
||||
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.run_count.fetch_sub(1, Ordering::SeqCst);
|
||||
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] {
|
||||
if let Err(e) = self.stop(unit) {
|
||||
|
@ -356,12 +356,23 @@ impl CurrentProgram {
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
|
@ -86,9 +86,6 @@ pub struct Args {
|
||||
#[clap(long, help = "List available channel ids")]
|
||||
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")]
|
||||
pub listen: Option<String>,
|
||||
|
||||
@ -123,8 +120,8 @@ pub struct Args {
|
||||
#[clap(long, env, help = "Log to console")]
|
||||
pub log_to_console: bool,
|
||||
|
||||
#[clap(long, env, help = "Public (HLS) output path")]
|
||||
pub public_root: Option<String>,
|
||||
#[clap(long, env, help = "Path to public files, also HLS playlists")]
|
||||
pub public: Option<String>,
|
||||
|
||||
#[clap(long, env, help = "Playlist root path")]
|
||||
pub playlist_root: Option<String>,
|
||||
@ -438,7 +435,7 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
|
||||
if !args.init
|
||||
&& args.storage_root.is_some()
|
||||
&& args.playlist_root.is_some()
|
||||
&& args.public_root.is_some()
|
||||
&& args.public.is_some()
|
||||
&& args.log_path.is_some()
|
||||
{
|
||||
error_code = 0;
|
||||
@ -448,7 +445,7 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
|
||||
secret: None,
|
||||
logging_path: args.log_path.unwrap().to_string_lossy().to_string(),
|
||||
playlist_root: args.playlist_root.unwrap(),
|
||||
public_root: args.public_root.unwrap(),
|
||||
public_root: args.public.unwrap(),
|
||||
storage_root: args.storage_root.unwrap(),
|
||||
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 {
|
||||
match AdvancedConfig::dump(pool, id).await {
|
||||
Ok(_) => {
|
||||
|
@ -407,14 +407,14 @@ impl Playlist {
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize)]
|
||||
pub struct Storage {
|
||||
pub help_text: String,
|
||||
#[serde(skip_deserializing)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub path: PathBuf,
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub paths: Vec<PathBuf>,
|
||||
pub filler: PathBuf,
|
||||
pub extensions: Vec<String>,
|
||||
pub shuffle: bool,
|
||||
#[serde(skip_deserializing)]
|
||||
#[serde(skip_serializing, skip_deserializing)]
|
||||
pub shared_storage: bool,
|
||||
}
|
||||
|
||||
@ -603,10 +603,14 @@ impl PlayoutConfig {
|
||||
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.logo = logo_path.to_string_lossy().to_string();
|
||||
|
||||
if processing.audio_tracks < 1 {
|
||||
processing.audio_tracks = 1
|
||||
}
|
||||
@ -711,6 +715,9 @@ impl PlayoutConfig {
|
||||
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 {
|
||||
channel,
|
||||
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 {
|
||||
config.processing.volume = volume;
|
||||
}
|
||||
|
@ -305,6 +305,10 @@ pub fn log_file_path() -> PathBuf {
|
||||
.clone()
|
||||
.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() {
|
||||
log_path = env::current_dir().unwrap();
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ pub mod playlist;
|
||||
pub mod system;
|
||||
pub mod task_runner;
|
||||
|
||||
use crate::db::models::GlobalSettings;
|
||||
use crate::player::utils::time_to_sec;
|
||||
use crate::utils::{errors::ServiceError, logging::log_file_path};
|
||||
use crate::ARGS;
|
||||
@ -195,19 +196,28 @@ pub fn db_path() -> Result<&'static str, Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
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() {
|
||||
return path;
|
||||
if let Some(p) = &ARGS.public {
|
||||
// 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/");
|
||||
|
||||
if path.is_dir() {
|
||||
return path;
|
||||
}
|
||||
|
||||
PathBuf::from("./public/")
|
||||
public_path
|
||||
}
|
||||
|
||||
pub async fn read_log_file(channel_id: &i32, date: &str) -> Result<String, ServiceError> {
|
||||
|
2
frontend
2
frontend
@ -1 +1 @@
|
||||
Subproject commit 48f123bf6ad136968495e9e5e22249b8ca5ef192
|
||||
Subproject commit bb7446850c683c3a4465c336e348476d3c8bb49c
|
@ -188,7 +188,7 @@ INSERT INTO
|
||||
VALUES
|
||||
(
|
||||
'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',
|
||||
0
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user