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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
#[macro_export] #[macro_export]
macro_rules! vec_strings { macro_rules! vec_strings {
($($str:expr),*) => ({ ($($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, duration: item.duration,
category: item.category.clone(), category: item.category.clone(),
source: item.source.clone(), source: item.source.clone(),
audio: item.audio.clone(),
cmd: item.cmd.clone(), cmd: item.cmd.clone(),
probe: item.probe.clone(), probe: item.probe.clone(),
process: Some(true), process: Some(true),

View File

@ -54,6 +54,9 @@ pub struct Media {
#[serde(deserialize_with = "null_string")] #[serde(deserialize_with = "null_string")]
pub source: String, pub source: String,
#[serde(default, deserialize_with = "null_string")]
pub audio: String,
#[serde(skip_serializing, skip_deserializing)] #[serde(skip_serializing, skip_deserializing)]
pub cmd: Option<Vec<String>>, pub cmd: Option<Vec<String>>,
@ -98,6 +101,7 @@ impl Media {
duration, duration,
category: String::new(), category: String::new(),
source: src.clone(), source: src.clone(),
audio: String::new(),
cmd: Some(vec!["-i".to_string(), src]), cmd: Some(vec!["-i".to_string(), src]),
filter: Some(vec![]), filter: Some(vec![]),
probe, probe,
@ -395,31 +399,79 @@ pub fn check_sync(config: &PlayoutConfig, delta: f64) -> bool {
} }
/// Loop image until target duration is reached. /// Loop image until target duration is reached.
pub fn loop_image(source: &str, duration: f64) -> Vec<String> { pub fn loop_image(node: &Media) -> Vec<String> {
info!("Loop image <b><magenta>{source}</></b>, total duration: <yellow>{duration:.2}</>"); 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. if Path::new(&node.audio).is_file() {
pub fn loop_input(source: &str, source_duration: f64, target_duration: f64) -> Vec<String> { if node.seek > 0.0 {
let loop_count = (target_duration / source_duration).ceil() as i32; source_cmd.append(&mut vec_strings!["-ss", node.seek])
let mut cmd = vec![]; }
if loop_count > 1 { source_cmd.append(&mut vec_strings!["-i", node.audio.clone()]);
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()]);
} }
cmd.append(&mut vec_strings![ source_cmd.append(&mut vec_strings!["-t", duration]);
"-i",
source,
"-t",
target_duration.to_string()
]);
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. /// 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) (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 /// Prepare output parameters
/// ///
/// seek for multiple outputs and add mapping for it /// seek for multiple outputs and add mapping for it