Merge pull request from jb-alvarado/master

add null output, ask for credentials, add postinst script
This commit is contained in:
jb-alvarado 2022-06-27 14:13:53 +02:00 committed by GitHub
commit 034a3617e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 198 additions and 63 deletions

41
Cargo.lock generated

@ -472,9 +472,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "base64ct"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179"
checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851"
[[package]]
name = "bitflags"
@ -625,8 +625,8 @@ dependencies = [
[[package]]
name = "chrono"
version = "0.4.19"
source = "git+https://github.com/chronotope/chrono.git#b1d74aef688c27fccc738c64746535905903471a"
version = "0.4.20-beta.1"
source = "git+https://github.com/chronotope/chrono.git#39ac80a6a51b2a5081750c451feb261c1cb43960"
dependencies = [
"num-integer",
"num-traits",
@ -1027,7 +1027,7 @@ dependencies = [
[[package]]
name = "ffplayout-api"
version = "0.3.1"
version = "0.3.2"
dependencies = [
"actix-multipart",
"actix-web",
@ -1045,6 +1045,7 @@ dependencies = [
"rand 0.8.5",
"relative-path",
"reqwest",
"rpassword",
"sanitize-filename",
"serde",
"serde_json",
@ -1057,7 +1058,7 @@ dependencies = [
name = "ffplayout-lib"
version = "0.10.1"
dependencies = [
"chrono 0.4.19 (git+https://github.com/chronotope/chrono.git)",
"chrono 0.4.20-beta.1",
"crossbeam-channel 0.5.5",
"ffprobe",
"file-rotate",
@ -1763,9 +1764,9 @@ dependencies = [
[[package]]
name = "linked-hash-map"
version = "0.5.4"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "local-channel"
@ -2454,6 +2455,18 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "rpassword"
version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf099a1888612545b683d2661a1940089f6c2e5a8e38979b2159da876bfd956"
dependencies = [
"libc",
"serde",
"serde_json",
"winapi 0.3.9",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
@ -2657,9 +2670,9 @@ checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
[[package]]
name = "smallvec"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
checksum = "cc88c725d61fc6c3132893370cac4a0200e3fedf5da8331c570664b1987f5ca2"
[[package]]
name = "socket2"
@ -3005,9 +3018,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.27"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921"
checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7"
dependencies = [
"once_cell",
]
@ -3063,9 +3076,9 @@ checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
[[package]]
name = "unicode-normalization"
version = "0.1.19"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
checksum = "81dee68f85cab8cf68dec42158baf3a79a1cdc065a8b103025965d6ccb7f6cbd"
dependencies = [
"tinyvec",
]

@ -44,6 +44,7 @@ Check the [releases](https://github.com/ffplayout/ffplayout-engine/releases/late
- **stream**
- **desktop**
- **HLS**
- **null** (for debugging)
- JSON RPC server, for getting infos about current playing and controlling
- [live ingest](/docs/live_ingest.md)

@ -115,10 +115,9 @@ text:
regex: ^.+[/\\](.*)(.mp4|.mkv)$
out:
help_text: The final playout compression. Set the settings to your needs.
'mode' has the standard options 'desktop', 'hls', 'stream'. Self made
outputs can be define, by adding script in output folder with an 'output' function
inside. 'preview' works only in streaming output and creates a separate preview stream.
help_text: The final playout compression. Set the settings to your needs. 'mode'
has the options 'desktop', 'hls', 'null', 'stream'.
'preview' works only in streaming output and creates a separate preview stream.
mode: 'stream'
preview: false
preview_param: >-

0
debian/.gitkeep vendored

10
debian/postinst vendored Normal file

@ -0,0 +1,10 @@
#DEBHELPER#
if [ ! -d "/usr/share/ffplayout/db" ]; then
mkdir "/usr/share/ffplayout/db"
chmod 777 "/usr/share/ffplayout/db"
/usr/bin/ffpapi -i
chown www-data. "/usr/share/ffplayout/db/ffplayout.db"
fi

@ -129,13 +129,13 @@ Response is in TEXT format
- **POST** `api/control/{id}/playout/reset/`\
Response is in TEXT format
- **GET** `/api/control/{id}/media/current/`\
- **GET** `/api/control/{id}/media/current`\
Response is in JSON format
- **GET** `/api/control/{id}/media/next/`\
- **GET** `/api/control/{id}/media/next`\
Response is in JSON format
- **GET** `/api/control/{id}/media/last/`\
- **GET** `/api/control/{id}/media/last`\
Response is in JSON format
- **POST** `/api/control/{id}/process/`\

@ -4,7 +4,7 @@ description = "Rest API for ffplayout"
license = "GPL-3.0"
authors = ["Jonathan Baecker jonbae77@gmail.com"]
readme = "README.md"
version = "0.3.1"
version = "0.3.2"
edition = "2021"
[dependencies]
@ -24,6 +24,7 @@ once_cell = "1.10"
rand = "0.8"
relative-path = "1.6"
reqwest = { version = "0.11", features = ["blocking", "json"] }
rpassword = "6.0"
sanitize-filename = "0.3"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

@ -21,4 +21,6 @@ Then run the API thru the systemd service, or like:
ffpapi -l 127.0.0.1:8080
```
If you plan to run ffpapi with systemd set permission from **/usr/share/ffplayout** and content to user **www-data:www-data**.
**For possible endpoints read: [api endpoints](/docs/api.md)**

@ -5,6 +5,9 @@ use clap::Parser;
about = "REST API for ffplayout",
long_about = None)]
pub struct Args {
#[clap(short, long, help = "ask for user credentials")]
pub ask: bool,
#[clap(short, long, help = "Listen on IP:PORT, like: 127.0.0.1:8080")]
pub listen: Option<String>,

@ -138,17 +138,15 @@ pub async fn rename_file(id: i64, move_object: &MoveObject) -> Result<MoveObject
.normalize()
.to_string();
if !source_path.starts_with(&relativ_path) {
source_path = path.join(source);
} else {
source_path = path.join(source_path.strip_prefix(&relativ_path).unwrap());
}
source_path = match source_path.starts_with(&relativ_path) {
true => path.join(source_path.strip_prefix(&relativ_path).unwrap()),
false => path.join(source),
};
if !target_path.starts_with(&relativ_path) {
target_path = path.join(target);
} else {
target_path = path.join(target_path.strip_prefix(relativ_path).unwrap());
}
target_path = match target_path.starts_with(&relativ_path) {
true => path.join(target_path.strip_prefix(relativ_path).unwrap()),
false => path.join(target),
};
if !source_path.exists() {
return Err(ServiceError::BadRequest("Source file not exist!".into()));
@ -273,7 +271,6 @@ pub async fn upload(id: i64, mut payload: Multipart) -> Result<HttpResponse, Ser
return Err(ServiceError::BadRequest("Target already exists!".into()));
}
// File::create is blocking operation, use threadpool
let mut f = web::block(|| std::fs::File::create(filepath)).await??;
while let Some(chunk) = field.try_next().await? {

@ -1,7 +1,13 @@
use std::{error::Error, fs::File, path::Path};
use std::{
error::Error,
fs::File,
io::{stdin, stdout, Write},
path::Path,
};
use faccess::PathExt;
use once_cell::sync::OnceCell;
use rpassword::read_password;
use simplelog::*;
pub mod args_parse;
@ -69,20 +75,24 @@ pub async fn init_config() {
}
pub fn db_path() -> Result<String, Box<dyn std::error::Error>> {
let sys_path = Path::new("/usr/share/ffplayout");
let mut db_path = String::from("./ffplayout.db");
let sys_path = Path::new("/usr/share/ffplayout/db");
let mut db_path = "./ffplayout.db".to_string();
if sys_path.is_dir() && !sys_path.writable() {
error!("Path {} is not writable!", sys_path.display());
}
if sys_path.is_dir() && sys_path.writable() {
db_path = String::from("/usr/share/ffplayout/ffplayout.db");
db_path = "/usr/share/ffplayout/db/ffplayout.db".to_string();
} else if Path::new("./assets").is_dir() {
db_path = String::from("./assets/ffplayout.db");
db_path = "./assets/ffplayout.db".to_string();
}
Ok(db_path)
}
pub async fn run_args(args: Args) -> Result<(), i32> {
if !args.init && args.listen.is_none() && args.username.is_none() {
pub async fn run_args(mut args: Args) -> Result<(), i32> {
if !args.init && args.listen.is_none() && !args.ask && args.username.is_none() {
error!("Wrong number of arguments! Run ffpapi --help for more information.");
return Err(0);
@ -96,6 +106,46 @@ pub async fn run_args(args: Args) -> Result<(), i32> {
return Err(0);
}
if args.ask {
let mut user = String::new();
print!("Username: ");
stdout().flush().unwrap();
stdin()
.read_line(&mut user)
.expect("Did not enter a correct name?");
if let Some('\n') = user.chars().next_back() {
user.pop();
}
if let Some('\r') = user.chars().next_back() {
user.pop();
}
args.username = Some(user);
print!("Password: ");
stdout().flush().unwrap();
let password = read_password();
args.password = password.ok();
let mut email = String::new();
print!("EMail: ");
stdout().flush().unwrap();
stdin()
.read_line(&mut email)
.expect("Did not enter a correct name?");
if let Some('\n') = email.chars().next_back() {
email.pop();
}
if let Some('\r') = email.chars().next_back() {
email.pop();
}
args.email = Some(email);
}
if let Some(username) = args.username {
if args.email.is_none() || args.password.is_none() {
error!("Email/password missing!");

@ -41,20 +41,16 @@ suggests = "ffmpeg"
copyright = "Copyright (c) 2022, Jonathan Baecker. All rights reserved."
conf-files = ["/etc/ffplayout/ffplayout.yml"]
assets = [
[
"../target/x86_64-unknown-linux-musl/release/ffpapi",
"/usr/bin/ffpapi",
"755"
],
["../target/x86_64-unknown-linux-musl/release/ffpapi", "/usr/bin/", "755"],
[
"../target/x86_64-unknown-linux-musl/release/ffplayout",
"/usr/bin/ffplayout",
"/usr/bin/",
"755"
],
["../assets/ffpapi.service", "/lib/systemd/system/ffpapi.service", "644"],
["../assets/11-ffplayout", "/etc/sudoers.d/11-ffplayout", "644"],
["../assets/ffplayout.yml", "/etc/ffplayout/ffplayout.yml", "644"],
["../assets/logo.png", "/usr/share/ffplayout/logo.png", "644"],
["../assets/ffpapi.service", "/lib/systemd/system/", "644"],
["../assets/11-ffplayout", "/etc/sudoers.d/", "644"],
["../assets/ffplayout.yml", "/etc/ffplayout/", "644"],
["../assets/logo.png", "/usr/share/ffplayout/", "644"],
["../README.md", "/usr/share/doc/ffplayout/README", "644"],
]
maintainer-scripts = "../debian/"

@ -102,12 +102,11 @@ fn main() {
status_file(&config.general.stat_file, &playout_stat);
if &config.out.mode.to_lowercase() == "hls" {
match config.out.mode.to_lowercase().as_str() {
// write files/playlist to HLS m3u8 playlist
write_hls(&config, play_control, playout_stat, proc_control);
} else {
"hls" => write_hls(&config, play_control, playout_stat, proc_control),
// play on desktop or stream to a remote target
player(&config, play_control, playout_stat, proc_control);
_ => player(&config, play_control, playout_stat, proc_control),
}
info!("Playout done...");

@ -11,6 +11,7 @@ use simplelog::*;
mod desktop;
mod hls;
mod null;
mod stream;
pub use hls::write_hls;
@ -55,6 +56,7 @@ pub fn player(
// get ffmpeg output instance
let mut enc_proc = match config.out.mode.as_str() {
"desktop" => desktop::output(config, &ff_log_format),
"null" => null::output(config, &ff_log_format),
"stream" => stream::output(config, &ff_log_format),
_ => panic!("Output mode doesn't exists!"),
};

@ -0,0 +1,64 @@
use std::process::{self, Command, Stdio};
use simplelog::*;
use ffplayout_lib::filter::v_drawtext;
use ffplayout_lib::utils::{Media, PlayoutConfig};
use ffplayout_lib::vec_strings;
/// Desktop Output
///
/// Instead of streaming, we run a ffplay instance and play on desktop.
pub fn output(config: &PlayoutConfig, log_format: &str) -> process::Child {
let mut enc_filter: Vec<String> = vec![];
let mut enc_cmd = vec_strings![
"-hide_banner",
"-nostats",
"-v",
log_format,
"-re",
"-i",
"pipe:0",
"-f",
"null",
"-"
];
if config.text.add_text && !config.text.text_from_filename {
if let Some(socket) = config.text.bind_address.clone() {
debug!(
"Using drawtext filter, listening on address: <yellow>{}</>",
socket
);
let mut filter: String = "null,".to_string();
filter.push_str(
v_drawtext::filter_node(config, &Media::new(0, String::new(), false)).as_str(),
);
enc_filter = vec!["-vf".to_string(), filter];
}
}
enc_cmd.splice(7..7, enc_filter);
debug!(
"Encoder CMD: <bright-blue>\"ffmpeg {}\"</>",
enc_cmd.join(" ")
);
let enc_proc = match Command::new("ffmpeg")
.args(enc_cmd)
.stdin(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
{
Err(e) => {
error!("couldn't spawn encoder process: {e}");
panic!("couldn't spawn encoder process: {e}")
}
Ok(proc) => proc,
};
enc_proc
}

@ -189,7 +189,7 @@ impl PlayoutConfig {
println!(
"{config_path:?} doesn't exists!\nPut \"ffplayout.yml\" in \"/etc/playout/\" or beside the executable!"
);
process::exit(0x0100);
process::exit(1);
}
};

@ -184,10 +184,9 @@ pub fn read_json(
validate_playlist(list_clone, is_terminated, config_clone)
});
if config.playlist.infinit {
return loop_playlist(config, current_file, playlist);
} else {
return set_defaults(playlist, current_file, start_sec);
match config.playlist.infinit {
true => return loop_playlist(config, current_file, playlist),
false => return set_defaults(playlist, current_file, start_sec),
}
}
}
@ -206,10 +205,9 @@ pub fn read_json(
thread::spawn(move || validate_playlist(list_clone, is_terminated, config_clone));
if config.playlist.infinit {
return loop_playlist(config, current_file, playlist);
} else {
return set_defaults(playlist, current_file, start_sec);
match config.playlist.infinit {
true => return loop_playlist(config, current_file, playlist),
false => return set_defaults(playlist, current_file, start_sec),
}
}