support separate audio (#113), convert to string instead of String::from()

This commit is contained in:
jb-alvarado 2022-07-31 22:03:15 +02:00
parent c41cb4396f
commit fc43a1dca8
6 changed files with 98 additions and 67 deletions

View File

@ -12,7 +12,7 @@ use simplelog::*;
use ffplayout_lib::utils::{
check_sync, gen_dummy, get_delta, get_sec, is_close, is_remote, json_serializer::read_json,
loop_image, loop_input, modified_time, seek_and_length, valid_source, Media, MediaProbe,
loop_filler, loop_image, modified_time, seek_and_length, valid_source, Media, MediaProbe,
PlayoutConfig, PlayoutStatus, DUMMY_LEN, IMAGE_FORMAT,
};
@ -460,8 +460,6 @@ fn gen_source(
mut node: Media,
filter_chain: &Arc<Mutex<Vec<String>>>,
) -> Media {
let duration = node.out - node.seek;
if valid_source(&node.source) {
node.add_probe();
@ -472,17 +470,12 @@ fn gen_source(
.filter(|c| IMAGE_FORMAT.contains(&c.as_str()))
.is_some()
{
let cmd = loop_image(&node.source, duration);
node.cmd = Some(cmd);
node.cmd = Some(loop_image(&node));
} else {
node.cmd = Some(seek_and_length(
node.source.clone(),
node.seek,
node.out,
node.duration,
));
node.cmd = Some(seek_and_length(&node));
}
} else {
let duration = node.out - node.seek;
let probe = MediaProbe::new(&config.storage.filler_clip);
if node.source.is_empty() {
@ -499,9 +492,8 @@ fn gen_source(
.filter(|c| IMAGE_FORMAT.contains(&c.as_str()))
.is_some()
{
let cmd = loop_image(&config.storage.filler_clip, duration);
node.source = config.storage.filler_clip.clone();
node.cmd = Some(cmd);
node.cmd = Some(loop_image(&node));
node.probe = Some(probe);
} else if let Some(length) = probe
.clone()
@ -510,9 +502,11 @@ fn gen_source(
.and_then(|d| d.parse::<f64>().ok())
{
// create placeholder from config filler.
let cmd = loop_input(&config.storage.filler_clip, length, duration);
node.source = config.storage.filler_clip.clone();
node.cmd = Some(cmd);
node.duration = length;
node.out = duration;
node.cmd = Some(loop_filler(&node));
node.probe = Some(probe);
} else {
// create colored placeholder.
@ -578,12 +572,7 @@ fn handle_list_end(
node.source
);
node.out = out;
node.cmd = Some(seek_and_length(
node.source.clone(),
node.seek,
node.out,
node.duration,
));
node.cmd = Some(seek_and_length(&node));
node.process = Some(false);

View File

@ -92,9 +92,10 @@ pub fn player(
}
info!(
"Play for <yellow>{}</>: <b><magenta>{}</></b>",
"Play for <yellow>{}</>: <b><magenta>{} {}</></b>",
sec_to_time(node.out - node.seek),
node.source
node.source,
node.audio
);
let mut filter = node.filter.unwrap();

View File

@ -10,7 +10,7 @@ pub mod ingest_filter;
pub mod v_drawtext;
pub mod v_overlay;
use crate::utils::{get_delta, is_close, Media, PlayoutConfig};
use crate::utils::{get_delta, is_close, Media, MediaProbe, PlayoutConfig};
#[derive(Debug, Clone)]
struct Filters {
@ -218,6 +218,7 @@ fn add_audio(node: &mut Media, chain: &mut Filters) {
.and_then(|p| p.audio_streams.as_ref())
.unwrap_or(&vec![])
.is_empty()
&& !Path::new(&node.audio).is_file()
{
warn!("Clip <b><magenta>{}</></b> has no audio!", node.source);
let audio = format!(
@ -229,11 +230,15 @@ fn add_audio(node: &mut Media, chain: &mut Filters) {
}
fn extend_audio(node: &mut Media, chain: &mut Filters) {
if let Some(audio_duration) = node
.probe
.as_ref()
.and_then(|p| p.audio_streams.as_ref())
.and_then(|a| a[0].duration.as_ref())
let probe = if Path::new(&node.audio).is_file() {
Some(MediaProbe::new(&node.audio))
} else {
node.probe.clone()
};
if let Some(audio_duration) = probe
.and_then(|p| p.audio_streams)
.and_then(|a| a[0].duration.clone())
.and_then(|a| a.parse::<f64>().ok())
{
if node.out - node.seek > audio_duration - node.seek + 0.1 && node.duration >= node.out {
@ -323,7 +328,7 @@ pub fn filter_chains(
let mut filters = Filters::new();
if let Some(probe) = node.probe.as_ref() {
if probe.audio_streams.is_some() {
if probe.audio_streams.is_some() && !Path::new(&node.audio).is_file() {
filters.audio_map = "0:a".to_string();
}

View File

@ -1,6 +1,6 @@
#[macro_export]
macro_rules! vec_strings {
($($str:expr),*) => ({
vec![$(String::from($str),)*] as Vec<String>
vec![$($str.to_string(),)*] as Vec<String>
});
}

View File

@ -108,6 +108,7 @@ fn loop_playlist(
duration: item.duration,
category: item.category.clone(),
source: item.source.clone(),
audio: item.audio.clone(),
cmd: item.cmd.clone(),
probe: item.probe.clone(),
process: Some(true),

View File

@ -54,6 +54,9 @@ pub struct Media {
#[serde(deserialize_with = "null_string")]
pub source: String,
#[serde(default, deserialize_with = "null_string")]
pub audio: String,
#[serde(skip_serializing, skip_deserializing)]
pub cmd: Option<Vec<String>>,
@ -98,6 +101,7 @@ impl Media {
duration,
category: String::new(),
source: src.clone(),
audio: String::new(),
cmd: Some(vec!["-i".to_string(), src]),
filter: Some(vec![]),
probe,
@ -395,31 +399,79 @@ pub fn check_sync(config: &PlayoutConfig, delta: f64) -> bool {
}
/// Loop image until target duration is reached.
pub fn loop_image(source: &str, duration: f64) -> Vec<String> {
info!("Loop image <b><magenta>{source}</></b>, total duration: <yellow>{duration:.2}</>");
pub fn loop_image(node: &Media) -> Vec<String> {
let duration = node.out - node.seek;
let mut source_cmd: Vec<String> = vec_strings!["-loop", "1", "-i", node.source.clone()];
vec_strings!["-loop", "1", "-i", source, "-t", duration.to_string()]
}
info!(
"Loop image <b><magenta>{}</></b>, total duration: <yellow>{duration:.2}</>",
node.source
);
/// Loop source until target duration is reached.
pub fn loop_input(source: &str, source_duration: f64, target_duration: f64) -> Vec<String> {
let loop_count = (target_duration / source_duration).ceil() as i32;
let mut cmd = vec![];
if Path::new(&node.audio).is_file() {
if node.seek > 0.0 {
source_cmd.append(&mut vec_strings!["-ss", node.seek])
}
if loop_count > 1 {
info!("Loop <b><magenta>{source}</></b> <yellow>{loop_count}</> times, total duration: <yellow>{target_duration:.2}</>");
cmd.append(&mut vec_strings!["-stream_loop", loop_count.to_string()]);
source_cmd.append(&mut vec_strings!["-i", node.audio.clone()]);
}
cmd.append(&mut vec_strings![
"-i",
source,
"-t",
target_duration.to_string()
]);
source_cmd.append(&mut vec_strings!["-t", duration]);
cmd
source_cmd
}
/// Loop filler until target duration is reached.
pub fn loop_filler(node: &Media) -> Vec<String> {
let loop_count = (node.out / node.duration).ceil() as i32;
let mut source_cmd = vec![];
if loop_count > 1 {
info!("Loop <b><magenta>{}</></b> <yellow>{loop_count}</> times, total duration: <yellow>{:.2}</>", node.source, node.out);
source_cmd.append(&mut vec_strings!["-stream_loop", loop_count]);
}
source_cmd.append(&mut vec_strings!["-i", node.source, "-t", node.out]);
source_cmd
}
/// Set clip seek in and length value.
pub fn seek_and_length(node: &Media) -> Vec<String> {
let mut source_cmd = vec![];
let mut cut_audio = false;
if node.seek > 0.0 {
source_cmd.append(&mut vec_strings!["-ss", node.seek])
}
source_cmd.append(&mut vec_strings!["-i", node.source.clone()]);
if Path::new(&node.audio).is_file() {
let audio_probe = MediaProbe::new(&node.audio);
if node.seek > 0.0 {
source_cmd.append(&mut vec_strings!["-ss", node.seek])
}
source_cmd.append(&mut vec_strings!["-i", node.audio.clone()]);
if audio_probe
.audio_streams
.and_then(|a| a[0].duration.clone())
.and_then(|d| d.parse::<f64>().ok())
> Some(node.out - node.seek)
{
cut_audio = true;
}
}
if node.duration > node.out || cut_audio {
source_cmd.append(&mut vec_strings!["-t", node.out - node.seek]);
}
source_cmd
}
/// Create a dummy clip as a placeholder for missing video files.
@ -446,23 +498,6 @@ pub fn gen_dummy(config: &PlayoutConfig, duration: f64) -> (String, Vec<String>)
(source, cmd)
}
/// Set clip seek in and length value.
pub fn seek_and_length(src: String, seek: f64, out: f64, duration: f64) -> Vec<String> {
let mut source_cmd: Vec<String> = vec![];
if seek > 0.0 {
source_cmd.append(&mut vec!["-ss".to_string(), format!("{seek}")])
}
source_cmd.append(&mut vec!["-i".to_string(), src]);
if duration > out {
source_cmd.append(&mut vec!["-t".to_string(), format!("{}", out - seek)]);
}
source_cmd
}
/// Prepare output parameters
///
/// seek for multiple outputs and add mapping for it