fix channel delete/create, copy assets to storage, start with vtt support

This commit is contained in:
jb-alvarado 2024-09-25 12:30:27 +02:00
parent da970aa4ff
commit 93e753129e
17 changed files with 370 additions and 81 deletions

View File

@ -48,12 +48,13 @@
}, },
"cSpell.words": [ "cSpell.words": [
"actix", "actix",
"canonicalize",
"ffpengine", "ffpengine",
"flexi", "flexi",
"lettre", "lettre",
"libc", "libc",
"nuxt",
"neli", "neli",
"nuxt",
"paris", "paris",
"reqwest", "reqwest",
"rsplit", "rsplit",

View File

@ -120,6 +120,9 @@ If you are in playlist mode and move backwards or forwards in time, the time shi
(Endless) streaming over multiple days will only work if config has a **day_start** value and the **length** value is **24 hours**. If you only need a few hours for each day, use a *cron* job or something similar. (Endless) streaming over multiple days will only work if config has a **day_start** value and the **length** value is **24 hours**. If you only need a few hours for each day, use a *cron* job or something similar.
## Note
This project includes the DejaVu font, which are licensed under the [Bitstream Vera Fonts License](/assets/FONT_LICENSE.txt).
----- -----
## Sponsoring ## Sponsoring

BIN
assets/DejaVuSans.ttf Normal file

Binary file not shown.

187
assets/FONT_LICENSE.txt Normal file
View File

@ -0,0 +1,187 @@
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below)
Bitstream Vera Fonts Copyright
------------------------------
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is
a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of the fonts accompanying this license ("Fonts") and associated
documentation files (the "Font Software"), to reproduce and distribute the
Font Software, including without limitation the rights to use, copy, merge,
publish, distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to the
following conditions:
The above copyright and trademark notices and this permission notice shall
be included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular
the designs of glyphs or characters in the Fonts may be modified and
additional glyphs or characters may be added to the Fonts, only if the fonts
are renamed to names not containing either the words "Bitstream" or the word
"Vera".
This License becomes null and void to the extent applicable to Fonts or Font
Software that has been modified and is distributed under the "Bitstream
Vera" names.
The Font Software may be sold as part of a larger software package but no
copy of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING
ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE
FONT SOFTWARE.
Except as contained in this notice, the names of Gnome, the Gnome
Foundation, and Bitstream Inc., shall not be used in advertising or
otherwise to promote the sale, use or other dealings in this Font Software
without prior written authorization from the Gnome Foundation or Bitstream
Inc., respectively. For further information, contact: fonts at gnome dot
org.
Arev Fonts Copyright
------------------------------
Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining
a copy of the fonts accompanying this license ("Fonts") and
associated documentation files (the "Font Software"), to reproduce
and distribute the modifications to the Bitstream Vera Font Software,
including without limitation the rights to use, copy, merge, publish,
distribute, and/or sell copies of the Font Software, and to permit
persons to whom the Font Software is furnished to do so, subject to
the following conditions:
The above copyright and trademark notices and this permission notice
shall be included in all copies of one or more of the Font Software
typefaces.
The Font Software may be modified, altered, or added to, and in
particular the designs of glyphs or characters in the Fonts may be
modified and additional glyphs or characters may be added to the
Fonts, only if the fonts are renamed to names not containing either
the words "Tavmjong Bah" or the word "Arev".
This License becomes null and void to the extent applicable to Fonts
or Font Software that has been modified and is distributed under the
"Tavmjong Bah Arev" names.
The Font Software may be sold as part of a larger software package but
no copy of one or more of the Font Software typefaces may be sold by
itself.
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL
TAVMJONG BAH BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the name of Tavmjong Bah shall not
be used in advertising or otherwise to promote the sale, use or other
dealings in this Font Software without prior written authorization
from Tavmjong Bah. For further information, contact: tavmjong @ free
. fr.
TeX Gyre DJV Math
-----------------
Fonts are (c) Bitstream (see below). DejaVu changes are in public domain.
Math extensions done by B. Jackowski, P. Strzelczyk and P. Pianowski
(on behalf of TeX users groups) are in public domain.
Letters imported from Euler Fraktur from AMSfonts are (c) American
Mathematical Society (see below).
Bitstream Vera Fonts Copyright
Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera
is a trademark of Bitstream, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of the fonts accompanying this license (“Fonts”) and associated
documentation
files (the “Font Software”), to reproduce and distribute the Font Software,
including without limitation the rights to use, copy, merge, publish,
distribute,
and/or sell copies of the Font Software, and to permit persons to whom
the Font Software is furnished to do so, subject to the following
conditions:
The above copyright and trademark notices and this permission notice
shall be
included in all copies of one or more of the Font Software typefaces.
The Font Software may be modified, altered, or added to, and in particular
the designs of glyphs or characters in the Fonts may be modified and
additional
glyphs or characters may be added to the Fonts, only if the fonts are
renamed
to names not containing either the words “Bitstream” or the word “Vera”.
This License becomes null and void to the extent applicable to Fonts or
Font Software
that has been modified and is distributed under the “Bitstream Vera”
names.
The Font Software may be sold as part of a larger software package but
no copy
of one or more of the Font Software typefaces may be sold by itself.
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM OR THE GNOME
FOUNDATION
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL,
SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN
ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR
INABILITY TO USE
THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
Except as contained in this notice, the names of GNOME, the GNOME
Foundation,
and Bitstream Inc., shall not be used in advertising or otherwise to promote
the sale, use or other dealings in this Font Software without prior written
authorization from the GNOME Foundation or Bitstream Inc., respectively.
For further information, contact: fonts at gnome dot org.
AMSFonts (v. 2.2) copyright
The PostScript Type 1 implementation of the AMSFonts produced by and
previously distributed by Blue Sky Research and Y&Y, Inc. are now freely
available for general use. This has been accomplished through the
cooperation
of a consortium of scientific publishers with Blue Sky Research and Y&Y.
Members of this consortium include:
Elsevier Science IBM Corporation Society for Industrial and Applied
Mathematics (SIAM) Springer-Verlag American Mathematical Society (AMS)
In order to assure the authenticity of these fonts, copyright will be
held by
the American Mathematical Society. This is not meant to restrict in any way
the legitimate use of the fonts, such as (but not limited to) electronic
distribution of documents containing these fonts, inclusion of these fonts
into other public domain or commercial font collections or computer
applications, use of the outline data to create derivative fonts and/or
faces, etc. However, the AMS does require that the AMS copyright notice be
removed from any derivative versions of the fonts which have been altered in
any way. In addition, to ensure the fidelity of TeX documents using Computer
Modern fonts, Professor Donald Knuth, creator of the Computer Modern faces,
has requested that any alterations which yield different font metrics be
given a different name.
$Id$

1
assets/dummy.vtt Normal file
View File

@ -0,0 +1 @@
WEBVTT

View File

@ -78,7 +78,6 @@ static-files = "0.2"
name = "ffplayout" name = "ffplayout"
path = "src/main.rs" path = "src/main.rs"
# DEBIAN DEB PACKAGE # DEBIAN DEB PACKAGE
[package.metadata.deb] [package.metadata.deb]
name = "ffplayout" name = "ffplayout"
@ -99,6 +98,21 @@ assets = [
"/lib/systemd/system/", "/lib/systemd/system/",
"644", "644",
], ],
[
"../assets/dummy.vtt",
"/usr/share/ffplayout/",
"644",
],
[
"../assets/DejaVuSans.ttf",
"/usr/share/ffplayout/",
"644",
],
[
"../assets/FONT_LICENSE.txt",
"/usr/share/ffplayout/",
"644",
],
[ [
"../assets/logo.png", "../assets/logo.png",
"/usr/share/ffplayout/", "/usr/share/ffplayout/",
@ -135,6 +149,21 @@ assets = [
"/lib/systemd/system/", "/lib/systemd/system/",
"644", "644",
], ],
[
"../assets/dummy.vtt",
"/usr/share/ffplayout/",
"644",
],
[
"../assets/DejaVuSans.ttf",
"/usr/share/ffplayout/",
"644",
],
[
"../assets/FONT_LICENSE.txt",
"/usr/share/ffplayout/",
"644",
],
[ [
"../assets/logo.png", "../assets/logo.png",
"/usr/share/ffplayout/", "/usr/share/ffplayout/",
@ -157,7 +186,7 @@ assets = [
], ],
] ]
# REHL RPM PACKAGE # RHEL RPM PACKAGE
[package.metadata.generate-rpm] [package.metadata.generate-rpm]
name = "ffplayout" name = "ffplayout"
license = "GPL-3.0" license = "GPL-3.0"
@ -167,6 +196,9 @@ assets = [
{ source = "../README.md", dest = "/usr/share/doc/ffplayout/README", mode = "644" }, { source = "../README.md", dest = "/usr/share/doc/ffplayout/README", mode = "644" },
{ source = "../assets/ffplayout.1.gz", dest = "/usr/share/man/man1/ffplayout.1.gz", mode = "644", doc = true }, { source = "../assets/ffplayout.1.gz", dest = "/usr/share/man/man1/ffplayout.1.gz", mode = "644", doc = true },
{ source = "../LICENSE", dest = "/usr/share/doc/ffplayout/LICENSE", mode = "644" }, { source = "../LICENSE", dest = "/usr/share/doc/ffplayout/LICENSE", mode = "644" },
{ source = "../assets/dummy.vtt", dest = "/usr/share/ffplayout/dummy.vtt", mode = "644" },
{ source = "../assets/DejaVuSans.ttf", dest = "/usr/share/ffplayout/DejaVuSans.ttf", mode = "644" },
{ source = "../assets/FONT_LICENSE.txt", dest = "/usr/share/ffplayout/FONT_LICENSE.txt", mode = "644" },
{ source = "../assets/logo.png", dest = "/usr/share/ffplayout/logo.png", mode = "644" }, { source = "../assets/logo.png", dest = "/usr/share/ffplayout/logo.png", mode = "644" },
{ source = "../assets/ffplayout.conf", dest = "/usr/share/ffplayout/ffplayout.conf.example", mode = "644" }, { source = "../assets/ffplayout.conf", dest = "/usr/share/ffplayout/ffplayout.conf.example", mode = "644" },
{ source = "../debian/postinst", dest = "/usr/share/ffplayout/postinst", mode = "755" }, { source = "../debian/postinst", dest = "/usr/share/ffplayout/postinst", mode = "755" },

View File

@ -480,7 +480,11 @@ async fn patch_channel(
role: AuthDetails<Role>, role: AuthDetails<Role>,
user: web::ReqData<UserMeta>, user: web::ReqData<UserMeta>,
) -> Result<impl Responder, ServiceError> { ) -> Result<impl Responder, ServiceError> {
let manager = controllers.lock().unwrap().get(*id).unwrap(); let manager = controllers
.lock()
.unwrap()
.get(*id)
.ok_or(format!("Channel {id} not found!"))?;
let mut data = data.into_inner(); let mut data = data.into_inner();
if !role.has_authority(&Role::GlobalAdmin) { if !role.has_authority(&Role::GlobalAdmin) {

View File

@ -302,6 +302,10 @@ pub struct Configuration {
pub processing_volume: f64, pub processing_volume: f64,
#[serde(default)] #[serde(default)]
pub processing_filter: String, pub processing_filter: String,
#[serde(default)]
pub processing_vtt_enable: bool,
#[serde(default)]
pub processing_vtt_dummy: Option<String>,
pub ingest_help: String, pub ingest_help: String,
pub ingest_enable: bool, pub ingest_enable: bool,
@ -375,6 +379,8 @@ impl Configuration {
processing_audio_channels: config.processing.audio_channels, processing_audio_channels: config.processing.audio_channels,
processing_volume: config.processing.volume, processing_volume: config.processing.volume,
processing_filter: config.processing.custom_filter, processing_filter: config.processing.custom_filter,
processing_vtt_enable: config.processing.vtt_enable,
processing_vtt_dummy: config.processing.vtt_dummy,
ingest_help: config.ingest.help_text, ingest_help: config.ingest.help_text,
ingest_enable: config.ingest.enable, ingest_enable: config.ingest.enable,
ingest_param: config.ingest.input_param, ingest_param: config.ingest.input_param,

View File

@ -199,6 +199,8 @@ async fn main() -> std::io::Result<()> {
.workers(thread_count) .workers(thread_count)
.run() .run()
.await?; .await?;
} else if ARGS.drop_db {
db_drop().await;
} else { } else {
let channels = ARGS.channels.clone().unwrap_or_else(|| vec![1]); let channels = ARGS.channels.clone().unwrap_or_else(|| vec![1]);
@ -267,8 +269,6 @@ async fn main() -> std::io::Result<()> {
playlist, playlist,
Arc::new(AtomicBool::new(false)), Arc::new(AtomicBool::new(false)),
); );
} else if ARGS.drop_db {
db_drop().await;
} else if !ARGS.init { } else if !ARGS.init {
error!("Run ffplayout with parameters! Run ffplayout -h for more information."); error!("Run ffplayout with parameters! Run ffplayout -h for more information.");
} }

View File

@ -4,11 +4,12 @@ use std::{
}; };
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
use std::{fs, process::exit}; use std::process::exit;
use clap::Parser; use clap::Parser;
use rpassword::read_password; use rpassword::read_password;
use sqlx::{Pool, Sqlite}; use sqlx::{Pool, Sqlite};
use tokio::fs;
use crate::db::{ use crate::db::{
handles, handles,
@ -17,6 +18,7 @@ use crate::db::{
use crate::utils::{ use crate::utils::{
advanced_config::AdvancedConfig, advanced_config::AdvancedConfig,
config::{OutputMode, PlayoutConfig}, config::{OutputMode, PlayoutConfig},
copy_assets,
}; };
use crate::ARGS; use crate::ARGS;
@ -197,6 +199,13 @@ fn global_user(args: &mut Args) {
} }
pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> { pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
let mut user = None;
let mut fix_permission = false;
if cfg!(target_family = "unix") {
user = nix::unistd::User::from_name("ffpu").unwrap_or_default();
}
let mut args = ARGS.clone(); let mut args = ARGS.clone();
if !args.dump_advanced && !args.dump_config && !args.drop_db { if !args.dump_advanced && !args.dump_config && !args.drop_db {
@ -212,18 +221,12 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
let mut error_code = -1; let mut error_code = -1;
if args.init { if args.init {
#[cfg(target_family = "unix")]
let process_user = nix::unistd::User::from_name("ffpu").unwrap_or_default();
#[cfg(target_family = "unix")]
let mut fix_permission = false;
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
{ {
let uid = nix::unistd::Uid::current(); let uid = nix::unistd::Uid::current();
let current_user = nix::unistd::User::from_uid(uid).unwrap_or_default(); let current_user = nix::unistd::User::from_uid(uid).unwrap_or_default();
if current_user != process_user { if current_user != user {
let user_name = current_user.unwrap().name; let user_name = current_user.unwrap().name;
let mut fix_perm = String::new(); let mut fix_perm = String::new();
@ -353,7 +356,11 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
channel.playlist_path = global.playlist_root; channel.playlist_path = global.playlist_root;
channel.storage_path = global.storage_root; channel.storage_path = global.storage_root;
let mut storage_path = PathBuf::from(channel.storage_path.clone());
if global.shared_storage { if global.shared_storage {
storage_path = storage_path.join("1");
channel.preview_url = "http://127.0.0.1:8787/1/stream.m3u8".to_string(); channel.preview_url = "http://127.0.0.1:8787/1/stream.m3u8".to_string();
channel.hls_path = Path::new(&channel.hls_path) channel.hls_path = Path::new(&channel.hls_path)
.join("1") .join("1")
@ -363,22 +370,27 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.join("1") .join("1")
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
channel.storage_path = Path::new(&channel.storage_path) channel.storage_path = storage_path.to_string_lossy().to_string();
.join("1") };
.to_string_lossy()
.to_string(); if let Err(e) = copy_assets(&storage_path, fix_permission, user.clone()).await {
eprintln!("{e}");
}; };
handles::update_channel(pool, 1, channel).await.unwrap(); handles::update_channel(pool, 1, channel).await.unwrap();
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
if fix_permission { if fix_permission {
let db_path = Path::new(db_path().unwrap()).with_extension(""); let user = user.clone().unwrap();
let user = process_user.unwrap(); let db_path = Path::new(db_path().unwrap());
let db = fs::canonicalize(db_path.with_extension("db")).unwrap(); let db = fs::canonicalize(db_path).await.unwrap();
let shm = fs::canonicalize(db_path.with_extension("db-shm")).unwrap(); let shm = fs::canonicalize(db_path.with_extension("db-shm"))
let wal = fs::canonicalize(db_path.with_extension("db-wal")).unwrap(); .await
.unwrap();
let wal = fs::canonicalize(db_path.with_extension("db-wal"))
.await
.unwrap();
nix::unistd::chown(&db, Some(user.uid), Some(user.gid)).expect("Change DB owner"); nix::unistd::chown(&db, Some(user.uid), Some(user.gid)).expect("Change DB owner");
@ -410,7 +422,7 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
let chl: Vec<i32> = channels.clone().iter().map(|c| c.id).collect(); let chl: Vec<i32> = channels.clone().iter().map(|c| c.id).collect();
let user = User { let ff_user = User {
id: 0, id: 0,
mail: Some(args.mail.unwrap()), mail: Some(args.mail.unwrap()),
username: username.clone(), username: username.clone(),
@ -420,7 +432,7 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
token: None, token: None,
}; };
if let Err(e) = handles::insert_user(pool, user).await { if let Err(e) = handles::insert_user(pool, ff_user).await {
eprintln!("{e}"); eprintln!("{e}");
error_code = 1; error_code = 1;
}; };
@ -449,8 +461,11 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
let mut channel = handles::select_channel(pool, &1) let mut channel = handles::select_channel(pool, &1)
.await .await
.expect("Select Channel 1"); .expect("Select Channel 1");
let mut storage_path = PathBuf::from(global.storage_root.clone());
if args.shared_storage { if args.shared_storage {
storage_path = storage_path.join("1");
channel.hls_path = Path::new(&global.public_root) channel.hls_path = Path::new(&global.public_root)
.join("1") .join("1")
.to_string_lossy() .to_string_lossy()
@ -459,16 +474,17 @@ pub async fn run_args(pool: &Pool<Sqlite>) -> Result<(), i32> {
.join("1") .join("1")
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
channel.storage_path = Path::new(&global.storage_root) channel.storage_path = storage_path.to_string_lossy().to_string();
.join("1")
.to_string_lossy()
.to_string();
} else { } else {
channel.hls_path = global.public_root.clone(); channel.hls_path = global.public_root.clone();
channel.playlist_path = global.playlist_root.clone(); channel.playlist_path = global.playlist_root.clone();
channel.storage_path = global.storage_root.clone(); channel.storage_path = global.storage_root.clone();
} }
if let Err(e) = copy_assets(&storage_path, false, user).await {
eprintln!("{e}");
};
match handles::update_global(pool, global.clone()).await { match handles::update_global(pool, global.clone()).await {
Ok(_) => println!("Update globals done..."), Ok(_) => println!("Update globals done..."),
Err(e) => { Err(e) => {

View File

@ -1,7 +1,6 @@
use std::{ use std::{
ffi::OsStr,
io, io,
path::Path, path::PathBuf,
sync::{Arc, Mutex}, sync::{Arc, Mutex},
}; };
@ -11,7 +10,7 @@ use sqlx::{Pool, Sqlite};
use super::logging::MailQueue; use super::logging::MailQueue;
use crate::db::{handles, models::Channel}; use crate::db::{handles, models::Channel};
use crate::player::controller::{ChannelController, ChannelManager}; use crate::player::controller::{ChannelController, ChannelManager};
use crate::utils::{config::get_config, errors::ServiceError}; use crate::utils::{config::get_config, copy_assets, errors::ServiceError};
async fn map_global_admins(conn: &Pool<Sqlite>) -> Result<(), ServiceError> { async fn map_global_admins(conn: &Pool<Sqlite>) -> Result<(), ServiceError> {
let channels = handles::select_related_channels(conn, None).await?; let channels = handles::select_related_channels(conn, None).await?;
@ -29,58 +28,32 @@ async fn map_global_admins(conn: &Pool<Sqlite>) -> Result<(), ServiceError> {
Ok(()) Ok(())
} }
fn preview_url(url: &str, id: i32) -> String {
let url_path = Path::new(url);
if let Some(parent) = url_path.parent() {
if let Some(filename) = url_path.file_name() {
let new_path = if parent
.file_name()
.unwrap_or_else(|| OsStr::new("0"))
.to_string_lossy()
.to_string()
.parse::<i32>()
.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();
}
}
}
url.to_string()
}
pub async fn create_channel( pub async fn create_channel(
conn: &Pool<Sqlite>, conn: &Pool<Sqlite>,
controllers: Arc<Mutex<ChannelController>>, controllers: Arc<Mutex<ChannelController>>,
queue: Arc<Mutex<Vec<Arc<Mutex<MailQueue>>>>>, queue: Arc<Mutex<Vec<Arc<Mutex<MailQueue>>>>>,
target_channel: Channel, target_channel: Channel,
) -> Result<Channel, ServiceError> { ) -> Result<Channel, ServiceError> {
let global = handles::select_global(conn).await?; let channel = handles::insert_channel(conn, target_channel).await?;
let mut channel = handles::insert_channel(conn, target_channel).await?; let storage_path = PathBuf::from(channel.storage_path.clone());
let mut user = None;
let mut fix_permission = false;
if cfg!(target_family = "unix") {
user = nix::unistd::User::from_name("ffpu").unwrap_or_default();
let uid = nix::unistd::Uid::current();
let current_user = nix::unistd::User::from_uid(uid).unwrap_or_default();
if current_user.unwrap().name == "root" {
fix_permission = true;
};
}
handles::new_channel_presets(conn, channel.id).await?; handles::new_channel_presets(conn, channel.id).await?;
channel.preview_url = preview_url(&channel.preview_url, channel.id); if let Err(e) = copy_assets(&storage_path, fix_permission, user).await {
error!("{e}");
if global.shared_storage { };
channel.hls_path = Path::new(&global.public_root)
.join(channel.id.to_string())
.to_string_lossy()
.to_string();
channel.playlist_path = Path::new(&global.playlist_root)
.join(channel.id.to_string())
.to_string_lossy()
.to_string();
channel.storage_path = Path::new(&global.storage_root)
.join(channel.id.to_string())
.to_string_lossy()
.to_string();
}
handles::update_channel(conn, channel.id, channel.clone()).await?; handles::update_channel(conn, channel.id, channel.clone()).await?;

View File

@ -328,6 +328,8 @@ pub struct Processing {
pub audio_channels: u8, pub audio_channels: u8,
pub volume: f64, pub volume: f64,
pub custom_filter: String, pub custom_filter: String,
pub vtt_enable: bool,
pub vtt_dummy: Option<String>,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
pub cmd: Option<Vec<String>>, pub cmd: Option<Vec<String>>,
} }
@ -355,6 +357,8 @@ impl Processing {
audio_channels: config.processing_audio_channels, audio_channels: config.processing_audio_channels,
volume: config.processing_volume, volume: config.processing_volume,
custom_filter: config.processing_filter.clone(), custom_filter: config.processing_filter.clone(),
vtt_enable: config.processing_vtt_enable,
vtt_dummy: config.processing_vtt_dummy.clone(),
cmd: None, cmd: None,
} }
} }

View File

@ -310,3 +310,54 @@ pub fn round_to_nearest_ten(num: i64) -> i64 {
(num / 10) * 10 (num / 10) * 10
} }
} }
pub async fn copy_assets(
storage_path: &Path,
fix_permission: bool,
user: Option<nix::unistd::User>,
) -> Result<(), std::io::Error> {
if storage_path.is_dir() {
let target = storage_path.join("00-assets");
let mut dummy_source = Path::new("/usr/share/ffplayout/dummy.vtt");
let mut font_source = Path::new("/usr/share/ffplayout/DejaVuSans.ttf");
let mut logo_source = Path::new("/usr/share/ffplayout/logo.png");
if !dummy_source.is_file() {
dummy_source = Path::new("./assets/dummy.vtt")
}
if !font_source.is_file() {
font_source = Path::new("./assets/DejaVuSans.ttf")
}
if !logo_source.is_file() {
logo_source = Path::new("./assets/logo.png")
}
if !target.is_dir() {
let dummy_target = target.join("dummy.vtt");
let font_target = target.join("DejaVuSans.ttf");
let logo_target = target.join("logo.png");
fs::create_dir(&target).await?;
fs::copy(&dummy_source, &dummy_target).await?;
fs::copy(&font_source, &font_target).await?;
fs::copy(&logo_source, &logo_target).await?;
#[cfg(target_family = "unix")]
if fix_permission {
let user = user.unwrap();
if dummy_target.is_file() {
nix::unistd::chown(&dummy_target, Some(user.uid), Some(user.gid))?;
}
if font_target.is_file() {
nix::unistd::chown(&font_target, Some(user.uid), Some(user.gid))?;
}
if logo_target.is_file() {
nix::unistd::chown(&logo_target, Some(user.uid), Some(user.gid))?;
}
}
}
}
Ok(())
}

View File

@ -120,7 +120,7 @@ function newChannel() {
newChannel.id = channels.length + 1 newChannel.id = channels.length + 1
newChannel.name = `Channel ${newChannel.id}` newChannel.name = `Channel ${newChannel.id}`
newChannel.preview_url = `${window.location.protocol}//${window.location.host}/live/${newChannel.id}/stream.m3u8` newChannel.preview_url = `${window.location.protocol}//${window.location.host}/${newChannel.id}/live/stream.m3u8`
newChannel.hls_path = `${rmId(newChannel.hls_path)}/${newChannel.id}` newChannel.hls_path = `${rmId(newChannel.hls_path)}/${newChannel.id}`
newChannel.playlist_path = `${rmId(newChannel.playlist_path)}/${newChannel.id}` newChannel.playlist_path = `${rmId(newChannel.playlist_path)}/${newChannel.id}`
newChannel.storage_path = `${rmId(newChannel.storage_path)}/${newChannel.id}` newChannel.storage_path = `${rmId(newChannel.storage_path)}/${newChannel.id}`
@ -158,6 +158,7 @@ async function deleteChannel() {
}) })
config.splice(configStore.id, 1) config.splice(configStore.id, 1)
configStore.channelsRaw.splice(configStore.id, 1)
configStore.channels = config configStore.channels = config
configStore.id = configStore.channels.length - 1 configStore.id = configStore.channels.length - 1

View File

@ -15,7 +15,15 @@
class="form-control w-full" class="form-control w-full"
:class="[typeof prop === 'boolean' && 'flex-row', name.toString() !== 'help_text' && 'mt-2']" :class="[typeof prop === 'boolean' && 'flex-row', name.toString() !== 'help_text' && 'mt-2']"
> >
<template v-if="name.toString() !== 'startInSec' && name.toString() !== 'lengthInSec' && !(name.toString() === 'path' && key.toString() === 'storage')" > <!-- TODO: vtt_ check is temporary, needs to be removed when is done implemented -->
<template
v-if="
name.toString() !== 'startInSec' &&
name.toString() !== 'lengthInSec' &&
!name.startsWith('vtt_') &&
!(name.toString() === 'path' && key.toString() === 'storage')
"
>
<div v-if="name.toString() !== 'help_text'" class="label"> <div v-if="name.toString() !== 'help_text'" class="label">
<span class="label-text !text-md font-bold">{{ name }}</span> <span class="label-text !text-md font-bold">{{ name }}</span>
</div> </div>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="grid grid-cols-1 xs:grid-cols-2 border-4 rounded-md border-primary text-left shadow min-w-[320px] max-w-[960px] mt-5 xs:mt-0"> <div class="grid grid-cols-1 xs:grid-cols-2 border-4 rounded-md border-primary text-left shadow min-w-[320px] md:min-w-[728px] max-w-[960px] mt-5 xs:mt-0">
<div class="p-4 bg-base-100"> <div class="p-4 bg-base-100">
<span class="text-3xl">{{ sysStat.system.name }} {{ sysStat.system.version }}</span> <span class="text-3xl">{{ sysStat.system.name }} {{ sysStat.system.version }}</span>
<span v-if="sysStat.system.kernel"> <span v-if="sysStat.system.kernel">

View File

@ -104,7 +104,7 @@ CREATE TABLE
processing_aspect REAL NOT NULL DEFAULT 1.778, processing_aspect REAL NOT NULL DEFAULT 1.778,
processing_fps REAL NOT NULL DEFAULT 25.0, processing_fps REAL NOT NULL DEFAULT 25.0,
processing_add_logo INTEGER NOT NULL DEFAULT 1, processing_add_logo INTEGER NOT NULL DEFAULT 1,
processing_logo TEXT NOT NULL DEFAULT "graphics/logo.png", processing_logo TEXT NOT NULL DEFAULT "00-assets/logo.png",
processing_logo_scale TEXT NOT NULL DEFAULT "", processing_logo_scale TEXT NOT NULL DEFAULT "",
processing_logo_opacity REAL NOT NULL DEFAULT 0.7, processing_logo_opacity REAL NOT NULL DEFAULT 0.7,
processing_logo_position TEXT NOT NULL DEFAULT "W-w-12:12", processing_logo_position TEXT NOT NULL DEFAULT "W-w-12:12",
@ -113,6 +113,8 @@ CREATE TABLE
processing_audio_channels INTEGER NOT NULL DEFAULT 2, processing_audio_channels INTEGER NOT NULL DEFAULT 2,
processing_volume REAL NOT NULL DEFAULT 1.0, processing_volume REAL NOT NULL DEFAULT 1.0,
processing_filter TEXT NOT NULL DEFAULT "", processing_filter TEXT NOT NULL DEFAULT "",
processing_vtt_enable INTEGER NOT NULL DEFAULT 0,
processing_vtt_dummy TEXT NULL DEFAULT "00-assets/dummy.vtt",
ingest_help "Run a server for a ingest stream. This stream will override the normal streaming until is done. There is only a very simple authentication mechanism, which check if the stream name is correct.\n'custom_filter' can be used in the same way then the one in the process section.", ingest_help "Run a server for a ingest stream. This stream will override the normal streaming until is done. There is only a very simple authentication mechanism, which check if the stream name is correct.\n'custom_filter' can be used in the same way then the one in the process section.",
ingest_enable INTEGER NOT NULL DEFAULT 0, ingest_enable INTEGER NOT NULL DEFAULT 0,
ingest_param TEXT NOT NULL DEFAULT "-f live_flv -listen 1 -i rtmp://127.0.0.1:1936/live/stream", ingest_param TEXT NOT NULL DEFAULT "-f live_flv -listen 1 -i rtmp://127.0.0.1:1936/live/stream",
@ -128,7 +130,7 @@ CREATE TABLE
text_help TEXT NOT NULL DEFAULT "Overlay text in combination with libzmq for remote text manipulation. fontfile is a relative path to your storage folder.\n'text_from_filename' activate the extraction from text of a filename. With 'style' you can define the drawtext parameters like position, color, etc. Post Text over API will override this. With 'regex' you can format file names, to get a title from it.", text_help TEXT NOT NULL DEFAULT "Overlay text in combination with libzmq for remote text manipulation. fontfile is a relative path to your storage folder.\n'text_from_filename' activate the extraction from text of a filename. With 'style' you can define the drawtext parameters like position, color, etc. Post Text over API will override this. With 'regex' you can format file names, to get a title from it.",
text_add INTEGER NOT NULL DEFAULT 1, text_add INTEGER NOT NULL DEFAULT 1,
text_from_filename INTEGER NOT NULL DEFAULT 0, text_from_filename INTEGER NOT NULL DEFAULT 0,
text_font TEXT NOT NULL DEFAULT "fonts/DejaVuSans.ttf", text_font TEXT NOT NULL DEFAULT "00-assets/DejaVuSans.ttf",
text_style TEXT NOT NULL DEFAULT "x=(w-tw)/2:y=(h-line_h)*0.9:fontsize=24:fontcolor=#ffffff:box=1:boxcolor=#000000:boxborderw=4", text_style TEXT NOT NULL DEFAULT "x=(w-tw)/2:y=(h-line_h)*0.9:fontsize=24:fontcolor=#ffffff:box=1:boxcolor=#000000:boxborderw=4",
text_regex TEXT NOT NULL DEFAULT "^.+[/\\](.*)(.mp4|.mkv|.webm)$", text_regex TEXT NOT NULL DEFAULT "^.+[/\\](.*)(.mp4|.mkv|.webm)$",
task_help TEXT NOT NULL DEFAULT "Run an external program with a given media object. The media object is in json format and contains all the information about the current clip. The external program can be a script or a binary, but should only run for a short time.", task_help TEXT NOT NULL DEFAULT "Run an external program with a given media object. The media object is in json format and contains all the information about the current clip. The external program can be a script or a binary, but should only run for a short time.",