diff --git a/Cargo.lock b/Cargo.lock
index 0c0d5170..b0b9a12e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1014,7 +1014,7 @@ dependencies = [
[[package]]
name = "ffplayout"
-version = "0.13.2"
+version = "0.14.0"
dependencies = [
"clap",
"crossbeam-channel 0.5.6",
@@ -1062,7 +1062,7 @@ dependencies = [
[[package]]
name = "ffplayout-lib"
-version = "0.13.1"
+version = "0.14.0"
dependencies = [
"chrono",
"crossbeam-channel 0.5.6",
diff --git a/ffplayout-engine/Cargo.toml b/ffplayout-engine/Cargo.toml
index b405a0d4..2c5c24a5 100644
--- a/ffplayout-engine/Cargo.toml
+++ b/ffplayout-engine/Cargo.toml
@@ -4,7 +4,7 @@ description = "24/7 playout based on rust and ffmpeg"
license = "GPL-3.0"
authors = ["Jonathan Baecker jonbae77@gmail.com"]
readme = "README.md"
-version = "0.13.2"
+version = "0.14.0"
edition = "2021"
[dependencies]
diff --git a/ffplayout-engine/src/input/playlist.rs b/ffplayout-engine/src/input/playlist.rs
index 06268c90..1db52101 100644
--- a/ffplayout-engine/src/input/playlist.rs
+++ b/ffplayout-engine/src/input/playlist.rs
@@ -479,8 +479,13 @@ fn gen_source(
error!("Source not found: {}>", node.source);
}
let (source, cmd) = gen_dummy(config, node.out - node.seek);
- node.source = source;
+ node.source = source.clone();
node.cmd = Some(cmd);
+
+ if source == config.storage.filler_clip {
+ node.add_probe();
+ }
+
node.add_filter(config, filter_chain);
}
diff --git a/ffplayout-engine/src/output/mod.rs b/ffplayout-engine/src/output/mod.rs
index e223413d..9184247b 100644
--- a/ffplayout-engine/src/output/mod.rs
+++ b/ffplayout-engine/src/output/mod.rs
@@ -119,11 +119,11 @@ pub fn player(
.stderr(Stdio::piped())
.spawn()
{
+ Ok(proc) => proc,
Err(e) => {
error!("couldn't spawn decoder process: {}", e);
panic!("couldn't spawn decoder process: {}", e)
}
- Ok(proc) => proc,
};
let mut dec_reader = BufReader::new(dec_proc.stdout.take().unwrap());
diff --git a/lib/Cargo.toml b/lib/Cargo.toml
index 2f890872..4c5a3fb4 100644
--- a/lib/Cargo.toml
+++ b/lib/Cargo.toml
@@ -4,7 +4,7 @@ description = "Library for ffplayout"
license = "GPL-3.0"
authors = ["Jonathan Baecker jonbae77@gmail.com"]
readme = "README.md"
-version = "0.13.1"
+version = "0.14.0"
edition = "2021"
[dependencies]
diff --git a/lib/src/filter/mod.rs b/lib/src/filter/mod.rs
index 9f9bc071..b9e2376a 100644
--- a/lib/src/filter/mod.rs
+++ b/lib/src/filter/mod.rs
@@ -167,19 +167,18 @@ fn overlay(node: &mut Media, chain: &mut Filters, config: &PlayoutConfig) {
}
fn extend_video(node: &mut Media, chain: &mut Filters) {
- if let Some(duration) = node
+ if let Some(video_duration) = node
.probe
.as_ref()
.and_then(|p| p.video_streams.as_ref())
.and_then(|v| v[0].duration.as_ref())
+ .and_then(|v| v.parse::().ok())
{
- let duration_float = duration.clone().parse::().unwrap();
-
- if node.out - node.seek > duration_float - node.seek + 0.1 {
+ if node.out - node.seek > video_duration - node.seek + 0.1 && node.duration >= node.out {
chain.add_filter(
&format!(
"tpad=stop_mode=add:stop_duration={}",
- (node.out - node.seek) - (duration_float - node.seek)
+ (node.out - node.seek) - (video_duration - node.seek)
),
"video",
)
@@ -221,15 +220,14 @@ fn add_audio(node: &mut Media, chain: &mut Filters) {
}
fn extend_audio(node: &mut Media, chain: &mut Filters) {
- if let Some(duration) = node
+ 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())
+ .and_then(|a| a.parse::().ok())
{
- let duration_float = duration.clone().parse::().unwrap();
-
- if node.out - node.seek > duration_float - node.seek + 0.1 {
+ if node.out - node.seek > audio_duration - node.seek + 0.1 && node.duration >= node.out {
chain.add_filter(&format!("apad=whole_dur={}", node.out - node.seek), "audio")
}
}
diff --git a/lib/src/utils/mod.rs b/lib/src/utils/mod.rs
index fa1b478b..4e9c8322 100644
--- a/lib/src/utils/mod.rs
+++ b/lib/src/utils/mod.rs
@@ -49,7 +49,7 @@ pub struct Media {
pub out: f64,
pub duration: f64,
- #[serde(deserialize_with = "null_string")]
+ #[serde(default, deserialize_with = "null_string")]
pub category: String,
#[serde(deserialize_with = "null_string")]
pub source: String,
@@ -394,8 +394,40 @@ pub fn check_sync(config: &PlayoutConfig, delta: f64) -> bool {
true
}
+/// Loop source until target duration is reached.
+fn loop_input(source: &str, source_duration: f64, target_duration: f64) -> Vec {
+ let loop_count = (target_duration / source_duration).ceil() as i32;
+
+ info!("Loop {source}> {loop_count}> times, total duration: {target_duration:.2}");
+
+ vec_strings![
+ "-stream_loop",
+ loop_count.to_string(),
+ "-i",
+ source,
+ "-t",
+ target_duration.to_string()
+ ]
+}
+
/// Create a dummy clip as a placeholder for missing video files.
pub fn gen_dummy(config: &PlayoutConfig, duration: f64) -> (String, Vec) {
+ // create placeholder from config filler.
+ if Path::new(&config.storage.filler_clip).is_file() {
+ let probe = MediaProbe::new(&config.storage.filler_clip);
+
+ if let Some(length) = probe
+ .format
+ .and_then(|f| f.duration)
+ .and_then(|d| d.parse::().ok())
+ {
+ let cmd = loop_input(&config.storage.filler_clip, length, duration);
+
+ return (config.storage.filler_clip.clone(), cmd);
+ }
+ }
+
+ // create colored placeholder.
let color = "#121212";
let source = format!(
"color=c={color}:s={}x{}:d={duration}",
@@ -551,6 +583,11 @@ pub fn stderr_reader(buffer: BufReader, suffix: &str) -> Result<(),
"[{suffix}]> {}",
format_log_line(line, "error")
)
+ } else if line.contains("[fatal]") {
+ error!(
+ "[{suffix}]> {}",
+ format_log_line(line, "fatal")
+ )
}
}