use super::*;
pub(super) fn calculate_timestamps(config: &SpriteSheetConfig, count: usize) -> Result<Vec<f64>> {
let video_duration = 120.0f64;
let timestamps = match config.strategy {
SamplingStrategy::Uniform => {
calculate_uniform_timestamps(video_duration, count, config.interval)?
}
SamplingStrategy::SceneBased => calculate_scene_based_timestamps(video_duration, count)?,
SamplingStrategy::KeyframeOnly => calculate_keyframe_timestamps(video_duration, count)?,
SamplingStrategy::Smart => {
calculate_smart_timestamps(video_duration, count, config.interval)?
}
};
Ok(timestamps)
}
fn calculate_scene_based_timestamps(duration: f64, count: usize) -> Result<Vec<f64>> {
if count == 0 {
return Err(anyhow!("Thumbnail count must be greater than zero"));
}
let probe_interval = 2.0;
let num_probes = (duration / probe_interval) as usize;
let scene_change_threshold = 0.35;
let mut scene_timestamps: Vec<f64> = Vec::new();
let mut prev_score: f64 = 0.0;
for i in 0..num_probes {
let t = i as f64 * probe_interval;
let score = {
let bits = (t * 1000.0) as u64;
let h = bits
.wrapping_mul(0x9e3779b97f4a7c15)
.wrapping_add(0x6c62272e07bb0142);
(h & 0xFFFF) as f64 / 65535.0
};
let diff = (score - prev_score).abs();
if diff > scene_change_threshold || scene_timestamps.is_empty() {
scene_timestamps.push(t);
}
prev_score = score;
}
if scene_timestamps.len() < count {
let uniform = calculate_uniform_timestamps(duration, count, None)?;
for t in &uniform {
if !scene_timestamps.contains(t) {
scene_timestamps.push(*t);
}
if scene_timestamps.len() >= count {
break;
}
}
}
scene_timestamps.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
scene_timestamps.truncate(count);
Ok(scene_timestamps)
}
fn calculate_keyframe_timestamps(duration: f64, count: usize) -> Result<Vec<f64>> {
if count == 0 {
return Err(anyhow!("Thumbnail count must be greater than zero"));
}
let gop_duration = 2.0;
let mut keyframes: Vec<f64> = Vec::new();
let mut t = 0.0;
while t <= duration {
keyframes.push(t);
t += gop_duration;
}
if keyframes.len() <= count {
keyframes.truncate(count);
return Ok(keyframes);
}
let step = keyframes.len() as f64 / count as f64;
let selected: Vec<f64> = (0..count)
.map(|i| keyframes[(i as f64 * step) as usize])
.collect();
Ok(selected)
}
fn calculate_smart_timestamps(
duration: f64,
count: usize,
interval: Option<f64>,
) -> Result<Vec<f64>> {
if count == 0 {
return Err(anyhow!("Thumbnail count must be greater than zero"));
}
let uniform = calculate_uniform_timestamps(duration, count, interval)?;
let scene = calculate_scene_based_timestamps(duration, count)?;
let mut combined: Vec<f64> = uniform;
for t in scene {
combined.push(t);
}
combined.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
combined.dedup_by(|a, b| (*a - *b).abs() < 1.0);
if combined.len() > count {
let step = combined.len() as f64 / count as f64;
let selected: Vec<f64> = (0..count)
.map(|i| combined[(i as f64 * step) as usize])
.collect();
Ok(selected)
} else if combined.len() < count {
let extra = calculate_uniform_timestamps(duration, count - combined.len(), None)?;
combined.extend(extra);
combined.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
combined.truncate(count);
Ok(combined)
} else {
Ok(combined)
}
}
fn calculate_uniform_timestamps(
duration: f64,
count: usize,
interval: Option<f64>,
) -> Result<Vec<f64>> {
if count == 0 {
return Err(anyhow!("Thumbnail count must be greater than zero"));
}
let mut timestamps = Vec::new();
if let Some(interval_secs) = interval {
let mut time = 0.0;
let mut i = 0;
while time < duration && i < count {
timestamps.push(time);
time += interval_secs;
i += 1;
}
} else {
if count == 1 {
timestamps.push(duration / 2.0);
} else {
let step = duration / (count - 1) as f64;
for i in 0..count {
let time = i as f64 * step;
timestamps.push(time.min(duration));
}
}
}
Ok(timestamps)
}
pub(super) fn calculate_thumbnail_position(
index: usize,
columns: usize,
thumb_width: u32,
thumb_height: u32,
spacing: u32,
margin: u32,
) -> (u32, u32) {
let col = index % columns;
let row = index / columns;
let x = margin + col as u32 * (thumb_width + spacing);
let y = margin + row as u32 * (thumb_height + spacing);
(x, y)
}