mod analyze;
#[macro_use]
mod cpu;
mod data;
mod math;
use std::{
collections::{BTreeMap, BTreeSet},
sync::{
Arc,
mpsc::{channel, sync_channel},
},
thread,
time::Instant,
};
pub use av_decoders::{self, Decoder};
pub use num_rational::Rational32;
use v_frame::pixel::Pixel;
pub use crate::analyze::{SceneChangeDetector, ScenecutResult};
const FRAME_PREFETCH_DEPTH: usize = 8;
#[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, serde::Deserialize))]
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.unwrap_or(0),
opts.max_scenecut_distance.unwrap_or(u32::MAX as usize),
))
}
#[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 detector = new_detector::<T>(dec, opts)?;
let (frame_tx, frame_rx) = sync_channel(FRAME_PREFETCH_DEPTH);
let (progress_tx, progress_rx) = if progress_callback.is_some() {
let (tx, rx) = channel();
(Some(tx), Some(rx))
} else {
(None, None)
};
let detection_handle = {
let progress_tx = progress_tx;
thread::spawn(move || -> anyhow::Result<DetectionResults> {
let mut detector = detector;
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 = 0usize;
loop {
let mut next_input_frameno =
frame_queue.keys().last().copied().map_or(0, |key| key + 1);
let max_needed =
(frameno + opts.lookahead_distance + 1).min(frame_limit.unwrap_or(usize::MAX));
while next_input_frameno < max_needed {
match frame_rx.recv() {
Ok(frame) => {
frame_queue.insert(next_input_frameno, frame);
next_input_frameno += 1;
}
Err(_) => 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(ref progress_tx) = progress_tx {
let _ = progress_tx.send((frameno, keyframes.len()));
}
if let Some(frame_limit) = frame_limit
&& 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,
})
})
};
let mut produced = 0usize;
while frame_limit.map_or_else(|| true, |limit| produced < limit) {
match dec.read_video_frame() {
Ok(frame) => {
produced += 1;
if frame_tx.send(Arc::new(frame)).is_err() {
break;
}
}
Err(av_decoders::DecoderError::EndOfFile) => break,
Err(e) => {
return Err(e.into());
}
}
if let (Some(progress_rx), Some(progress_fn)) = (&progress_rx, progress_callback) {
while let Ok((frames, keyframe_count)) = progress_rx.try_recv() {
progress_fn(frames, keyframe_count);
}
}
}
drop(frame_tx);
if let (Some(progress_rx), Some(progress_fn)) = (&progress_rx, progress_callback) {
while let Ok((frames, keyframe_count)) = progress_rx.try_recv() {
progress_fn(frames, keyframe_count);
}
}
let results = detection_handle
.join()
.map_err(|_| anyhow::anyhow!("scene detection thread panicked"))??;
if let (Some(progress_rx), Some(progress_fn)) = (&progress_rx, progress_callback) {
while let Ok((frames, keyframe_count)) = progress_rx.try_recv() {
progress_fn(frames, keyframe_count);
}
}
Ok(results)
}
#[derive(Clone, Copy, Debug, PartialOrd, PartialEq, Eq)]
pub enum SceneDetectionSpeed {
Fast,
Standard,
None,
}