support image source, usefull for #113

This commit is contained in:
jb-alvarado 2022-07-29 16:03:53 +02:00
parent 88254226f7
commit 79fe17cf65
5 changed files with 87 additions and 29 deletions

View File

@ -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<Mutex<Vec<String>>>,
) -> 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 <yellow>{duration:.2}</> seconds length!");
} else {
error!("Source not found: <b><magenta>{}</></b>", 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::<f64>().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
}

View File

@ -75,11 +75,20 @@ fn deinterlace(field_order: &Option<String>, 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);
}

View File

@ -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.

View File

@ -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 {

View File

@ -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<String> {
info!("Loop image <b><magenta>{source}</></b>, total duration: <yellow>{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<String> {
let loop_count = (target_duration / source_duration).ceil() as i32;