1use crate::error::{IrisError, Result};
2use crate::video::frame::Frame;
3use burn::tensor::backend::Backend;
4use std::path::{Path, PathBuf};
5use std::time::Duration;
6
7use super::metadata::ContainerFormat;
8
9pub struct FrameIterator<B: Backend> {
11 frames: Vec<Frame<B>>,
12 current: usize,
13 loop_playback: bool,
14}
15
16impl<B: Backend> FrameIterator<B> {
17 #[must_use]
19 pub fn new(frames: Vec<Frame<B>>) -> Self {
20 Self {
21 frames,
22 current: 0,
23 loop_playback: false,
24 }
25 }
26
27 #[must_use]
29 pub fn with_loop(mut self) -> Self {
30 self.loop_playback = true;
31 self
32 }
33
34 #[must_use]
36 pub fn total_frames(&self) -> usize {
37 self.frames.len()
38 }
39
40 #[must_use]
42 pub fn current_index(&self) -> usize {
43 self.current
44 }
45
46 pub fn seek(&mut self, index: usize) -> Result<()> {
48 if index >= self.frames.len() {
49 return Err(IrisError::InvalidParameter(format!(
50 "Frame index {} out of range [0, {})",
51 index,
52 self.frames.len()
53 )));
54 }
55 self.current = index;
56 Ok(())
57 }
58
59 pub fn seek_to_time(&mut self, time: Duration, fps: f64) -> Result<()> {
61 let frame_index = (time.as_secs_f64() * fps).round() as usize;
62 self.seek(frame_index)
63 }
64
65 #[must_use]
67 pub fn remaining(&self) -> usize {
68 self.frames.len().saturating_sub(self.current)
69 }
70
71 #[must_use]
73 pub fn total_duration(&self) -> Duration {
74 self.frames
75 .iter()
76 .map(|f| {
77 if f.duration.is_zero() {
78 Duration::from_secs_f64(1.0 / 30.0)
79 } else {
80 f.duration
81 }
82 })
83 .sum()
84 }
85}
86
87impl<B: Backend> Iterator for FrameIterator<B> {
88 type Item = Frame<B>;
89
90 fn next(&mut self) -> Option<Self::Item> {
91 if self.current >= self.frames.len() {
92 if self.loop_playback && !self.frames.is_empty() {
93 self.current = 0;
94 } else {
95 return None;
96 }
97 }
98 let frame = self.frames[self.current].clone();
99 self.current += 1;
100 Some(frame)
101 }
102
103 fn size_hint(&self) -> (usize, Option<usize>) {
104 let remaining = self.remaining();
105 (remaining, Some(remaining))
106 }
107}
108
109impl<B: Backend> ExactSizeIterator for FrameIterator<B> {}
110
111pub fn load_animated_image<B: Backend>(path: &Path, device: &B::Device) -> Result<Vec<Frame<B>>> {
113 use image::AnimationDecoder;
114 use image::codecs::gif::GifDecoder;
115
116 let path_str = path.to_string_lossy().to_string();
117 let format = ContainerFormat::from_path(&path_str);
118
119 let file = std::fs::File::open(path)
120 .map_err(|e| IrisError::Video(format!("Failed to open video file: {e}")))?;
121
122 let reader = std::io::BufReader::new(file);
123
124 match format {
125 ContainerFormat::Gif => {
126 let decoder = GifDecoder::new(reader)
127 .map_err(|e| IrisError::Video(format!("Failed to decode GIF: {e}")))?;
128
129 let frames_iter = decoder.into_frames();
130 let mut frames = Vec::new();
131
132 for (i, result) in frames_iter.enumerate() {
133 let frame = result
134 .map_err(|e| IrisError::Video(format!("Failed to read GIF frame {i}: {e}")))?;
135
136 let delay = frame.delay();
137 let (numer, denom) = delay.numer_denom_ms();
138 let duration_ms = if denom > 0 {
139 numer as u64 * 1000 / denom as u64
140 } else {
141 33
142 };
143 let duration = Duration::from_millis(duration_ms);
144
145 let img = frame.buffer();
146 let (w, h) = img.dimensions();
147 let raw: Vec<f32> = img
148 .pixels()
149 .flat_map(|p| {
150 let [r, g, b, _a] = p.0;
151 [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0]
152 })
153 .collect();
154
155 let tensor = burn::tensor::Tensor::<B, 3>::from_data(
156 burn::tensor::TensorData::new(raw, [3, h as usize, w as usize]),
157 device,
158 );
159 let image = crate::image::Image::new(tensor);
160
161 let pts = Duration::from_secs_f64(i as f64 * duration.as_secs_f64());
162 let frame = Frame::new(image, pts, i)
163 .with_duration(duration)
164 .with_keyframe(i == 0 || i % 30 == 0);
165
166 frames.push(frame);
167 }
168
169 Ok(frames)
170 }
171 ContainerFormat::Png => {
172 use image::codecs::png::PngDecoder;
173
174 let file2 = std::fs::File::open(path)
175 .map_err(|e| IrisError::Video(format!("Failed to open PNG file: {e}")))?;
176 let reader2 = std::io::BufReader::new(file2);
177
178 let decoder = PngDecoder::new(reader2)
179 .map_err(|e| IrisError::Video(format!("Failed to decode PNG: {e}")))?;
180
181 let apng = decoder
182 .apng()
183 .map_err(|e| IrisError::Video(format!("Failed to parse APNG: {e}")))?;
184
185 let frames_iter = apng.into_frames();
186 let mut frames = Vec::new();
187
188 for (i, result) in frames_iter.enumerate() {
189 let frame = result
190 .map_err(|e| IrisError::Video(format!("Failed to read APNG frame {i}: {e}")))?;
191
192 let delay = frame.delay();
193 let (numer, denom) = delay.numer_denom_ms();
194 let duration_ms = if denom > 0 {
195 numer as u64 * 1000 / denom as u64
196 } else {
197 33
198 };
199 let duration = Duration::from_millis(duration_ms);
200
201 let img = frame.buffer();
202 let (w, h) = img.dimensions();
203 let raw: Vec<f32> = img
204 .pixels()
205 .flat_map(|p| {
206 let [r, g, b, _a] = p.0;
207 [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0]
208 })
209 .collect();
210
211 let tensor = burn::tensor::Tensor::<B, 3>::from_data(
212 burn::tensor::TensorData::new(raw, [3, h as usize, w as usize]),
213 device,
214 );
215 let image = crate::image::Image::new(tensor);
216
217 let pts = Duration::from_secs_f64(i as f64 * duration.as_secs_f64());
218 let frame = Frame::new(image, pts, i)
219 .with_duration(duration)
220 .with_keyframe(i == 0 || i % 30 == 0);
221
222 frames.push(frame);
223 }
224
225 Ok(frames)
226 }
227 _ => {
228 let img = image::open(path)
230 .map_err(|e| IrisError::Video(format!("Failed to open image: {e}")))?;
231
232 let rgb = img.to_rgb8();
233 let (w, h) = rgb.dimensions();
234 let raw: Vec<f32> = rgb
235 .pixels()
236 .flat_map(|p| {
237 let [r, g, b] = p.0;
238 [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0]
239 })
240 .collect();
241
242 let tensor = burn::tensor::Tensor::<B, 3>::from_data(
243 burn::tensor::TensorData::new(raw, [3, h as usize, w as usize]),
244 device,
245 );
246 let image = crate::image::Image::new(tensor);
247 Ok(vec![Frame::new(image, Duration::ZERO, 0)])
248 }
249 }
250}
251
252pub fn load_image_sequence<B: Backend>(
254 dir: &Path,
255 pattern: &str,
256 device: &B::Device,
257 fps: f64,
258) -> Result<Vec<Frame<B>>> {
259 let mut entries: Vec<PathBuf> = std::fs::read_dir(dir)
260 .map_err(|e| IrisError::Video(format!("Failed to read directory: {e}")))?
261 .filter_map(|e| e.ok())
262 .map(|e| e.path())
263 .filter(|p| {
264 p.file_name()
265 .and_then(|n| n.to_str())
266 .map(|n| n.contains(pattern))
267 .unwrap_or(false)
268 })
269 .filter(|p| {
270 p.extension().is_some_and(|ext| {
271 matches!(
272 ext.to_str(),
273 Some("png" | "jpg" | "jpeg" | "bmp" | "tiff" | "webp")
274 )
275 })
276 })
277 .collect();
278
279 entries.sort();
280
281 let mut frames = Vec::with_capacity(entries.len());
282 for (i, entry) in entries.iter().enumerate() {
283 let img = image::open(entry)
284 .map_err(|e| IrisError::Video(format!("Failed to open frame {}: {e}", i + 1)))?;
285
286 let rgb = img.to_rgb8();
287 let (w, h) = rgb.dimensions();
288 let raw: Vec<f32> = rgb
289 .pixels()
290 .flat_map(|p| {
291 let [r, g, b] = p.0;
292 [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0]
293 })
294 .collect();
295
296 let tensor = burn::tensor::Tensor::<B, 3>::from_data(
297 burn::tensor::TensorData::new(raw, [3, h as usize, w as usize]),
298 device,
299 );
300 let image = crate::image::Image::new(tensor);
301 let pts = Duration::from_secs_f64(i as f64 / fps);
302 let frame = Frame::new(image, pts, i)
303 .with_duration(Duration::from_secs_f64(1.0 / fps))
304 .with_keyframe(i == 0 || i % 30 == 0);
305
306 frames.push(frame);
307 }
308
309 Ok(frames)
310}
311
312pub trait FrameExt {
314 fn with_keyframe(self, is_keyframe: bool) -> Self;
316}
317
318impl<B: Backend> FrameExt for Frame<B> {
319 fn with_keyframe(mut self, is_keyframe: bool) -> Self {
320 self.is_keyframe = is_keyframe;
321 self
322 }
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use crate::test_helpers::{TestBackend, test_device};
329 use burn::tensor::TensorData;
330
331 fn make_frame(index: usize) -> Frame<TestBackend> {
332 let device = test_device();
333 let data = TensorData::new(vec![0.5f32; 3 * 32 * 32], [3, 32, 32]);
334 let tensor = burn::tensor::Tensor::<TestBackend, 3>::from_data(data, &device);
335 let img = crate::image::Image::new(tensor);
336 Frame::new(img, Duration::from_millis(index as u64 * 33), index)
337 .with_duration(Duration::from_millis(33))
338 }
339
340 #[test]
341 fn test_frame_iterator_sequential() {
342 let frames: Vec<_> = (0..5).map(make_frame).collect();
343 let mut iter = FrameIterator::new(frames);
344 assert_eq!(iter.total_frames(), 5);
345 assert_eq!(iter.remaining(), 5);
346
347 let first = iter.next().unwrap();
348 assert_eq!(first.index, 0);
349 assert_eq!(iter.current_index(), 1);
350 assert_eq!(iter.remaining(), 4);
351 }
352
353 #[test]
354 fn test_frame_iterator_exact_size() {
355 let frames: Vec<_> = (0..10).map(make_frame).collect();
356 let iter = FrameIterator::new(frames);
357 assert_eq!(iter.len(), 10);
358 }
359
360 #[test]
361 fn test_frame_iterator_seek() {
362 let frames: Vec<_> = (0..5).map(make_frame).collect();
363 let mut iter = FrameIterator::new(frames);
364 iter.seek(3).unwrap();
365 assert_eq!(iter.current_index(), 3);
366 let frame = iter.next().unwrap();
367 assert_eq!(frame.index, 3);
368 }
369
370 #[test]
371 fn test_frame_iterator_seek_out_of_bounds() {
372 let frames: Vec<_> = (0..5).map(make_frame).collect();
373 let mut iter = FrameIterator::new(frames);
374 assert!(iter.seek(10).is_err());
375 }
376
377 #[test]
378 fn test_frame_iterator_loop() {
379 let frames: Vec<_> = (0..3).map(make_frame).collect();
380 let mut iter = FrameIterator::new(frames).with_loop();
381 for _ in 0..7 {
382 assert!(iter.next().is_some());
383 }
384 }
385
386 #[test]
387 fn test_frame_iterator_total_duration() {
388 let frames: Vec<_> = (0..30).map(make_frame).collect();
389 let iter = FrameIterator::new(frames);
390 let dur = iter.total_duration();
391 assert!((dur.as_secs_f64() - 1.0).abs() < 0.02);
392 }
393
394 #[test]
395 fn test_frame_iterator_seek_to_time() {
396 let frames: Vec<_> = (0..60).map(make_frame).collect();
397 let mut iter = FrameIterator::new(frames);
398 iter.seek_to_time(Duration::from_secs(1), 30.0).unwrap();
399 assert_eq!(iter.current_index(), 30);
400 }
401}