Merge pull request #142 from jb-alvarado/master
add null output, ask for credentials, add postinst script
This commit is contained in:
commit
034a3617e6
41
Cargo.lock
generated
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
0
debian/.gitkeep
vendored
10
debian/postinst
vendored
Normal file
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!"),
|
||||
};
|
||||
|
64
ffplayout-engine/src/output/null.rs
Normal file
64
ffplayout-engine/src/output/null.rs
Normal file
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user