mod analyze;
#[macro_use]
mod cpu;
mod data;
use std::{
collections::{BTreeMap, BTreeSet},
sync::Arc,
time::Instant,
};
pub use av_decoders::{self, Decoder};
pub use num_rational::Rational32;
use v_frame::pixel::Pixel;
pub use crate::analyze::{SceneChangeDetector, ScenecutResult};
#[derive(Debug, Clone, Copy)]
pub struct DetectionOptions {
pub analysis_speed: SceneDetectionSpeed,
pub detect_flashes: bool,
pub min_scenecut_distance: Option<usize>,
pub max_scenecut_distance: Option<usize>,
pub lookahead_distance: usize,
}
impl Default for DetectionOptions {
#[inline]
fn default() -> Self {
DetectionOptions {
analysis_speed: SceneDetectionSpeed::Standard,
detect_flashes: true,
lookahead_distance: 5,
min_scenecut_distance: None,
max_scenecut_distance: None,
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
pub struct DetectionResults {
pub scene_changes: Vec<usize>,
pub scores: BTreeMap<usize, ScenecutResult>,
pub frame_count: usize,
pub speed: f64,
}
#[inline]
pub fn new_detector<T: Pixel>(
dec: &mut Decoder,
opts: DetectionOptions,
) -> anyhow::Result<SceneChangeDetector<T>> {
let video_details = dec.get_video_details();
Ok(SceneChangeDetector::new(
(video_details.width, video_details.height),
video_details.bit_depth,
video_details.frame_rate.recip(),
video_details.chroma_sampling,
if opts.detect_flashes {
opts.lookahead_distance
} else {
1
},
opts.analysis_speed,
opts.min_scenecut_distance.map_or(0, |val| val),
opts.max_scenecut_distance
.map_or_else(|| u32::MAX as usize, |val| val),
))
}
#[inline]
pub fn detect_scene_changes<T: Pixel>(
dec: &mut Decoder,
opts: DetectionOptions,
frame_limit: Option<usize>,
progress_callback: Option<&dyn Fn(usize, usize)>,
) -> anyhow::Result<DetectionResults> {
assert!(opts.lookahead_distance >= 1);
let mut detector = new_detector::<T>(dec, opts)?;
let mut frame_queue = BTreeMap::new();
let mut keyframes = BTreeSet::new();
keyframes.insert(0);
let mut scores = BTreeMap::new();
let start_time = Instant::now();
let mut frameno = 0;
loop {
let mut next_input_frameno = frame_queue.keys().last().copied().map_or(0, |key| key + 1);
while next_input_frameno
< (frameno + opts.lookahead_distance + 1).min(frame_limit.unwrap_or(usize::MAX))
{
let frame = dec.read_video_frame();
if let Ok(frame) = frame {
frame_queue.insert(next_input_frameno, Arc::new(frame));
next_input_frameno += 1;
} else {
break;
}
}
let frame_set = frame_queue
.values()
.take(opts.lookahead_distance + 2)
.collect::<Vec<_>>();
if frame_set.len() < 2 {
break;
}
if frameno == 0 {
keyframes.insert(frameno);
} else {
let (cut, score) = detector.analyze_next_frame(
&frame_set,
frameno,
*keyframes
.iter()
.last()
.expect("at least 1 keyframe should exist"),
);
if let Some(score) = score {
scores.insert(frameno, score);
}
if cut {
keyframes.insert(frameno);
}
};
if frameno > 0 {
frame_queue.remove(&(frameno - 1));
}
frameno += 1;
if let Some(progress_fn) = progress_callback {
progress_fn(frameno, keyframes.len());
}
if let Some(frame_limit) = frame_limit {
if frameno == frame_limit {
break;
}
}
}
Ok(DetectionResults {
scene_changes: keyframes.into_iter().collect(),
frame_count: frameno,
speed: frameno as f64 / start_time.elapsed().as_secs_f64(),
scores,
})
}
#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq)]
pub enum SceneDetectionSpeed {
Fast,
Standard,
None,
}