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::{
|
use ffplayout_lib::utils::{
|
||||||
check_sync, gen_dummy, get_delta, get_sec, is_close, is_remote, json_serializer::read_json,
|
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,
|
loop_image, loop_input, modified_time, seek_and_length, valid_source, Media, MediaProbe,
|
||||||
PlayoutStatus, DUMMY_LEN,
|
PlayoutConfig, PlayoutStatus, DUMMY_LEN, IMAGE_CODEC_NAME,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Struct for current playlist.
|
/// Struct for current playlist.
|
||||||
@ -461,26 +461,51 @@ fn gen_source(
|
|||||||
mut node: Media,
|
mut node: Media,
|
||||||
filter_chain: &Arc<Mutex<Vec<String>>>,
|
filter_chain: &Arc<Mutex<Vec<String>>>,
|
||||||
) -> Media {
|
) -> Media {
|
||||||
|
let duration = node.out - node.seek;
|
||||||
|
|
||||||
if valid_source(&node.source) {
|
if valid_source(&node.source) {
|
||||||
node.add_probe();
|
node.add_probe();
|
||||||
|
|
||||||
|
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.cmd = Some(seek_and_length(
|
||||||
node.source.clone(),
|
node.source.clone(),
|
||||||
node.seek,
|
node.seek,
|
||||||
node.out,
|
node.out,
|
||||||
node.duration,
|
node.duration,
|
||||||
));
|
));
|
||||||
node.add_filter(config, filter_chain);
|
}
|
||||||
} else {
|
} else {
|
||||||
let duration = node.out - node.seek;
|
let probe = MediaProbe::new(&config.storage.filler_clip);
|
||||||
|
|
||||||
if node.source.is_empty() {
|
if node.source.is_empty() {
|
||||||
warn!("Generate filler with <yellow>{duration:.2}</> seconds length!");
|
warn!("Generate filler with <yellow>{duration:.2}</> seconds length!");
|
||||||
} else {
|
} else {
|
||||||
error!("Source not found: <b><magenta>{}</></b>", node.source);
|
error!("Source not found: <b><magenta>{}</></b>", node.source);
|
||||||
}
|
}
|
||||||
|
|
||||||
let probe = MediaProbe::new(&config.storage.filler_clip);
|
if probe
|
||||||
|
.clone()
|
||||||
if let Some(length) = probe
|
.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
|
.format
|
||||||
.and_then(|f| f.duration)
|
.and_then(|f| f.duration)
|
||||||
.and_then(|d| d.parse::<f64>().ok())
|
.and_then(|d| d.parse::<f64>().ok())
|
||||||
@ -489,16 +514,16 @@ fn gen_source(
|
|||||||
let cmd = loop_input(&config.storage.filler_clip, length, duration);
|
let cmd = loop_input(&config.storage.filler_clip, length, duration);
|
||||||
node.source = config.storage.filler_clip.clone();
|
node.source = config.storage.filler_clip.clone();
|
||||||
node.cmd = Some(cmd);
|
node.cmd = Some(cmd);
|
||||||
node.add_probe();
|
node.probe = Some(probe);
|
||||||
} else {
|
} else {
|
||||||
// create colored placeholder.
|
// create colored placeholder.
|
||||||
let (source, cmd) = gen_dummy(config, duration);
|
let (source, cmd) = gen_dummy(config, duration);
|
||||||
node.source = source;
|
node.source = source;
|
||||||
node.cmd = Some(cmd);
|
node.cmd = Some(cmd);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
node.add_filter(config, filter_chain);
|
node.add_filter(config, filter_chain);
|
||||||
}
|
|
||||||
|
|
||||||
node
|
node
|
||||||
}
|
}
|
||||||
@ -566,12 +591,24 @@ fn handle_list_end(mut node: Media, total_delta: f64) -> Media {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.process = Some(true);
|
node.process = Some(true);
|
||||||
|
|
||||||
|
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.cmd = Some(seek_and_length(
|
||||||
node.source.clone(),
|
node.source.clone(),
|
||||||
node.seek,
|
node.seek,
|
||||||
node.out,
|
node.out,
|
||||||
node.duration,
|
node.duration,
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
node
|
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) {
|
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(
|
chain.add_filter(
|
||||||
&format!(
|
&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
|
config.processing.width, config.processing.height
|
||||||
),
|
),
|
||||||
"video",
|
"video",
|
||||||
@ -325,7 +334,7 @@ pub fn filter_chains(
|
|||||||
let frame_per_sec = fps_calc(&v_stream.r_frame_rate);
|
let frame_per_sec = fps_calc(&v_stream.r_frame_rate);
|
||||||
|
|
||||||
deinterlace(&v_stream.field_order, &mut filters);
|
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);
|
fps(frame_per_sec, &mut filters, config);
|
||||||
scale(v_stream, aspect, &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::utils::{free_tcp_socket, time_to_sec};
|
||||||
use crate::vec_strings;
|
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
|
/// Global Config
|
||||||
///
|
///
|
||||||
/// This we init ones, when ffplayout is starting and use them globally in the hole program.
|
/// 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::{
|
use crate::utils::{
|
||||||
get_date, is_remote, modified_time, time_from_header, validate_playlist, Media, PlayoutConfig,
|
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.
|
/// This is our main playlist object, it holds all necessary information for the current day.
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct JsonPlaylist {
|
pub struct JsonPlaylist {
|
||||||
|
@ -27,10 +27,10 @@ pub mod json_serializer;
|
|||||||
mod json_validate;
|
mod json_validate;
|
||||||
mod logging;
|
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 controller::{PlayerControl, PlayoutStatus, ProcessControl, ProcessUnit::*};
|
||||||
pub use generator::generate_playlist;
|
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 json_validate::validate_playlist;
|
||||||
pub use logging::{init_logging, send_mail};
|
pub use logging::{init_logging, send_mail};
|
||||||
|
|
||||||
@ -394,6 +394,13 @@ pub fn check_sync(config: &PlayoutConfig, delta: f64) -> bool {
|
|||||||
true
|
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.
|
/// Loop source until target duration is reached.
|
||||||
pub fn loop_input(source: &str, source_duration: f64, target_duration: f64) -> Vec<String> {
|
pub fn loop_input(source: &str, source_duration: f64, target_duration: f64) -> Vec<String> {
|
||||||
let loop_count = (target_duration / source_duration).ceil() as i32;
|
let loop_count = (target_duration / source_duration).ceil() as i32;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user