#![allow(clippy::too_many_arguments)]
mod y4m;
pub use rav1e::scenechange::SceneChangeDetector;
use ::y4m::Decoder;
use rav1e::config::{CpuFeatureLevel, EncoderConfig};
use rav1e::prelude::{Pixel, Sequence};
use std::collections::{BTreeMap, BTreeSet};
use std::io::Read;
use std::sync::Arc;
use std::time::Instant;
#[derive(Debug, Clone, Copy)]
pub struct DetectionOptions {
pub fast_analysis: SceneDetectionSpeed,
pub ignore_flashes: bool,
pub min_scenecut_distance: Option<usize>,
pub max_scenecut_distance: Option<usize>,
pub lookahead_distance: usize,
}
impl Default for DetectionOptions {
fn default() -> Self {
DetectionOptions {
fast_analysis: SceneDetectionSpeed::Slow,
ignore_flashes: false,
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 frame_count: usize,
pub speed: f64,
}
pub type ProgressCallback = Box<dyn Fn(usize, usize)>;
pub fn new_detector<R: Read, T: Pixel>(
dec: &mut Decoder<R>,
opts: DetectionOptions,
) -> SceneChangeDetector<T> {
let video_details = y4m::get_video_details(dec);
let mut config =
EncoderConfig::with_speed_preset(if opts.fast_analysis == SceneDetectionSpeed::Fast {
10
} else if opts.fast_analysis == SceneDetectionSpeed::Medium {
8
} else {
6
});
config.min_key_frame_interval = opts
.min_scenecut_distance
.map(|val| val as u64)
.unwrap_or(0);
config.max_key_frame_interval = opts
.max_scenecut_distance
.map(|val| val as u64)
.unwrap_or(u32::max_value() as u64);
config.width = video_details.width;
config.height = video_details.height;
config.bit_depth = video_details.bit_depth;
config.time_base = video_details.time_base;
config.chroma_sampling = video_details.chroma_sampling;
config.chroma_sample_position = video_details.chroma_sample_position;
let sequence = Arc::new(Sequence::new(&config));
SceneChangeDetector::new(
config,
CpuFeatureLevel::default(),
opts.lookahead_distance,
sequence,
)
}
pub fn detect_scene_changes<R: Read, T: Pixel>(
dec: &mut Decoder<R>,
opts: DetectionOptions,
progress_callback: Option<ProgressCallback>,
) -> DetectionResults {
assert!(opts.lookahead_distance >= 1);
let mut detector = new_detector(dec, opts);
let video_details = y4m::get_video_details(dec);
let mut frame_queue = BTreeMap::new();
let mut keyframes = BTreeSet::new();
keyframes.insert(0);
let start_time = Instant::now();
let mut frameno = 0;
loop {
let mut next_input_frameno = frame_queue
.keys()
.last()
.copied()
.map(|key| key + 1)
.unwrap_or(0);
while next_input_frameno < frameno + opts.lookahead_distance {
let frame = y4m::read_video_frame::<R, T>(dec, &video_details);
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()
.cloned()
.take(opts.lookahead_distance + 1)
.collect::<Vec<_>>();
if frame_set.len() < 2 {
break;
}
if frameno == 0
|| detector.analyze_next_frame(
&frame_set,
frameno as u64,
*keyframes.iter().last().unwrap(),
)
{
keyframes.insert(frameno as u64);
};
if frameno > 0 {
frame_queue.remove(&(frameno - 1));
}
frameno += 1;
if let Some(ref progress_fn) = progress_callback {
progress_fn(frameno, keyframes.len());
}
}
DetectionResults {
scene_changes: keyframes.into_iter().map(|val| val as usize).collect(),
frame_count: frameno,
speed: frameno as f64 / start_time.elapsed().as_secs() as f64,
}
}
#[derive(Clone, Copy, Debug, PartialOrd, PartialEq)]
pub enum SceneDetectionSpeed {
Fast,
Medium,
Slow,
}