validate file compression settings and filtering
- fix length from filler clip in playlist generator - serialize values only when string is not empty - compare also audio and custom filter on playlist existing check
This commit is contained in:
parent
fd8e4738ca
commit
9c5122696d
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -960,7 +960,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ffplayout-api"
|
name = "ffplayout-api"
|
||||||
version = "0.6.0"
|
version = "0.6.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-files",
|
"actix-files",
|
||||||
"actix-multipart",
|
"actix-multipart",
|
||||||
|
@ -4,7 +4,7 @@ description = "Rest API for ffplayout"
|
|||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
authors = ["Jonathan Baecker jonbae77@gmail.com"]
|
authors = ["Jonathan Baecker jonbae77@gmail.com"]
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
version = "0.6.0"
|
version = "0.6.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -141,7 +141,7 @@ pub fn generate_playlist(
|
|||||||
|
|
||||||
length += duration;
|
length += duration;
|
||||||
} else if filler_length > 0.0 && filler_length > total_length - length {
|
} else if filler_length > 0.0 && filler_length > total_length - length {
|
||||||
filler.out = filler_length - (total_length - length);
|
filler.out = total_length - length;
|
||||||
playlist.program.push(filler);
|
playlist.program.push(filler);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -10,44 +10,57 @@ use std::{
|
|||||||
use simplelog::*;
|
use simplelog::*;
|
||||||
|
|
||||||
use crate::utils::{
|
use crate::utils::{
|
||||||
format_log_line, sec_to_time, valid_source, vec_strings, Media, JsonPlaylist, PlayoutConfig,
|
format_log_line, loop_image, sec_to_time, seek_and_length, valid_source, vec_strings,
|
||||||
|
JsonPlaylist, Media, PlayoutConfig, IMAGE_FORMAT,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// check if ffmpeg can read the file and apply filter to it.
|
/// check if ffmpeg can read the file and apply filter to it.
|
||||||
fn check_media(item: Media, begin: f64, config: &PlayoutConfig) -> Result<(), Error> {
|
fn check_media(
|
||||||
let mut clip = item;
|
mut node: Media,
|
||||||
clip.add_probe();
|
pos: usize,
|
||||||
clip.add_filter(config, &Arc::new(Mutex::new(vec![])));
|
begin: f64,
|
||||||
|
config: &PlayoutConfig,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut enc_cmd = vec_strings!["-hide_banner", "-nostats", "-v", "level+error"];
|
||||||
|
let mut error_list = vec![];
|
||||||
|
|
||||||
let enc_cmd = vec_strings![
|
node.add_probe();
|
||||||
"-hide_banner",
|
|
||||||
"-nostats",
|
|
||||||
"-v",
|
|
||||||
"level+error",
|
|
||||||
"-ignore_chapters",
|
|
||||||
"1",
|
|
||||||
"-i",
|
|
||||||
clip.source,
|
|
||||||
"-t",
|
|
||||||
"0.25",
|
|
||||||
"-f",
|
|
||||||
"null",
|
|
||||||
"-"
|
|
||||||
];
|
|
||||||
|
|
||||||
if clip.probe.and_then(|p| p.format).is_none() {
|
if node.probe.clone().and_then(|p| p.format).is_none() {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
ErrorKind::Other,
|
ErrorKind::Other,
|
||||||
format!(
|
format!(
|
||||||
"No Metadata at <yellow>{}</>, from file <b><magenta>\"{}\"</></b>",
|
"No Metadata at position <yellow>{pos}</> {}, from file <b><magenta>\"{}\"</></b>",
|
||||||
sec_to_time(begin),
|
sec_to_time(begin),
|
||||||
clip.source
|
node.source
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// take care, that no seek and length command is added.
|
||||||
|
node.seek = 0.0;
|
||||||
|
node.out = node.duration;
|
||||||
|
|
||||||
|
if node
|
||||||
|
.source
|
||||||
|
.rsplit_once('.')
|
||||||
|
.map(|(_, e)| e.to_lowercase())
|
||||||
|
.filter(|c| IMAGE_FORMAT.contains(&c.as_str()))
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
node.cmd = Some(loop_image(&node));
|
||||||
|
} else {
|
||||||
|
node.cmd = Some(seek_and_length(&node));
|
||||||
|
}
|
||||||
|
|
||||||
|
node.add_filter(config, &Arc::new(Mutex::new(vec![])));
|
||||||
|
|
||||||
|
enc_cmd.append(&mut node.cmd.unwrap_or_default());
|
||||||
|
enc_cmd.append(&mut node.filter.unwrap_or_default());
|
||||||
|
enc_cmd.append(&mut vec_strings!["-t", "0.15", "-f", "null", "-"]);
|
||||||
|
|
||||||
let mut enc_proc = match Command::new("ffmpeg")
|
let mut enc_proc = match Command::new("ffmpeg")
|
||||||
.args(enc_cmd)
|
.args(enc_cmd.clone())
|
||||||
.stderr(Stdio::piped())
|
.stderr(Stdio::piped())
|
||||||
.spawn()
|
.spawn()
|
||||||
{
|
{
|
||||||
@ -61,17 +74,30 @@ fn check_media(item: Media, begin: f64, config: &PlayoutConfig) -> Result<(), Er
|
|||||||
let line = line?;
|
let line = line?;
|
||||||
|
|
||||||
if line.contains("[error]") {
|
if line.contains("[error]") {
|
||||||
error!(
|
let log_line = format_log_line(line, "error");
|
||||||
"<bright black>[Validator]</> {}",
|
|
||||||
format_log_line(line, "error")
|
if !error_list.contains(&log_line) {
|
||||||
);
|
error_list.push(log_line);
|
||||||
|
}
|
||||||
} else if line.contains("[fatal]") {
|
} else if line.contains("[fatal]") {
|
||||||
|
let log_line = format_log_line(line, "fatal");
|
||||||
|
|
||||||
|
if !error_list.contains(&log_line) {
|
||||||
|
error_list.push(log_line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !error_list.is_empty() {
|
||||||
error!(
|
error!(
|
||||||
"<bright black>[Validator]</> {}",
|
"<bright black>[Validator]</> Compressing error on position <yellow>{pos}</> {}: <b><magenta>{}</></b>:\n{}",
|
||||||
format_log_line(line, "fatal")
|
sec_to_time(begin),
|
||||||
|
node.source,
|
||||||
|
error_list.join("\n")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
error_list.clear();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -94,20 +120,22 @@ pub fn validate_playlist(
|
|||||||
|
|
||||||
length += begin;
|
length += begin;
|
||||||
|
|
||||||
debug!("validate playlist from: <yellow>{date}</>");
|
debug!("Validate playlist from: <yellow>{date}</>");
|
||||||
|
|
||||||
for item in playlist.program.iter() {
|
for (index, item) in playlist.program.iter().enumerate() {
|
||||||
if is_terminated.load(Ordering::SeqCst) {
|
if is_terminated.load(Ordering::SeqCst) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let pos = index + 1;
|
||||||
|
|
||||||
if valid_source(&item.source) {
|
if valid_source(&item.source) {
|
||||||
if let Err(e) = check_media(item.clone(), begin, &config) {
|
if let Err(e) = check_media(item.clone(), pos, begin, &config) {
|
||||||
error!("{e}");
|
error!("{e}");
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
error!(
|
error!(
|
||||||
"Source on position <yellow>{}</> not exists: <b><magenta>\"{}\"</></b>",
|
"Source on position <yellow>{pos}</> {} not exists: <b><magenta>\"{}\"</></b>",
|
||||||
sec_to_time(begin),
|
sec_to_time(begin),
|
||||||
item.source
|
item.source
|
||||||
);
|
);
|
||||||
@ -122,4 +150,6 @@ pub fn validate_playlist(
|
|||||||
sec_to_time(length - begin),
|
sec_to_time(length - begin),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debug!("Validation done...");
|
||||||
}
|
}
|
||||||
|
@ -55,12 +55,20 @@ pub struct Media {
|
|||||||
pub out: f64,
|
pub out: f64,
|
||||||
pub duration: f64,
|
pub duration: f64,
|
||||||
|
|
||||||
#[serde(default, deserialize_with = "null_string")]
|
#[serde(
|
||||||
|
default,
|
||||||
|
deserialize_with = "null_string",
|
||||||
|
skip_serializing_if = "is_empty_string"
|
||||||
|
)]
|
||||||
pub category: String,
|
pub category: String,
|
||||||
#[serde(deserialize_with = "null_string")]
|
#[serde(deserialize_with = "null_string")]
|
||||||
pub source: String,
|
pub source: String,
|
||||||
|
|
||||||
#[serde(default, deserialize_with = "null_string")]
|
#[serde(
|
||||||
|
default,
|
||||||
|
deserialize_with = "null_string",
|
||||||
|
skip_serializing_if = "is_empty_string"
|
||||||
|
)]
|
||||||
pub audio: String,
|
pub audio: String,
|
||||||
|
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
@ -69,7 +77,7 @@ pub struct Media {
|
|||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
pub filter: Option<Vec<String>>,
|
pub filter: Option<Vec<String>>,
|
||||||
|
|
||||||
#[serde(default)]
|
#[serde(default, skip_serializing_if = "is_empty_string")]
|
||||||
pub custom_filter: String,
|
pub custom_filter: String,
|
||||||
|
|
||||||
#[serde(skip_serializing, skip_deserializing)]
|
#[serde(skip_serializing, skip_deserializing)]
|
||||||
@ -158,6 +166,8 @@ impl PartialEq for Media {
|
|||||||
&& self.duration == other.duration
|
&& self.duration == other.duration
|
||||||
&& self.source == other.source
|
&& self.source == other.source
|
||||||
&& self.category == other.category
|
&& self.category == other.category
|
||||||
|
&& self.audio == other.audio
|
||||||
|
&& self.custom_filter == other.custom_filter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,6 +180,11 @@ where
|
|||||||
Deserialize::deserialize(d).map(|x: Option<_>| x.unwrap_or_default())
|
Deserialize::deserialize(d).map(|x: Option<_>| x.unwrap_or_default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||||
|
fn is_empty_string(st: &String) -> bool {
|
||||||
|
*st == String::new()
|
||||||
|
}
|
||||||
|
|
||||||
/// We use the ffprobe crate, but we map the metadata to our needs.
|
/// We use the ffprobe crate, but we map the metadata to our needs.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
|
||||||
pub struct MediaProbe {
|
pub struct MediaProbe {
|
||||||
@ -463,12 +478,15 @@ pub fn seek_and_length(node: &Media) -> Vec<String> {
|
|||||||
source_cmd.append(&mut vec_strings!["-ss", node.seek])
|
source_cmd.append(&mut vec_strings!["-ss", node.seek])
|
||||||
}
|
}
|
||||||
|
|
||||||
source_cmd.append(&mut vec_strings![
|
if file_extension(Path::new(&node.source))
|
||||||
"-ignore_chapters",
|
.unwrap_or_default()
|
||||||
"1",
|
.to_lowercase()
|
||||||
"-i",
|
== "mp4"
|
||||||
node.source.clone()
|
{
|
||||||
]);
|
source_cmd.append(&mut vec_strings!["-ignore_chapters", "1"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
source_cmd.append(&mut vec_strings!["-i", node.source.clone()]);
|
||||||
|
|
||||||
if Path::new(&node.audio).is_file() {
|
if Path::new(&node.audio).is_file() {
|
||||||
let audio_probe = MediaProbe::new(&node.audio);
|
let audio_probe = MediaProbe::new(&node.audio);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user