From fc43a1dca8bd9ee851505370a618061849a7b314 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Sun, 31 Jul 2022 22:03:15 +0200 Subject: [PATCH] support separate audio (#113), convert to string instead of String::from() --- ffplayout-engine/src/input/playlist.rs | 31 +++---- ffplayout-engine/src/output/mod.rs | 5 +- lib/src/filter/mod.rs | 19 +++-- lib/src/macros/mod.rs | 2 +- lib/src/utils/json_serializer.rs | 1 + lib/src/utils/mod.rs | 107 ++++++++++++++++--------- 6 files changed, 98 insertions(+), 67 deletions(-) diff --git a/ffplayout-engine/src/input/playlist.rs b/ffplayout-engine/src/input/playlist.rs index 94e9a89a..55768c50 100644 --- a/ffplayout-engine/src/input/playlist.rs +++ b/ffplayout-engine/src/input/playlist.rs @@ -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>>, ) -> 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::().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); diff --git a/ffplayout-engine/src/output/mod.rs b/ffplayout-engine/src/output/mod.rs index 9184247b..a9fbe320 100644 --- a/ffplayout-engine/src/output/mod.rs +++ b/ffplayout-engine/src/output/mod.rs @@ -92,9 +92,10 @@ pub fn player( } info!( - "Play for {}: {}", + "Play for {}: {} {}", sec_to_time(node.out - node.seek), - node.source + node.source, + node.audio ); let mut filter = node.filter.unwrap(); diff --git a/lib/src/filter/mod.rs b/lib/src/filter/mod.rs index 7e39a915..b062ff85 100644 --- a/lib/src/filter/mod.rs +++ b/lib/src/filter/mod.rs @@ -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 {} 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::().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(); } diff --git a/lib/src/macros/mod.rs b/lib/src/macros/mod.rs index 7951c866..3881e9a9 100644 --- a/lib/src/macros/mod.rs +++ b/lib/src/macros/mod.rs @@ -1,6 +1,6 @@ #[macro_export] macro_rules! vec_strings { ($($str:expr),*) => ({ - vec![$(String::from($str),)*] as Vec + vec![$($str.to_string(),)*] as Vec }); } diff --git a/lib/src/utils/json_serializer.rs b/lib/src/utils/json_serializer.rs index cb53168b..c2f810f0 100644 --- a/lib/src/utils/json_serializer.rs +++ b/lib/src/utils/json_serializer.rs @@ -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), diff --git a/lib/src/utils/mod.rs b/lib/src/utils/mod.rs index 4277645c..83d442fd 100644 --- a/lib/src/utils/mod.rs +++ b/lib/src/utils/mod.rs @@ -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>, @@ -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 { - info!("Loop image {source}, total duration: {duration:.2}"); +pub fn loop_image(node: &Media) -> Vec { + let duration = node.out - node.seek; + let mut source_cmd: Vec = vec_strings!["-loop", "1", "-i", node.source.clone()]; - vec_strings!["-loop", "1", "-i", source, "-t", duration.to_string()] -} + info!( + "Loop image {}, total duration: {duration:.2}", + node.source + ); -/// Loop source until target duration is reached. -pub fn loop_input(source: &str, source_duration: f64, target_duration: f64) -> Vec { - 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 {source} {loop_count} times, total duration: {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 { + let loop_count = (node.out / node.duration).ceil() as i32; + let mut source_cmd = vec![]; + + if loop_count > 1 { + info!("Loop {} {loop_count} times, total duration: {:.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 { + 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::().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) (source, cmd) } -/// Set clip seek in and length value. -pub fn seek_and_length(src: String, seek: f64, out: f64, duration: f64) -> Vec { - let mut source_cmd: Vec = 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