support image source, usefull for #113
This commit is contained in:
parent
88254226f7
commit
79fe17cf65
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user