use ::rayon::iter::{IntoParallelIterator, ParallelIterator};
use image::DynamicImage;
use crate::configuration::ExtractOptions;
use crate::error::UnbundleError;
use crate::metadata::VideoMetadata;
use crate::unbundle::MediaFile;
use crate::video::FrameRange;
pub(crate) fn parallel_extract_frames(
source: &str,
frame_numbers: &[u64],
_video_metadata: &VideoMetadata,
config: &ExtractOptions,
) -> Result<Vec<(u64, DynamicImage)>, UnbundleError> {
if frame_numbers.is_empty() {
return Ok(Vec::new());
}
let chunks = split_into_runs(frame_numbers, 30);
let source = source.to_string();
let config = config.clone();
let results: Result<Vec<Vec<(u64, DynamicImage)>>, UnbundleError> = chunks
.into_par_iter()
.map(|chunk| {
if config.is_cancelled() {
return Err(UnbundleError::Cancelled);
}
decode_chunk(&source, &chunk, &config)
})
.collect();
let mut all_frames: Vec<(u64, DynamicImage)> = results?.into_iter().flatten().collect();
all_frames.sort_by_key(|(number, _)| *number);
Ok(all_frames)
}
fn split_into_runs(frame_numbers: &[u64], gap_threshold: u64) -> Vec<Vec<u64>> {
if frame_numbers.is_empty() {
return Vec::new();
}
let mut runs: Vec<Vec<u64>> = Vec::new();
let mut current_run: Vec<u64> = vec![frame_numbers[0]];
for &number in &frame_numbers[1..] {
if number - *current_run.last().unwrap() <= gap_threshold {
current_run.push(number);
} else {
runs.push(std::mem::take(&mut current_run));
current_run.push(number);
}
}
if !current_run.is_empty() {
runs.push(current_run);
}
runs
}
fn decode_chunk(
source: &str,
frame_numbers: &[u64],
config: &ExtractOptions,
) -> Result<Vec<(u64, DynamicImage)>, UnbundleError> {
let mut unbundler = MediaFile::open_source(source)?;
let mut frames = Vec::with_capacity(frame_numbers.len());
let range = FrameRange::Specific(frame_numbers.to_vec());
unbundler
.video()
.for_each_frame_with_options(range, config, |frame_number, image| {
frames.push((frame_number, image));
Ok(())
})?;
Ok(frames)
}