#![forbid(unsafe_code)]
use super::generator::CuePoint;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PlacementStrategy {
FixedInterval,
Adaptive,
Keyframe,
SceneChange,
}
#[derive(Debug, Clone)]
pub struct OptimizerConfig {
pub strategy: PlacementStrategy,
pub target_interval_secs: f64,
pub min_interval_secs: f64,
pub max_interval_secs: f64,
pub target_density: f64,
}
impl Default for OptimizerConfig {
fn default() -> Self {
Self {
strategy: PlacementStrategy::Adaptive,
target_interval_secs: 2.0,
min_interval_secs: 0.5,
max_interval_secs: 10.0,
target_density: 0.5,
}
}
}
impl OptimizerConfig {
#[must_use]
pub const fn new() -> Self {
Self {
strategy: PlacementStrategy::Adaptive,
target_interval_secs: 2.0,
min_interval_secs: 0.5,
max_interval_secs: 10.0,
target_density: 0.5,
}
}
#[must_use]
pub const fn with_strategy(mut self, strategy: PlacementStrategy) -> Self {
self.strategy = strategy;
self
}
#[must_use]
pub const fn with_target_interval(mut self, interval_secs: f64) -> Self {
self.target_interval_secs = interval_secs;
self
}
}
pub struct CueOptimizer {
config: OptimizerConfig,
}
impl CueOptimizer {
#[must_use]
pub const fn new(config: OptimizerConfig) -> Self {
Self { config }
}
#[must_use]
pub fn optimize(&self, cue_points: &[CuePoint]) -> Vec<CuePoint> {
match self.config.strategy {
PlacementStrategy::FixedInterval => self.optimize_fixed_interval(cue_points),
PlacementStrategy::Adaptive => self.optimize_adaptive(cue_points),
PlacementStrategy::Keyframe => Self::optimize_keyframe(cue_points),
PlacementStrategy::SceneChange => self.optimize_scene_change(cue_points),
}
}
fn optimize_fixed_interval(&self, cue_points: &[CuePoint]) -> Vec<CuePoint> {
let mut optimized = Vec::new();
let mut last_time = None;
for cue in cue_points {
if let Some(last) = last_time {
#[allow(clippy::cast_precision_loss)]
let interval = (cue.timestamp - last) as f64;
if interval < self.config.min_interval_secs * 1000.0 {
continue;
}
}
optimized.push(*cue);
last_time = Some(cue.timestamp);
}
optimized
}
fn optimize_adaptive(&self, cue_points: &[CuePoint]) -> Vec<CuePoint> {
let mut optimized = Vec::new();
if cue_points.is_empty() {
return optimized;
}
optimized.push(cue_points[0]);
for window in cue_points.windows(2) {
let prev = &window[0];
let curr = &window[1];
#[allow(clippy::cast_precision_loss)]
let interval = (curr.timestamp - prev.timestamp) as f64;
if interval >= self.config.min_interval_secs * 1000.0 {
optimized.push(*curr);
}
}
optimized
}
fn optimize_keyframe(cue_points: &[CuePoint]) -> Vec<CuePoint> {
cue_points.to_vec()
}
fn optimize_scene_change(&self, cue_points: &[CuePoint]) -> Vec<CuePoint> {
self.optimize_adaptive(cue_points)
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn calculate_stats(&self, cue_points: &[CuePoint]) -> CueStats {
if cue_points.is_empty() {
return CueStats::default();
}
let mut intervals = Vec::new();
for window in cue_points.windows(2) {
let interval = (window[1].timestamp - window[0].timestamp) as f64;
intervals.push(interval);
}
let total_interval: f64 = intervals.iter().sum();
let avg_interval = if intervals.is_empty() {
0.0
} else {
total_interval / intervals.len() as f64
};
let min_interval = intervals.iter().copied().fold(f64::INFINITY, f64::min);
let max_interval = intervals.iter().copied().fold(f64::NEG_INFINITY, f64::max);
CueStats {
count: cue_points.len(),
avg_interval,
min_interval: if min_interval.is_finite() {
min_interval
} else {
0.0
},
max_interval: if max_interval.is_finite() {
max_interval
} else {
0.0
},
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CueStats {
pub count: usize,
pub avg_interval: f64,
pub min_interval: f64,
pub max_interval: f64,
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_cues() -> Vec<CuePoint> {
vec![
CuePoint::new(0, 0, 1),
CuePoint::new(500, 5000, 1),
CuePoint::new(1000, 10000, 1),
CuePoint::new(1200, 12000, 1),
CuePoint::new(2000, 20000, 1),
]
}
#[test]
fn test_optimizer_config() {
let config = OptimizerConfig::new()
.with_strategy(PlacementStrategy::Keyframe)
.with_target_interval(3.0);
assert_eq!(config.strategy, PlacementStrategy::Keyframe);
assert_eq!(config.target_interval_secs, 3.0);
}
#[test]
fn test_optimize_fixed_interval() {
let config = OptimizerConfig::new()
.with_strategy(PlacementStrategy::FixedInterval)
.with_target_interval(1.0);
let optimizer = CueOptimizer::new(config);
let cues = create_test_cues();
let optimized = optimizer.optimize(&cues);
assert!(optimized.len() <= cues.len());
}
#[test]
fn test_calculate_stats() {
let config = OptimizerConfig::new();
let optimizer = CueOptimizer::new(config);
let cues = create_test_cues();
let stats = optimizer.calculate_stats(&cues);
assert_eq!(stats.count, 5);
assert!(stats.avg_interval > 0.0);
assert!(stats.min_interval > 0.0);
assert!(stats.max_interval >= stats.min_interval);
}
}