diff --git a/examples/deep_probe.rs b/examples/deep_probe.rs index 920b56b..46205a1 100644 --- a/examples/deep_probe.rs +++ b/examples/deep_probe.rs @@ -148,6 +148,22 @@ fn main() { th: None, pairs: Some(audio_qualif), }; + let freeze_duration_check = CheckParameterValue { + min: Some(2000), + max: None, + num: None, + den: None, + th: None, + pairs: None, + }; + let freeze_noise_check = CheckParameterValue { + min: None, + max: None, + num: None, + den: None, + th: Some(0.001), + pairs: None, + }; let mut silence_params = HashMap::new(); let mut black_params = HashMap::new(); @@ -159,6 +175,7 @@ fn main() { let mut loudness_params = HashMap::new(); let mut dualmono_params = HashMap::new(); let mut sine_params = HashMap::new(); + let mut freeze_params = HashMap::new(); silence_params.insert("duration".to_string(), duration_params); black_params.insert("duration".to_string(), black_duration_params); black_params.insert("picture".to_string(), black_picture_params); @@ -175,6 +192,8 @@ fn main() { ocr_params.insert("threshold".to_string(), ocr_check); sine_params.insert("duration".to_string(), sine_duration_check); sine_params.insert("pairing_list".to_string(), sine_qualif_check); + freeze_params.insert("duration".to_string(), freeze_duration_check); + freeze_params.insert("noise".to_string(), freeze_noise_check); let check = DeepProbeCheck { silence_detect: Some(silence_params), black_detect: Some(black_params), @@ -186,6 +205,7 @@ fn main() { loudness_detect: Some(loudness_params), dualmono_detect: Some(dualmono_params), sine_detect: Some(sine_params), + freeze_detect: Some(freeze_params), }; probe.process(LevelFilter::Off, check).unwrap(); let result = serde_json::to_string(&probe).unwrap(); diff --git a/src/probe/deep.rs b/src/probe/deep.rs index 49c15cd..0ec3f9e 100644 --- a/src/probe/deep.rs +++ b/src/probe/deep.rs @@ -6,6 +6,7 @@ use crate::probe::black_detect::{blackframes_init, detect_black_frames}; use crate::probe::blackfade_detect::detect_blackfade; use crate::probe::crop_detect::{black_borders_init, detect_black_borders}; use crate::probe::dualmono_detect::{detect_dualmono, dualmono_init}; +use crate::probe::freeze_detect::{detect_freeze, freeze_init}; use crate::probe::loudness_detect::{detect_loudness, loudness_init}; use crate::probe::ocr_detect::{detect_ocr, ocr_init}; use crate::probe::scene_detect::{detect_scene, scene_init}; @@ -112,6 +113,12 @@ pub struct SineResult { pub end: i64, } +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +pub struct FreezeResult { + pub start: i64, + pub end: i64, +} + #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] pub struct StreamProbeResult { stream_index: usize, @@ -148,6 +155,8 @@ pub struct StreamProbeResult { pub detected_black_and_silence: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub detected_sine: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub detected_freeze: Option>, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq, Serialize)] @@ -190,6 +199,7 @@ pub struct DeepProbeCheck { pub loudness_detect: Option>, pub dualmono_detect: Option>, pub sine_detect: Option>, + pub freeze_detect: Option>, } #[derive(Clone, Debug, Default)] @@ -237,6 +247,7 @@ pub enum CheckName { Loudness, DualMono, Tone, + Freeze, } impl fmt::Display for DeepProbeResult { @@ -295,6 +306,11 @@ impl fmt::Display for DeepProbeResult { "{:30} : {:?}", "Media offline detection", stream.detected_ocr )?; + writeln!( + f, + "{:30} : {:?}", + "Freeze detection", stream.detected_freeze + )?; writeln!( f, "{:30} : {:?}", @@ -341,6 +357,7 @@ impl StreamProbeResult { detected_dualmono: None, detected_sine: None, detected_bitrate: None, + detected_freeze: None, } } } @@ -589,6 +606,14 @@ impl DeepProbe { deep_orders.output_results.insert(CheckName::Tone, vec![]); } + if let Some(params) = deep_orders.check.freeze_detect.clone() { + deep_orders.orders.insert( + CheckName::Freeze, + freeze_init(&self.filename, deep_orders.video_indexes.clone(), params).unwrap(), + ); + deep_orders.output_results.insert(CheckName::Freeze, vec![]); + } + Ok(()) } @@ -693,6 +718,17 @@ impl DeepProbe { ) } } + CheckName::Freeze => { + if let Some(params) = deep_orders.check.freeze_detect.clone() { + detect_freeze( + &deep_orders.output_results, + &mut deep_orders.streams, + deep_orders.video_indexes.clone(), + params, + deep_orders.video_details.clone(), + ) + } + } } } @@ -929,6 +965,22 @@ fn deep_probe() { th: None, pairs: Some(audio_qualif), }; + let freeze_duration_check = CheckParameterValue { + min: Some(2000), + max: None, + num: None, + den: None, + th: None, + pairs: None, + }; + let freeze_noise_check = CheckParameterValue { + min: None, + max: None, + num: None, + den: None, + th: Some(0.001), + pairs: None, + }; let mut silence_params = HashMap::new(); let mut black_params = HashMap::new(); @@ -940,6 +992,7 @@ fn deep_probe() { let mut loudness_params = HashMap::new(); let mut dualmono_params = HashMap::new(); let mut sine_params = HashMap::new(); + let mut freeze_params = HashMap::new(); silence_params.insert("duration".to_string(), duration_params); black_params.insert("duration".to_string(), black_duration_params); black_params.insert("picture".to_string(), black_picture_params); @@ -956,6 +1009,8 @@ fn deep_probe() { ocr_params.insert("threshold".to_string(), ocr_check); sine_params.insert("duration".to_string(), sine_check); sine_params.insert("pairing_list".to_string(), sine_qualif_check); + freeze_params.insert("duration".to_string(), freeze_duration_check); + freeze_params.insert("noise".to_string(), freeze_noise_check); let check = DeepProbeCheck { silence_detect: Some(silence_params), black_detect: Some(black_params), @@ -967,6 +1022,7 @@ fn deep_probe() { loudness_detect: Some(loudness_params), dualmono_detect: Some(dualmono_params), sine_detect: Some(sine_params), + freeze_detect: Some(freeze_params), }; let id = Uuid::parse_str("ef7e3ad9-a08f-4cd0-9fec-3ac465bbdd85").unwrap(); let mut probe = DeepProbe::new("tests/test_file.mxf", id); diff --git a/src/probe/freeze_detect.rs b/src/probe/freeze_detect.rs new file mode 100644 index 0000000..83bf11f --- /dev/null +++ b/src/probe/freeze_detect.rs @@ -0,0 +1,152 @@ +use crate::{ + order::{ + filter_input::FilterInput, + filter_output::FilterOutput, + input::Input, + input_kind::InputKind, + output::Output, + output_kind::OutputKind, + stream::Stream, + Filter, Order, + OutputResult::{self, Entry}, + ParameterValue, + }, + probe::deep::{CheckName, CheckParameterValue, FreezeResult, StreamProbeResult, VideoDetails}, +}; +use std::collections::{BTreeMap, HashMap}; + +pub fn freeze_init( + filename: &str, + video_indexes: Vec, + params: HashMap, +) -> Result { + let mut order = create_graph(filename, video_indexes, params).unwrap(); + if let Err(msg) = order.setup() { + error!("{:?}", msg); + } + Ok(order) +} + +pub fn create_graph( + filename: &str, + video_indexes: Vec, + params: HashMap, +) -> Result { + let mut filters = vec![]; + let mut inputs = vec![]; + let mut outputs = vec![]; + for i in video_indexes { + let input_identifier = format!("video_input_{i}"); + let output_identifier = format!("video_output_{i}"); + + let input_streams = vec![Stream { + index: i, + label: Some(input_identifier.clone()), + }]; + + let mut freezedetect_params: HashMap = HashMap::new(); + if let Some(duration) = params.get("duration") { + if let Some(min_duration) = duration.min { + let min = (min_duration * 1000) as f64; + freezedetect_params.insert("duration".to_string(), ParameterValue::Float(min)); + } + } + if let Some(noise) = params.get("noise") { + if let Some(noise_th) = noise.th { + freezedetect_params.insert("noise".to_string(), ParameterValue::Float(noise_th)); + } + } + + filters.push(Filter { + name: "freezedetect".to_string(), + label: Some(format!("freezedetect_filter{i}")), + parameters: freezedetect_params.clone(), + inputs: Some(vec![FilterInput { + kind: InputKind::Stream, + stream_label: input_identifier, + }]), + outputs: Some(vec![FilterOutput { + stream_label: output_identifier.clone(), + }]), + }); + + inputs.push(Input::Streams { + id: i, + path: filename.to_string(), + streams: input_streams, + }); + outputs.push(Output { + kind: Some(OutputKind::VideoMetadata), + keys: vec![ + "lavfi.freezedetect.freeze_start".to_string(), + "lavfi.freezedetect.freeze_end".to_string(), + ], + stream: Some(output_identifier), + path: None, + streams: vec![], + parameters: HashMap::new(), + }); + } + + Order::new(inputs, filters, outputs) +} + +pub fn detect_freeze( + output_results: &BTreeMap>, + streams: &mut [StreamProbeResult], + video_indexes: Vec, + params: HashMap, + video_details: VideoDetails, +) { + for index in video_indexes { + streams[index as usize].detected_freeze = Some(vec![]); + } + let results = output_results.get(&CheckName::Freeze).unwrap(); + info!("END OF FREEZE PROCESS"); + info!("-> {:?} frames processed", results.len()); + + let end_from_duration = match video_details.stream_duration { + Some(duration) => ((duration - video_details.frame_duration) * 1000.0).round() as i64, + None => ((results.len() as f32 - 1.0) / video_details.frame_rate * 1000.0).round() as i64, + }; + let mut max_duration = None; + if let Some(duration) = params.get("duration") { + max_duration = duration.max; + } + + for result in results { + if let Entry(entry_map) = result { + if let Some(stream_id) = entry_map.get("stream_id") { + let index: i32 = stream_id.parse().unwrap(); + if streams[(index) as usize].detected_freeze.is_none() { + error!("Error : unexpected detection on stream ${index}"); + break; + } + let detected_freeze = streams[(index) as usize].detected_freeze.as_mut().unwrap(); + let mut freeze = FreezeResult { + start: 0, + end: end_from_duration, + }; + + if let Some(value) = entry_map.get("lavfi.freezedetect.freeze_start") { + freeze.start = (value.parse::().unwrap() * 1000.0).round() as i64; + detected_freeze.push(freeze); + } + if let Some(value) = entry_map.get("lavfi.freezedetect.freeze_end") { + if let Some(last_detect) = detected_freeze.last_mut() { + last_detect.end = ((value.parse::().unwrap() - video_details.frame_duration) + * 1000.0) + .round() as i64; + let freeze_duration = last_detect.end - last_detect.start + + (video_details.frame_duration * 1000.0).round() as i64; + if let Some(max) = max_duration { + if freeze_duration > max as i64 { + detected_freeze.pop(); + } + } + } + } + } + } + } +} diff --git a/src/probe/mod.rs b/src/probe/mod.rs index 687af0f..1505f8e 100644 --- a/src/probe/mod.rs +++ b/src/probe/mod.rs @@ -4,6 +4,7 @@ mod blackfade_detect; mod crop_detect; pub mod deep; mod dualmono_detect; +mod freeze_detect; mod loudness_detect; mod ocr_detect; mod scene_detect; diff --git a/tests/deep_probe.json b/tests/deep_probe.json index e030eb1..9e7dea5 100644 --- a/tests/deep_probe.json +++ b/tests/deep_probe.json @@ -60,6 +60,16 @@ "score": 33, "index": 2 } + ], + "detected_freeze": [ + { + "start": 0, + "end": 4920 + }, + { + "start": 9960, + "end": 14920 + } ] }, {