use crate::common::Confidence;
use crate::error::{SceneError, SceneResult};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum EventType {
SceneChange,
CameraMotion,
Flash,
Applause,
Explosion,
Unknown,
}
impl EventType {
#[must_use]
pub const fn name(&self) -> &'static str {
match self {
Self::SceneChange => "Scene Change",
Self::CameraMotion => "Camera Motion",
Self::Flash => "Flash",
Self::Applause => "Applause",
Self::Explosion => "Explosion",
Self::Unknown => "Unknown",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VideoEvent {
pub event_type: EventType,
pub frame_number: usize,
pub confidence: Confidence,
pub duration: usize,
}
pub struct EventDetector {
min_frames: usize,
}
impl EventDetector {
#[must_use]
pub fn new() -> Self {
Self { min_frames: 3 }
}
pub fn detect(
&self,
frames: &[&[u8]],
width: usize,
height: usize,
) -> SceneResult<Vec<VideoEvent>> {
if frames.len() < self.min_frames {
return Err(SceneError::InsufficientData(format!(
"Need at least {} frames",
self.min_frames
)));
}
let mut events = Vec::new();
for i in 1..frames.len() {
let diff = self.frame_difference(frames[i - 1], frames[i], width, height);
if diff > 0.5 {
events.push(VideoEvent {
event_type: EventType::SceneChange,
frame_number: i,
confidence: Confidence::new(diff.min(1.0)),
duration: 1,
});
}
}
let camera_events = self.detect_camera_motion(frames, width, height)?;
events.extend(camera_events);
let flash_events = self.detect_flashes(frames, width, height)?;
events.extend(flash_events);
Ok(events)
}
fn frame_difference(&self, frame1: &[u8], frame2: &[u8], _width: usize, _height: usize) -> f32 {
let mut diff_sum = 0u64;
let mut count = 0;
for i in (0..frame1.len().min(frame2.len())).step_by(3) {
for c in 0..3 {
diff_sum += (frame1[i + c] as i32 - frame2[i + c] as i32).unsigned_abs() as u64;
}
count += 3;
}
if count > 0 {
(diff_sum as f32 / count as f32 / 255.0).clamp(0.0, 1.0)
} else {
0.0
}
}
fn detect_camera_motion(
&self,
frames: &[&[u8]],
width: usize,
height: usize,
) -> SceneResult<Vec<VideoEvent>> {
let mut events = Vec::new();
for i in 1..frames.len() {
let motion = self.estimate_global_motion(frames[i - 1], frames[i], width, height);
if motion > 0.3 {
events.push(VideoEvent {
event_type: EventType::CameraMotion,
frame_number: i,
confidence: Confidence::new(motion.min(1.0)),
duration: 1,
});
}
}
Ok(events)
}
fn estimate_global_motion(
&self,
frame1: &[u8],
frame2: &[u8],
width: usize,
height: usize,
) -> f32 {
let block_size = 32;
let mut motion_sum = 0.0;
let mut count = 0;
for y in (0..height - block_size).step_by(block_size) {
for x in (0..width - block_size).step_by(block_size) {
let mut min_diff = f32::MAX;
for dy in -8..=8 {
for dx in -8..=8 {
let nx = x as i32 + dx;
let ny = y as i32 + dy;
if nx >= 0
&& ny >= 0
&& (nx as usize + block_size) < width
&& (ny as usize + block_size) < height
{
let diff = self.block_diff(
frame1,
frame2,
width,
x,
y,
nx as usize,
ny as usize,
block_size,
);
min_diff = min_diff.min(diff);
}
}
}
motion_sum += min_diff;
count += 1;
}
}
if count > 0 {
(motion_sum / count as f32 / 255.0).clamp(0.0, 1.0)
} else {
0.0
}
}
fn block_diff(
&self,
frame1: &[u8],
frame2: &[u8],
width: usize,
x1: usize,
y1: usize,
x2: usize,
y2: usize,
size: usize,
) -> f32 {
let mut diff = 0.0;
for dy in 0..size {
for dx in 0..size {
let idx1 = ((y1 + dy) * width + (x1 + dx)) * 3;
let idx2 = ((y2 + dy) * width + (x2 + dx)) * 3;
if idx1 + 2 < frame1.len() && idx2 + 2 < frame2.len() {
for c in 0..3 {
diff += (frame1[idx1 + c] as i32 - frame2[idx2 + c] as i32).unsigned_abs()
as f32;
}
}
}
}
diff / (size * size * 3) as f32
}
fn detect_flashes(
&self,
frames: &[&[u8]],
_width: usize,
_height: usize,
) -> SceneResult<Vec<VideoEvent>> {
let mut events = Vec::new();
for i in 1..frames.len() {
let brightness1 = self.avg_brightness(frames[i - 1]);
let brightness2 = self.avg_brightness(frames[i]);
let change = (brightness2 - brightness1).abs() / 255.0;
if change > 0.7 {
events.push(VideoEvent {
event_type: EventType::Flash,
frame_number: i,
confidence: Confidence::new(change.min(1.0)),
duration: 1,
});
}
}
Ok(events)
}
fn avg_brightness(&self, frame: &[u8]) -> f32 {
let mut sum = 0.0;
for i in (0..frame.len()).step_by(3) {
sum += (frame[i] as f32 + frame[i + 1] as f32 + frame[i + 2] as f32) / 3.0;
}
sum / (frame.len() / 3) as f32
}
}
impl Default for EventDetector {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_detector() {
let detector = EventDetector::new();
let width = 64;
let height = 48;
let frame = vec![128u8; width * height * 3];
let frames: Vec<&[u8]> = (0..10).map(|_| &frame[..]).collect();
let result = detector.detect(&frames, width, height);
assert!(result.is_ok());
}
#[test]
fn test_event_type_name() {
assert_eq!(EventType::SceneChange.name(), "Scene Change");
assert_eq!(EventType::Flash.name(), "Flash");
}
}