From 79fe17cf65aaf8da47828889445a5b290e5da06d Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Fri, 29 Jul 2022 16:03:53 +0200 Subject: [PATCH] support image source, usefull for #113 --- ffplayout-engine/src/input/playlist.rs | 81 +++++++++++++++++++------- lib/src/filter/mod.rs | 15 ++++- lib/src/utils/config.rs | 6 ++ lib/src/utils/json_serializer.rs | 3 +- lib/src/utils/mod.rs | 11 +++- 5 files changed, 87 insertions(+), 29 deletions(-) diff --git a/ffplayout-engine/src/input/playlist.rs b/ffplayout-engine/src/input/playlist.rs index 6dc597e8..7aea0e7f 100644 --- a/ffplayout-engine/src/input/playlist.rs +++ b/ffplayout-engine/src/input/playlist.rs @@ -12,8 +12,8 @@ use simplelog::*; use ffplayout_lib::utils::{ check_sync, gen_dummy, get_delta, get_sec, is_close, is_remote, json_serializer::read_json, - loop_input, modified_time, seek_and_length, valid_source, Media, MediaProbe, PlayoutConfig, - PlayoutStatus, DUMMY_LEN, + loop_image, loop_input, modified_time, seek_and_length, valid_source, Media, MediaProbe, + PlayoutConfig, PlayoutStatus, DUMMY_LEN, IMAGE_CODEC_NAME, }; /// Struct for current playlist. @@ -461,26 +461,51 @@ fn gen_source( mut node: Media, filter_chain: &Arc>>, ) -> Media { + let duration = node.out - node.seek; + if valid_source(&node.source) { node.add_probe(); - node.cmd = Some(seek_and_length( - node.source.clone(), - node.seek, - node.out, - node.duration, - )); - node.add_filter(config, filter_chain); + + if node + .probe + .clone() + .and_then(|p| p.video_streams) + .and_then(|v| v[0].codec_name.clone()) + .filter(|c| IMAGE_CODEC_NAME.contains(&c.as_str())) + .is_some() + { + let cmd = loop_image(&node.source, duration); + node.cmd = Some(cmd); + } else { + node.cmd = Some(seek_and_length( + node.source.clone(), + node.seek, + node.out, + node.duration, + )); + } } else { - let duration = node.out - node.seek; + let probe = MediaProbe::new(&config.storage.filler_clip); + if node.source.is_empty() { warn!("Generate filler with {duration:.2} seconds length!"); } else { error!("Source not found: {}", node.source); } - let probe = MediaProbe::new(&config.storage.filler_clip); - - if let Some(length) = probe + if probe + .clone() + .video_streams + .and_then(|v| v[0].codec_name.clone()) + .filter(|c| IMAGE_CODEC_NAME.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.probe = Some(probe); + } else if let Some(length) = probe + .clone() .format .and_then(|f| f.duration) .and_then(|d| d.parse::().ok()) @@ -489,17 +514,17 @@ fn gen_source( let cmd = loop_input(&config.storage.filler_clip, length, duration); node.source = config.storage.filler_clip.clone(); node.cmd = Some(cmd); - node.add_probe(); + node.probe = Some(probe); } else { // create colored placeholder. let (source, cmd) = gen_dummy(config, duration); node.source = source; node.cmd = Some(cmd); } - - node.add_filter(config, filter_chain); } + node.add_filter(config, filter_chain); + node } @@ -566,12 +591,24 @@ fn handle_list_end(mut node: Media, total_delta: f64) -> Media { } node.process = Some(true); - node.cmd = Some(seek_and_length( - node.source.clone(), - node.seek, - node.out, - node.duration, - )); + + if node + .probe + .clone() + .and_then(|p| p.video_streams) + .and_then(|v| v[0].codec_name.clone()) + .filter(|c| IMAGE_CODEC_NAME.contains(&c.as_str())) + .is_some() + { + node.cmd = Some(loop_image(&node.source, total_delta)); + } else { + node.cmd = Some(seek_and_length( + node.source.clone(), + node.seek, + node.out, + node.duration, + )); + } node } diff --git a/lib/src/filter/mod.rs b/lib/src/filter/mod.rs index b9e2376a..7e39a915 100644 --- a/lib/src/filter/mod.rs +++ b/lib/src/filter/mod.rs @@ -75,11 +75,20 @@ fn deinterlace(field_order: &Option, chain: &mut Filters) { } } -fn pad(aspect: f64, chain: &mut Filters, config: &PlayoutConfig) { +fn pad(aspect: f64, chain: &mut Filters, v_stream: &ffprobe::Stream, config: &PlayoutConfig) { if !is_close(aspect, config.processing.aspect, 0.03) { + let mut scale = String::new(); + + if let (Some(w), Some(h)) = (v_stream.width, v_stream.height) { + if w > config.processing.width && aspect > config.processing.aspect { + scale = format!("scale={}:-1,", config.processing.width); + } else if h > config.processing.height && aspect < config.processing.aspect { + scale = format!("scale=-1:{},", config.processing.height); + } + } chain.add_filter( &format!( - "pad=max(iw\\,ih*({0}/{1})):ow/({0}/{1}):(ow-iw)/2:(oh-ih)/2", + "{scale}pad=max(iw\\,ih*({0}/{1})):ow/({0}/{1}):(ow-iw)/2:(oh-ih)/2", config.processing.width, config.processing.height ), "video", @@ -325,7 +334,7 @@ pub fn filter_chains( let frame_per_sec = fps_calc(&v_stream.r_frame_rate); deinterlace(&v_stream.field_order, &mut filters); - pad(aspect, &mut filters, config); + pad(aspect, &mut filters, v_stream, config); fps(frame_per_sec, &mut filters, config); scale(v_stream, aspect, &mut filters, config); } diff --git a/lib/src/utils/config.rs b/lib/src/utils/config.rs index 483e842a..f6518873 100644 --- a/lib/src/utils/config.rs +++ b/lib/src/utils/config.rs @@ -11,6 +11,12 @@ use shlex::split; use crate::utils::{free_tcp_socket, time_to_sec}; use crate::vec_strings; +pub const DUMMY_LEN: f64 = 60.0; +pub const IMAGE_CODEC_NAME: [&str; 23] = [ + "bmp", "dds", "dpx", "exr", "gif", "hdr", "j2k", "jpeg2000", "jpegls", "jpegxl", "mjpeg", + "pcx", "pfm", "pgm", "phm", "png", "psd", "ppm", "sgi", "svg", "targa", "tiff", "webp", +]; + /// Global Config /// /// This we init ones, when ffplayout is starting and use them globally in the hole program. diff --git a/lib/src/utils/json_serializer.rs b/lib/src/utils/json_serializer.rs index 22252867..cb53168b 100644 --- a/lib/src/utils/json_serializer.rs +++ b/lib/src/utils/json_serializer.rs @@ -10,10 +10,9 @@ use simplelog::*; use crate::utils::{ get_date, is_remote, modified_time, time_from_header, validate_playlist, Media, PlayoutConfig, + DUMMY_LEN, }; -pub const DUMMY_LEN: f64 = 60.0; - /// This is our main playlist object, it holds all necessary information for the current day. #[derive(Debug, Serialize, Deserialize, Clone)] pub struct JsonPlaylist { diff --git a/lib/src/utils/mod.rs b/lib/src/utils/mod.rs index 202df3db..fbc7dc4e 100644 --- a/lib/src/utils/mod.rs +++ b/lib/src/utils/mod.rs @@ -27,10 +27,10 @@ pub mod json_serializer; mod json_validate; mod logging; -pub use config::{self as playout_config, PlayoutConfig}; +pub use config::{self as playout_config, PlayoutConfig, DUMMY_LEN, IMAGE_CODEC_NAME}; pub use controller::{PlayerControl, PlayoutStatus, ProcessControl, ProcessUnit::*}; pub use generator::generate_playlist; -pub use json_serializer::{read_json, JsonPlaylist, DUMMY_LEN}; +pub use json_serializer::{read_json, JsonPlaylist}; pub use json_validate::validate_playlist; pub use logging::{init_logging, send_mail}; @@ -394,6 +394,13 @@ pub fn check_sync(config: &PlayoutConfig, delta: f64) -> bool { true } +/// Loop image until target duration is reached. +pub fn loop_image(source: &str, duration: f64) -> Vec { + info!("Loop image {source}, total duration: {duration:.2}"); + + vec_strings!["-loop", "1", "-i", source, "-t", duration.to_string()] +} + /// 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;