integrate webvtt subtitles

This commit is contained in:
jb-alvarado 2024-09-25 17:18:50 +02:00
parent 1d4cdfaca6
commit 5061f21376
7 changed files with 133 additions and 12 deletions

View File

@ -51,11 +51,13 @@
"canonicalize", "canonicalize",
"ffpengine", "ffpengine",
"flexi", "flexi",
"httpauth",
"lettre", "lettre",
"libc", "libc",
"neli", "neli",
"nuxt", "nuxt",
"paris", "paris",
"Referer",
"reqwest", "reqwest",
"rsplit", "rsplit",
"rustls", "rustls",

View File

@ -72,6 +72,10 @@ pub fn ingest_server(
dummy_media.add_filter(&config, &None); dummy_media.add_filter(&config, &None);
let is_terminated = channel_mgr.is_terminated.clone(); let is_terminated = channel_mgr.is_terminated.clone();
let ingest_is_running = channel_mgr.ingest_is_running.clone(); let ingest_is_running = channel_mgr.ingest_is_running.clone();
let vtt_dummy = config
.channel
.storage_path
.join(&config.processing.vtt_dummy.clone().unwrap_or_default());
if let Some(ingest_input_cmd) = config.advanced.ingest.input_cmd { if let Some(ingest_input_cmd) = config.advanced.ingest.input_cmd {
server_cmd.append(&mut ingest_input_cmd.clone()); server_cmd.append(&mut ingest_input_cmd.clone());
@ -79,11 +83,19 @@ pub fn ingest_server(
server_cmd.append(&mut stream_input.clone()); server_cmd.append(&mut stream_input.clone());
if config.processing.vtt_enable && vtt_dummy.is_file() {
server_cmd.append(&mut vec_strings!["-i", vtt_dummy.to_string_lossy()]);
}
if let Some(mut filter) = dummy_media.filter { if let Some(mut filter) = dummy_media.filter {
server_cmd.append(&mut filter.cmd()); server_cmd.append(&mut filter.cmd());
server_cmd.append(&mut filter.map()); server_cmd.append(&mut filter.map());
} }
if config.processing.vtt_enable && vtt_dummy.is_file() {
server_cmd.append(&mut vec_strings!("-map", "1:s"));
}
if let Some(mut cmd) = config.processing.cmd { if let Some(mut cmd) = config.processing.cmd {
server_cmd.append(&mut cmd); server_cmd.append(&mut cmd);
} }

View File

@ -640,14 +640,14 @@ pub fn gen_source(
.filter(|c| IMAGE_FORMAT.contains(&c.as_str())) .filter(|c| IMAGE_FORMAT.contains(&c.as_str()))
.is_some() .is_some()
{ {
node.cmd = Some(loop_image(&node)); node.cmd = Some(loop_image(&config, &node));
} else { } else {
if node.seek > 0.0 && node.out > node.duration { if node.seek > 0.0 && node.out > node.duration {
warn!(target: Target::file_mail(), channel = config.general.channel_id; "Clip loops and has seek value: duplicate clip to separate loop and seek."); warn!(target: Target::file_mail(), channel = config.general.channel_id; "Clip loops and has seek value: duplicate clip to separate loop and seek.");
duplicate_for_seek_and_loop(&mut node, &manager.current_list); duplicate_for_seek_and_loop(&mut node, &manager.current_list);
} }
node.cmd = Some(seek_and_length(&mut node)); node.cmd = Some(seek_and_length(&config, &mut node));
} }
} else { } else {
trace!("clip index: {node_index} | last index: {last_index}"); trace!("clip index: {node_index} | last index: {last_index}");
@ -694,7 +694,7 @@ pub fn gen_source(
node.seek = 0.0; node.seek = 0.0;
node.out = filler_media.out; node.out = filler_media.out;
node.duration = filler_media.duration; node.duration = filler_media.duration;
node.cmd = Some(loop_filler(&node)); node.cmd = Some(loop_filler(&config, &node));
node.probe = filler_media.probe; node.probe = filler_media.probe;
} else { } else {
match MediaProbe::new(&config.storage.filler_path.to_string_lossy()) { match MediaProbe::new(&config.storage.filler_path.to_string_lossy()) {
@ -715,7 +715,7 @@ pub fn gen_source(
.clone() .clone()
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
node.cmd = Some(loop_image(&node)); node.cmd = Some(loop_image(&config, &node));
node.probe = Some(probe); node.probe = Some(probe);
} else if let Some(filler_duration) = probe } else if let Some(filler_duration) = probe
.clone() .clone()
@ -739,7 +739,7 @@ pub fn gen_source(
node.seek = 0.0; node.seek = 0.0;
node.out = filler_out; node.out = filler_out;
node.duration = filler_duration; node.duration = filler_duration;
node.cmd = Some(loop_filler(&node)); node.cmd = Some(loop_filler(&config, &node));
node.probe = Some(probe); node.probe = Some(probe);
} else { } else {
// Create colored placeholder. // Create colored placeholder.

View File

@ -62,6 +62,17 @@ fn ingest_to_hls_server(manager: ChannelManager) -> Result<(), ProcessError> {
server_prefix.append(&mut stream_input.clone()); server_prefix.append(&mut stream_input.clone());
if config.processing.vtt_enable {
let vtt_dummy = config
.channel
.storage_path
.join(&config.processing.vtt_dummy.clone().unwrap_or_default());
if vtt_dummy.is_file() {
server_prefix.append(&mut vec_strings!["-i", vtt_dummy.to_string_lossy()]);
}
}
let mut is_running; let mut is_running;
if let Some(url) = stream_input.iter().find(|s| s.contains("://")) { if let Some(url) = stream_input.iter().find(|s| s.contains("://")) {

View File

@ -150,6 +150,14 @@ pub fn player(manager: ChannelManager) -> Result<(), ProcessError> {
dec_cmd.append(&mut filter.map()); dec_cmd.append(&mut filter.map());
} }
if config.processing.vtt_enable && dec_cmd.iter().any(|s| s.ends_with(".vtt")) {
let mut i = dec_cmd.iter().filter(|&n| &*n == "-i").count();
if i > 0 {
i -= 1
}
dec_cmd.append(&mut vec_strings!("-map", format!("{i}:s"), "-c:s", "copy"));
}
if let Some(mut cmd) = config.processing.cmd.clone() { if let Some(mut cmd) = config.processing.cmd.clone() {
dec_cmd.append(&mut cmd); dec_cmd.append(&mut cmd);
} }

View File

@ -65,9 +65,9 @@ fn check_media(
.filter(|c| IMAGE_FORMAT.contains(&c.as_str())) .filter(|c| IMAGE_FORMAT.contains(&c.as_str()))
.is_some() .is_some()
{ {
node.cmd = Some(loop_image(&node)); node.cmd = Some(loop_image(&config, &node));
} else { } else {
node.cmd = Some(seek_and_length(&mut node)); node.cmd = Some(seek_and_length(&config, &mut node));
} }
node.add_filter(&config, &None); node.add_filter(&config, &None);

View File

@ -67,6 +67,10 @@ pub fn prepare_output_cmd(
let mut new_params = vec![]; let mut new_params = vec![];
let mut count = 0; let mut count = 0;
let re_v = Regex::new(r"\[?0:v(:0)?\]?").unwrap(); let re_v = Regex::new(r"\[?0:v(:0)?\]?").unwrap();
let vtt_dummy = config
.channel
.storage_path
.join(&config.processing.vtt_dummy.clone().unwrap_or_default());
if let Some(mut filter) = filters.clone() { if let Some(mut filter) = filters.clone() {
for (i, param) in output_params.iter().enumerate() { for (i, param) in output_params.iter().enumerate() {
@ -119,6 +123,15 @@ pub fn prepare_output_cmd(
} }
} }
if config.processing.vtt_enable && vtt_dummy.is_file() {
let mut i = cmd.iter().filter(|&n| &*n == "-i").count();
if i > 0 {
i -= 1
}
cmd.append(&mut vec_strings!("-map", format!("{i}:s?")));
}
cmd.append(&mut output_params); cmd.append(&mut output_params);
cmd cmd
@ -589,7 +602,7 @@ pub fn get_delta(config: &PlayoutConfig, begin: &f64) -> (f64, f64) {
} }
/// Loop image until target duration is reached. /// Loop image until target duration is reached.
pub fn loop_image(node: &Media) -> Vec<String> { pub fn loop_image(config: &PlayoutConfig, node: &Media) -> Vec<String> {
let duration = node.out - node.seek; let duration = node.out - node.seek;
let mut source_cmd: Vec<String> = vec_strings!["-loop", "1", "-i", node.source.clone()]; let mut source_cmd: Vec<String> = vec_strings!["-loop", "1", "-i", node.source.clone()];
@ -608,11 +621,30 @@ pub fn loop_image(node: &Media) -> Vec<String> {
source_cmd.append(&mut vec_strings!["-t", duration]); source_cmd.append(&mut vec_strings!["-t", duration]);
if config.processing.vtt_enable {
let vtt_file = Path::new(&node.source).with_extension("vtt");
let vtt_dummy = config
.channel
.storage_path
.join(&config.processing.vtt_dummy.clone().unwrap_or_default());
if vtt_file.is_file() {
source_cmd.append(&mut vec_strings![
"-i",
vtt_file.to_string_lossy(),
"-t",
node.out
]);
} else if vtt_dummy.is_file() {
source_cmd.append(&mut vec_strings!["-i", vtt_dummy.to_string_lossy()]);
}
}
source_cmd source_cmd
} }
/// Loop filler until target duration is reached. /// Loop filler until target duration is reached.
pub fn loop_filler(node: &Media) -> Vec<String> { pub fn loop_filler(config: &PlayoutConfig, node: &Media) -> Vec<String> {
let loop_count = (node.out / node.duration).ceil() as i32; let loop_count = (node.out / node.duration).ceil() as i32;
let mut source_cmd = vec![]; let mut source_cmd = vec![];
@ -624,11 +656,34 @@ pub fn loop_filler(node: &Media) -> Vec<String> {
source_cmd.append(&mut vec_strings!["-i", node.source, "-t", node.out]); source_cmd.append(&mut vec_strings!["-i", node.source, "-t", node.out]);
if config.processing.vtt_enable {
let vtt_file = Path::new(&node.source).with_extension("vtt");
let vtt_dummy = config
.channel
.storage_path
.join(&config.processing.vtt_dummy.clone().unwrap_or_default());
if vtt_file.is_file() {
if loop_count > 1 {
source_cmd.append(&mut vec_strings!["-stream_loop", loop_count]);
}
source_cmd.append(&mut vec_strings![
"-i",
vtt_file.to_string_lossy(),
"-t",
node.out
]);
} else if vtt_dummy.is_file() {
source_cmd.append(&mut vec_strings!["-i", vtt_dummy.to_string_lossy()]);
}
}
source_cmd source_cmd
} }
/// Set clip seek in and length value. /// Set clip seek in and length value.
pub fn seek_and_length(node: &mut Media) -> Vec<String> { pub fn seek_and_length(config: &PlayoutConfig, node: &mut Media) -> Vec<String> {
let loop_count = (node.out / node.duration).ceil() as i32; let loop_count = (node.out / node.duration).ceil() as i32;
let mut source_cmd = vec![]; let mut source_cmd = vec![];
let mut cut_audio = false; let mut cut_audio = false;
@ -673,6 +728,28 @@ pub fn seek_and_length(node: &mut Media) -> Vec<String> {
} }
} }
if config.processing.vtt_enable {
let vtt_file = Path::new(&node.source).with_extension("vtt");
let vtt_dummy = config
.channel
.storage_path
.join(&config.processing.vtt_dummy.clone().unwrap_or_default());
if vtt_file.is_file() {
if loop_count > 1 {
source_cmd.append(&mut vec_strings!["-stream_loop", loop_count]);
}
source_cmd.append(&mut vec_strings!["-i", vtt_file.to_string_lossy()]);
if node.duration > node.out || remote_source || loop_count > 1 {
source_cmd.append(&mut vec_strings!["-t", node.out - node.seek]);
}
} else if vtt_dummy.is_file() {
source_cmd.append(&mut vec_strings!["-i", vtt_dummy.to_string_lossy()]);
}
}
source_cmd source_cmd
} }
@ -683,7 +760,7 @@ pub fn gen_dummy(config: &PlayoutConfig, duration: f64) -> (String, Vec<String>)
"color=c={color}:s={}x{}:d={duration}", "color=c={color}:s={}x{}:d={duration}",
config.processing.width, config.processing.height config.processing.width, config.processing.height
); );
let cmd: Vec<String> = vec_strings![ let mut source_cmd: Vec<String> = vec_strings![
"-f", "-f",
"lavfi", "lavfi",
"-i", "-i",
@ -697,7 +774,18 @@ pub fn gen_dummy(config: &PlayoutConfig, duration: f64) -> (String, Vec<String>)
format!("anoisesrc=d={duration}:c=pink:r=48000:a=0.3") format!("anoisesrc=d={duration}:c=pink:r=48000:a=0.3")
]; ];
(source, cmd) if config.processing.vtt_enable {
let vtt_dummy = config
.channel
.storage_path
.join(&config.processing.vtt_dummy.clone().unwrap_or_default());
if vtt_dummy.is_file() {
source_cmd.append(&mut vec_strings!["-i", vtt_dummy.to_string_lossy()]);
}
}
(source, source_cmd)
} }
// fn get_output_count(cmd: &[String]) -> i32 { // fn get_output_count(cmd: &[String]) -> i32 {