From 934fd289cb24171a2daa217439fd69e5ec9591b7 Mon Sep 17 00:00:00 2001 From: jb-alvarado Date: Wed, 2 Mar 2022 18:14:21 +0100 Subject: [PATCH] WIP Playlist error handling. Filtering is broken. --- src/filter/mod.rs | 149 ++++++++++++++++---------- src/output/desktop.rs | 6 ++ src/utils/json_reader.rs | 13 +-- src/utils/mod.rs | 25 +++-- src/utils/playlist.rs | 218 +++++++++++++++++++++++++++++++-------- 5 files changed, 291 insertions(+), 120 deletions(-) diff --git a/src/filter/mod.rs b/src/filter/mod.rs index 4aa54e67..b607730c 100644 --- a/src/filter/mod.rs +++ b/src/filter/mod.rs @@ -4,15 +4,15 @@ use std::path::Path; use crate::utils::{is_close, Config, Media}; #[derive(Debug, Serialize, Deserialize, Clone)] -struct Filters { - audio_chain: Option, - video_chain: Option, - audio_map: Option, - video_map: Option, +pub struct Filters { + pub audio_chain: Option, + pub video_chain: Option, + pub audio_map: Option, + pub video_map: Option, } impl Filters { - fn new() -> Self { + pub fn new() -> Self { Filters { audio_chain: None, video_chain: None, @@ -58,13 +58,26 @@ impl Filters { } } -fn deinterlace(field_order: Option, chain: &mut Filters) { - if field_order.is_some() && field_order.unwrap() != "progressive".to_string() { +fn deinterlace(node: Media, chain: &mut Filters) { + // if node.probe.is_none() { + // return; + // } + + let v_stream = &node.probe.unwrap().video_streams.unwrap()[0]; + + if v_stream.field_order.is_some() && v_stream.field_order.unwrap() != "progressive".to_string() + { chain.add_filter("yadif=0:-1:0".into(), "video".into()) } } -fn pad(aspect: f64, chain: &mut Filters, config: &Config) { +fn pad(node: &Media, chain: &mut Filters, config: &Config) { + if node.probe.is_none() { + return; + } + + let aspect = aspect_calc(node); + if !is_close(aspect, config.processing.aspect, 0.03) { if aspect < config.processing.aspect { chain.add_filter( @@ -88,7 +101,13 @@ fn pad(aspect: f64, chain: &mut Filters, config: &Config) { } } -fn fps(fps: f64, chain: &mut Filters, config: &Config) { +fn fps(node: &Media, chain: &mut Filters, config: &Config) { + if node.probe.is_none() { + return; + } + + let fps = fps_calc(node); + if fps != config.processing.fps { chain.add_filter( format!("fps={}", config.processing.fps).into(), @@ -97,7 +116,17 @@ fn fps(fps: f64, chain: &mut Filters, config: &Config) { } } -fn scale(width: i64, height: i64, aspect: f64, chain: &mut Filters, config: &Config) { +fn scale(node: &Media, chain: &mut Filters, config: &Config) { + if node.probe.is_none() { + return; + } + + let v_stream = node.probe.unwrap().video_streams.unwrap()[0]; + let aspect = aspect_calc(node); + + let width = v_stream.width.unwrap(); + let height = v_stream.height.unwrap(); + if width != config.processing.width || height != config.processing.height { chain.add_filter( format!( @@ -136,7 +165,7 @@ fn fade(node: &mut Media, chain: &mut Filters, codec_type: String) { } } -fn overlay(node: &mut Media, chain: &mut Filters, config: &Config) { +pub fn overlay(node: &mut Media, chain: &mut Filters, config: &Config) { if config.processing.add_logo && Path::new(&config.processing.logo).is_file() && node.category != "advertisement".to_string() @@ -146,7 +175,10 @@ fn overlay(node: &mut Media, chain: &mut Filters, config: &Config) { config.processing.logo_opacity ); let logo_loop = "loop=loop=-1:size=1:start=0"; - let mut logo_chain = format!("null[v];movie={},{logo_loop},{opacity}", config.processing.logo); + let mut logo_chain = format!( + "null[v];movie={},{logo_loop},{opacity}", + config.processing.logo + ); if node.last_ad.unwrap() { logo_chain.push_str(",fade=in:st=0:d=1.0:alpha=1") @@ -174,7 +206,13 @@ fn extend_video(node: &mut Media, chain: &mut Filters) { let duration_float = video_duration.clone().unwrap().parse::().unwrap(); if node.out - node.seek > duration_float - node.seek + 0.1 { - chain.add_filter(format!("tpad=stop_mode=add:stop_duration={}", (node.out - node.seek) - (duration_float - node.seek)) , "video".into()) + chain.add_filter( + format!( + "tpad=stop_mode=add:stop_duration={}", + (node.out - node.seek) - (duration_float - node.seek) + ), + "video".into(), + ) } } } @@ -200,8 +238,11 @@ fn extend_audio(node: &mut Media, chain: &mut Filters) { if audio_duration.is_some() { let duration_float = audio_duration.clone().unwrap().parse::().unwrap(); - if node.out - node.seek > duration_float- node.seek + 0.1 { - chain.add_filter(format!("apad=whole_dur={}", node.out - node.seek), "audio".into()) + if node.out - node.seek > duration_float - node.seek + 0.1 { + chain.add_filter( + format!("apad=whole_dur={}", node.out - node.seek), + "audio".into(), + ) } } } @@ -209,53 +250,49 @@ fn extend_audio(node: &mut Media, chain: &mut Filters) { fn audio_volume(chain: &mut Filters, config: &Config) { if config.processing.volume != 1.0 { - chain.add_filter(format!("volume={}", config.processing.volume), "audio".into()) + chain.add_filter( + format!("volume={}", config.processing.volume), + "audio".into(), + ) } } +fn aspect_calc(node: &Media) -> f64 { + let v_stream = node.probe.unwrap().video_streams.unwrap()[0]; + let aspect_string = v_stream.display_aspect_ratio.clone().unwrap(); + let aspect_vec: Vec<&str> = aspect_string.split(':').collect(); + let w: f64 = aspect_vec[0].parse().unwrap(); + let h: f64 = aspect_vec[1].parse().unwrap(); + let source_aspect: f64 = w as f64 / h as f64; + + source_aspect +} + +fn fps_calc(node: &Media) -> f64 { + let v_stream = node.probe.unwrap().video_streams.unwrap()[0]; + let frame_rate_vec: Vec<&str> = v_stream.r_frame_rate.split('/').collect(); + let rate: f64 = frame_rate_vec[0].parse().unwrap(); + let factor: f64 = frame_rate_vec[1].parse().unwrap(); + let fps: f64 = rate / factor; + + fps +} + pub fn filter_chains(node: &mut Media, config: &Config) -> Vec { let mut filters = Filters::new(); - let probe = node.probe.clone(); - match probe { - Some(p) => { - // let a_stream = &p.audio_streams.unwrap()[0]; - if p.video_streams.is_some() { - let v_stream = &p.video_streams.unwrap()[0]; - let aspect_string = v_stream.display_aspect_ratio.clone().unwrap(); - let aspect_vec: Vec<&str> = aspect_string.split(':').collect(); - let w: f64 = aspect_vec[0].parse().unwrap(); - let h: f64 = aspect_vec[1].parse().unwrap(); - let source_aspect: f64 = w as f64 / h as f64; - let frame_rate_vec: Vec<&str> = v_stream.r_frame_rate.split('/').collect(); - let rate: f64 = frame_rate_vec[0].parse().unwrap(); - let factor: f64 = frame_rate_vec[1].parse().unwrap(); - let frames_per_second: f64 = rate / factor; + deinterlace(node.clone(), &mut filters); + pad(&node, &mut filters, &config); + fps(&node, &mut filters, &config); + scale(&node, &mut filters, &config); + extend_video(node, &mut filters); + fade(node, &mut filters, "video".into()); + overlay(node, &mut filters, &config); - deinterlace(v_stream.field_order.clone(), &mut filters); - pad(source_aspect, &mut filters, &config); - fps(frames_per_second, &mut filters, &config); - scale( - v_stream.width.unwrap(), - v_stream.height.unwrap(), - source_aspect, - &mut filters, - &config, - ); - extend_video(node, &mut filters); - fade(node, &mut filters, "video".into()); - overlay(node, &mut filters, &config); - - add_audio(node, &mut filters); - extend_audio(node, &mut filters); - fade(node, &mut filters, "audio".into()); - audio_volume(&mut filters, &config); - } - } - None => { - println!("Clip has no media probe object. No filter applied!") - } - } + add_audio(node, &mut filters); + extend_audio(node, &mut filters); + fade(node, &mut filters, "audio".into()); + audio_volume(&mut filters, &config); let mut filter_cmd = vec![]; let mut filter_str: String = "".to_string(); diff --git a/src/output/desktop.rs b/src/output/desktop.rs index 7b51cc83..8a86f18c 100644 --- a/src/output/desktop.rs +++ b/src/output/desktop.rs @@ -111,6 +111,10 @@ pub fn play(config: Config) { None => break }; + if !node.process.unwrap() { + continue + } + info!( "Play for {}: {}", sec_to_time(node.out - node.seek), @@ -168,6 +172,8 @@ pub fn play(config: Config) { if let Err(e) = dec_proc.wait() { panic!("Decoder error: {:?}", e) }; + + sleep(Duration::from_secs(1)); } sleep(Duration::from_secs(1)); diff --git a/src/utils/json_reader.rs b/src/utils/json_reader.rs index 5f79b945..5eaf311a 100644 --- a/src/utils/json_reader.rs +++ b/src/utils/json_reader.rs @@ -57,25 +57,14 @@ pub fn read_json(config: &Config, seek: bool) -> Playlist { time_sec += length_sec } - let cloned_program = playlist.program.clone(); - for (i, item) in playlist.program.iter_mut().enumerate() { item.begin = Some(start_sec); item.index = Some(i); item.last_ad = Some(false); item.next_ad = Some(false); + item.process = Some(true); let mut source_cmd: Vec = vec![]; - if i > 0 && cloned_program[i - 1].category == "advertisement".to_string() { - item.last_ad = Some(true); - } - - if i + 1 < cloned_program.len() { - if cloned_program[i + 1].category == "advertisement".to_string() { - item.next_ad = Some(true); - } - } - if seek_first { let tmp_length = item.out - item.seek; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 924c5ea7..0a42342e 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,7 +2,7 @@ use chrono::prelude::*; use chrono::Duration; use ffprobe::{ffprobe, Format, Stream}; use serde::{Deserialize, Serialize}; -use std::{fs::metadata, time, time::UNIX_EPOCH}; +use std::{fs::metadata, path::Path, time, time::UNIX_EPOCH}; use simplelog::*; @@ -37,16 +37,22 @@ pub struct Media { pub probe: Option, pub last_ad: Option, pub next_ad: Option, + pub process: Option, } impl Media { fn new(index: usize, src: String) -> Self { - let probe = MediaProbe::new(src.clone()); + let mut duration: f64 = 0.0; + let mut probe = None; - let duration: f64 = match &probe.clone().format.unwrap().duration { - Some(dur) => dur.parse().unwrap(), - None => 0.0, - }; + if Path::new("src").is_file() { + probe = Some(MediaProbe::new(src.clone())); + + duration = match probe.clone().unwrap().format.unwrap().duration { + Some(dur) => dur.parse().unwrap(), + None => 0.0, + }; + } Self { begin: None, @@ -58,9 +64,10 @@ impl Media { source: src.clone(), cmd: Some(vec!["-i".to_string(), src]), filter: Some(vec![]), - probe: Some(probe), + probe: probe, last_ad: Some(false), next_ad: Some(false), + process: Some(true), } } @@ -70,7 +77,7 @@ impl Media { fn add_filter(&mut self, config: &Config) { let mut node = self.clone(); - self.filter = Some(filter_chains(&mut node, &config)); + self.filter = Some(filter_chains(&mut node, &config)) } } @@ -235,7 +242,7 @@ pub fn get_delta(begin: &f64, config: &Config) -> (f64, f64) { pub fn check_sync(delta: f64, config: &Config) -> bool { if delta.abs() > config.general.stop_threshold && config.general.stop_threshold > 0.0 { error!("Start time out of sync for {} seconds", delta); - return false + return false; } true diff --git a/src/utils/playlist.rs b/src/utils/playlist.rs index e22c408d..15c21e35 100644 --- a/src/utils/playlist.rs +++ b/src/utils/playlist.rs @@ -3,16 +3,19 @@ use std::path::Path; use simplelog::*; use crate::utils::{ - check_sync, gen_dummy, get_delta, json_reader::read_json, modified_time, time_to_sec, Config, - Media, + check_sync, gen_dummy, get_delta, get_sec, json_reader::read_json, modified_time, time_to_sec, + Config, Media, }; +use crate::filter::{overlay, Filters}; + #[derive(Debug)] pub struct CurrentProgram { config: Config, json_mod: String, json_path: String, nodes: Vec, + current_node: Media, init: bool, idx: usize, } @@ -26,6 +29,7 @@ impl CurrentProgram { json_mod: json.modified.unwrap(), json_path: json.current_file.unwrap(), nodes: json.program.into(), + current_node: Media::new(json.start_index.unwrap(), "".to_string()), init: true, idx: json.start_index.unwrap(), } @@ -43,24 +47,47 @@ impl CurrentProgram { } } - fn check_for_next_playlist(&mut self, node: &Media, last: bool) { - let mut out = node.out.clone(); + fn check_for_next_playlist(&mut self, last: bool) -> f64 { + let mut out = self.current_node.out.clone(); let start_sec = time_to_sec(&self.config.playlist.day_start); let mut delta = 0.0; - if node.duration > node.out { - out = node.duration.clone() + if self.current_node.duration > self.current_node.out { + out = self.current_node.duration.clone() } if last { - let seek = if node.seek > 0.0 { node.seek } else { 0.0 }; - (delta, _) = get_delta(&node.begin.unwrap().clone(), &self.config); + let seek = if self.current_node.seek > 0.0 { + self.current_node.seek + } else { + 0.0 + }; + (delta, _) = get_delta(&self.current_node.begin.unwrap().clone(), &self.config); delta += seek + self.config.general.stop_threshold; } - let next_start = node.begin.unwrap() - start_sec + out + delta; + let next_start = self.current_node.begin.unwrap() - start_sec + out + delta; - println!("{}", next_start); + // println!("{}", next_start); + + next_start + } + + fn is_ad(&mut self, i: usize, next: bool) -> Option { + if next { + if i + 1 < self.nodes.len() && self.nodes[i + 1].category == "advertisement".to_string() + { + return Some(true); + } else { + return Some(false); + } + } else { + if i > 0 && i - 1 < self.nodes.len() && self.nodes[i - 1].category == "advertisement".to_string() { + return Some(true); + } else { + return Some(false); + } + } } } @@ -70,72 +97,167 @@ impl Iterator for CurrentProgram { fn next(&mut self) -> Option { if self.idx < self.nodes.len() { self.check_update(); - let mut current; if self.init { - current = handle_list_init(self.nodes[self.idx].clone(), &self.config); + self.current_node = handle_list_init(self.nodes[self.idx].clone(), &self.config); self.init = false; } else { - current = self.nodes[self.idx].clone(); + let new_source = timed_source(self.nodes[self.idx].clone(), &self.config, false); + self.current_node = match new_source { + Some(src) => src, + None => { + let mut media = Media::new(self.idx, "".to_string()); + media.process = Some(false); - let (delta, _) = get_delta(¤t.begin.unwrap(), &self.config); + media + } + }; + + let (delta, _) = get_delta(&self.current_node.begin.unwrap(), &self.config); debug!("Delta: {delta}"); let sync = check_sync(delta, &self.config); if !sync { - current.cmd = None; + self.current_node.cmd = None; - return Some(current); + return Some(self.current_node.clone()); } } - self.idx += 1; - current = gen_source(current, &self.config); + self.current_node.last_ad = self.is_ad(self.idx, false); + self.current_node.next_ad = self.is_ad(self.idx, false); - self.check_for_next_playlist(¤t, true); - Some(current) + self.idx += 1; + + self.check_for_next_playlist(false); + Some(self.current_node.clone()) } else { - let mut current; + let (_, time_diff) = get_delta(&get_sec(), &self.config); + let pl_time_diff = self.check_for_next_playlist(true); + let pl_delta = time_to_sec(&self.config.playlist.length) - pl_time_diff.abs(); + let mut last_ad = self.is_ad(self.idx, false); + + if time_diff.abs() > pl_delta && time_diff.abs() > self.config.general.stop_threshold { + self.current_node = Media::new(self.idx + 1, "".to_string()); + self.current_node.begin = Some(get_sec()); + let mut duration = time_diff.abs(); + + if duration > 60.0 { + duration = 60.0; + } + self.current_node.duration = duration; + self.current_node.out = duration; + self.current_node = gen_source(self.current_node.clone(), &self.config); + self.nodes.push(self.current_node.clone()); + + last_ad = self.is_ad(self.idx + 1, false); + self.current_node.last_ad = last_ad; + + + return Some(self.current_node.clone()); + } + let json = read_json(&self.config, false); self.json_mod = json.modified.unwrap(); self.json_path = json.current_file.unwrap(); self.nodes = json.program.into(); - self.idx = 1; if self.init { - current = handle_list_init(self.nodes[0].clone(), &self.config); + self.current_node = handle_list_init(self.nodes[0].clone(), &self.config); self.init = false; } else { - current = self.nodes[0].clone(); + let new_source = timed_source(self.nodes[0].clone(), &self.config, false); + self.current_node = match new_source { + Some(src) => src, + None => { + let mut media = Media::new(self.idx, "".to_string()); + media.process = Some(false); - let (delta, _) = get_delta(¤t.begin.unwrap(), &self.config); + media + } + }; + + let (delta, _) = get_delta(&self.current_node.begin.unwrap(), &self.config); debug!("Delta: {delta}"); let sync = check_sync(delta, &self.config); if !sync { - current.cmd = None; + self.current_node.cmd = None; - return Some(current); + return Some(self.current_node.clone()); } } - current = gen_source(current, &self.config); + self.current_node.last_ad = last_ad; + self.current_node.next_ad = self.is_ad(0, false); - Some(current) + self.idx = 1; + + Some(self.current_node.clone()) } } } +fn timed_source(node: Media, config: &Config, last: bool) -> Option { + // prepare input clip + // check begin and length from clip + // return clip only if we are in 24 hours time range + + let (delta, total_delta) = get_delta(&node.begin.unwrap(), &config); + let mut new_node = None; + + if config.playlist.day_start.contains(":") && config.playlist.length.contains(":") { + debug!("Delta: {delta}"); + check_sync(delta, &config); + } + + if (total_delta > node.out - node.seek && !last) || !config.playlist.length.contains(":") { + // when we are in the 24 hour range, get the clip + new_node = Some(gen_source(node, &config)); + } else if total_delta <= 0.0 { + info!("Begin is over play time, skip: {}", node.source); + } else if total_delta < node.duration - node.seek || last { + println!("handle list end"); + new_node = handle_list_end(node, total_delta); + } + + return new_node; +} + fn gen_source(mut node: Media, config: &Config) -> Media { if Path::new(&node.source).is_file() { node.add_probe(); node.add_filter(&config); } else { - error!("File not found: {}", node.source); - let dummy = gen_dummy(node.out - node.seek, &config); - node.source = dummy.0; - node.cmd = Some(dummy.1); - node.filter = Some(vec![]); + if node.source.chars().count() == 0 { + warn!( + "Generate filler with {} seconds length!", + node.out - node.seek + ); + } else { + error!("File not found: {}", node.source); + } + let (source, cmd) = gen_dummy(node.out - node.seek, &config); + node.source = source; + node.cmd = Some(cmd); + let mut filters = Filters::new(); + let mut chain: Vec = vec![]; + overlay(&mut node, &mut filters, &config); + + if filters.video_chain.is_some() { + let mut filter_str: String = "".to_string(); + filter_str.push_str(filters.video_chain.unwrap().as_str()); + filter_str.push_str(filters.video_map.clone().unwrap().as_str()); + + chain.push("-filter_complex".to_string()); + chain.push(filter_str); + chain.push("-map".to_string()); + chain.push(filters.video_map.unwrap()); + chain.push("-map".to_string()); + chain.push("1:a".to_string()); + } + + node.filter = Some(chain); } node @@ -146,19 +268,18 @@ fn handle_list_init(mut node: Media, config: &Config) -> Media { // this we have to figure out and calculate the right length debug!("Playlist init"); - println!("{:?}", node); let (_, total_delta) = get_delta(&node.begin.unwrap(), config); - let mut out = node.out; if node.out - node.seek > total_delta { - out = total_delta + node.seek + out = total_delta + node.seek; } node.out = out; + let new_node = gen_source(node, &config); - node + new_node } fn handle_list_end(mut node: Media, total_delta: f64) -> Option { @@ -168,22 +289,33 @@ fn handle_list_end(mut node: Media, total_delta: f64) -> Option { debug!("Playlist end"); - let mut out = if node.seek > 0.0 {node.seek + total_delta} else {total_delta}; + let mut out = if node.seek > 0.0 { + node.seek + total_delta + } else { + total_delta + }; // prevent looping if out > node.duration { out = node.duration } else { - warn!("Clip length is not in time, new duration is: {:.2}", total_delta) + warn!( + "Clip length is not in time, new duration is: {:.2}", + total_delta + ) } - if node.duration > total_delta && total_delta > 1.0 && node.duration - node.seek >= total_delta { + if node.duration > total_delta && total_delta > 1.0 && node.duration - node.seek >= total_delta + { node.out = out; } else if node.duration > total_delta && total_delta < 1.0 { warn!("Last clip less then 1 second long, skip: {}", node.source); - return None + return None; } else { - error!("Playlist is not long enough: {:.2} seconds needed", total_delta); + error!( + "Playlist is not long enough: {:.2} seconds needed", + total_delta + ); } Some(node)