1use crate::types::{
4 AnalyzedFrame, EncodableFrame, FrameOp, FrameOps, Segment, SegmentOp, SegmentOps,
5};
6
7#[cfg(feature = "parallel")]
8use rayon::prelude::*;
9
10pub fn apply_segment_operations<H: Sync + Send>(
25 frames: &[AnalyzedFrame<H>],
26 segments: &[Segment],
27 ops: &SegmentOps,
28) -> Vec<EncodableFrame> {
29 #[cfg(feature = "parallel")]
30 {
31 segments
32 .par_iter()
33 .map(|segment| {
34 let op = ops.get(&segment.id).unwrap_or(&SegmentOp::Keep);
35 let segment_frames = &frames[segment.frame_range.clone()];
36 let mut segment_output = Vec::new();
37
38 match op {
39 SegmentOp::Keep => {
40 for frame in segment_frames {
41 segment_output.push(EncodableFrame::from_decoded(&frame.frame));
42 }
43 }
44 SegmentOp::Remove => {}
45 SegmentOp::Collapse { delay_cs } => {
46 if let Some(first) = segment_frames.first() {
47 segment_output
48 .push(EncodableFrame::new(first.frame.image.clone(), *delay_cs));
49 }
50 }
51 SegmentOp::SetDuration { total_cs } => {
52 let frame_count = segment_frames.len();
53 if frame_count > 0 {
54 let per_frame_delay = *total_cs / frame_count as u16;
55 let remainder = *total_cs % frame_count as u16;
56
57 for (i, frame) in segment_frames.iter().enumerate() {
58 let delay = if (i as u16) < remainder {
59 per_frame_delay + 1
60 } else {
61 per_frame_delay
62 };
63 segment_output
64 .push(EncodableFrame::new(frame.frame.image.clone(), delay));
65 }
66 }
67 }
68 SegmentOp::Scale { factor } => {
69 for frame in segment_frames {
70 let original_delay = frame.frame.delay_centiseconds as f64;
71 let new_delay = (original_delay * factor).round() as u16;
72 segment_output.push(EncodableFrame::new(
73 frame.frame.image.clone(),
74 new_delay.max(1),
75 ));
76 }
77 }
78 SegmentOp::SetFrameDelay { delay_cs } => {
79 for frame in segment_frames {
80 segment_output
81 .push(EncodableFrame::new(frame.frame.image.clone(), *delay_cs));
82 }
83 }
84 }
85 segment_output
86 })
87 .flatten()
88 .collect()
89 }
90
91 #[cfg(not(feature = "parallel"))]
92 {
93 let mut output = Vec::new();
94
95 for segment in segments {
96 let op = ops.get(&segment.id).unwrap_or(&SegmentOp::Keep);
97 let segment_frames = &frames[segment.frame_range.clone()];
98
99 match op {
100 SegmentOp::Keep => {
101 for frame in segment_frames {
102 output.push(EncodableFrame::from_decoded(&frame.frame));
103 }
104 }
105
106 SegmentOp::Remove => {}
107
108 SegmentOp::Collapse { delay_cs } => {
109 if let Some(first) = segment_frames.first() {
110 output.push(EncodableFrame::new(first.frame.image.clone(), *delay_cs));
111 }
112 }
113
114 SegmentOp::SetDuration { total_cs } => {
115 let frame_count = segment_frames.len();
117 if frame_count > 0 {
118 let per_frame_delay = *total_cs / frame_count as u16;
119 let remainder = *total_cs % frame_count as u16;
120
121 for (i, frame) in segment_frames.iter().enumerate() {
122 let delay = if (i as u16) < remainder {
124 per_frame_delay + 1
125 } else {
126 per_frame_delay
127 };
128 output.push(EncodableFrame::new(frame.frame.image.clone(), delay));
129 }
130 }
131 }
132
133 SegmentOp::Scale { factor } => {
134 for frame in segment_frames {
136 let original_delay = frame.frame.delay_centiseconds as f64;
137 let new_delay = (original_delay * factor).round() as u16;
138 let new_delay = new_delay.max(1);
140 output.push(EncodableFrame::new(frame.frame.image.clone(), new_delay));
141 }
142 }
143
144 SegmentOp::SetFrameDelay { delay_cs } => {
145 for frame in segment_frames {
147 output.push(EncodableFrame::new(frame.frame.image.clone(), *delay_cs));
148 }
149 }
150 }
151 }
152
153 output
154 }
155}
156
157pub fn dry_run_segment_operations<H: Sync + Send>(
161 frames: &[AnalyzedFrame<H>],
162 segments: &[Segment],
163 ops: &SegmentOps,
164) -> (usize, u64) {
165 let mut total_frames = 0;
166 let mut total_duration_cs: u64 = 0;
167
168 for segment in segments {
169 let op = ops.get(&segment.id).unwrap_or(&SegmentOp::Keep);
170 let segment_frames = &frames[segment.frame_range.clone()];
171
172 match op {
173 SegmentOp::Keep => {
174 total_frames += segment_frames.len();
175 total_duration_cs += segment.total_duration_cs as u64;
176 }
177 SegmentOp::Remove => {}
178 SegmentOp::Collapse { delay_cs } => {
179 if !segment_frames.is_empty() {
180 total_frames += 1;
181 total_duration_cs += *delay_cs as u64;
182 }
183 }
184 SegmentOp::SetDuration { total_cs } => {
185 if !segment_frames.is_empty() {
186 total_frames += segment_frames.len();
187 total_duration_cs += *total_cs as u64;
188 }
189 }
190 SegmentOp::Scale { factor } => {
191 for frame in segment_frames {
192 let original_delay = frame.frame.delay_centiseconds as f64;
193 let new_delay = (original_delay * factor).round() as u16;
194 total_frames += 1;
195 total_duration_cs += new_delay.max(1) as u64;
196 }
197 }
198 SegmentOp::SetFrameDelay { delay_cs } => {
199 for _ in segment_frames {
200 total_frames += 1;
201 total_duration_cs += *delay_cs as u64;
202 }
203 }
204 }
205 }
206
207 (total_frames, total_duration_cs)
208}
209pub fn apply_operations<H: Sync + Send>(
224 frames: &[AnalyzedFrame<H>],
225 segments: &[Segment],
226 segment_ops: &SegmentOps,
227 frame_ops: &FrameOps,
228) -> Vec<EncodableFrame> {
229 #[cfg(feature = "parallel")]
230 {
231 segments
232 .par_iter()
233 .map(|segment| {
234 let seg_op = segment_ops.get(&segment.id).unwrap_or(&SegmentOp::Keep);
235 let segment_frames = &frames[segment.frame_range.clone()];
236 let mut segment_output = Vec::new();
237
238 if matches!(seg_op, SegmentOp::Remove) {
240 return segment_output;
241 }
242
243 if !matches!(seg_op, SegmentOp::Keep) {
245 match seg_op {
246 SegmentOp::Collapse { delay_cs } => {
247 if let Some(first) = segment_frames.first() {
248 segment_output.push(EncodableFrame::new(
249 first.frame.image.clone(),
250 *delay_cs,
251 ));
252 }
253 }
254 SegmentOp::SetDuration { total_cs } => {
255 let frame_count = segment_frames.len();
256 if frame_count > 0 {
257 let per_frame_delay = *total_cs / frame_count as u16;
258 let remainder = *total_cs % frame_count as u16;
259 for (i, frame) in segment_frames.iter().enumerate() {
260 let delay = if (i as u16) < remainder {
261 per_frame_delay + 1
262 } else {
263 per_frame_delay
264 };
265 segment_output.push(EncodableFrame::new(
266 frame.frame.image.clone(),
267 delay,
268 ));
269 }
270 }
271 }
272 SegmentOp::Scale { factor } => {
273 for frame in segment_frames {
274 let original_delay = frame.frame.delay_centiseconds as f64;
275 let new_delay = (original_delay * factor).round() as u16;
276 let new_delay = new_delay.max(1);
277 segment_output.push(EncodableFrame::new(
278 frame.frame.image.clone(),
279 new_delay,
280 ));
281 }
282 }
283 SegmentOp::SetFrameDelay { delay_cs } => {
284 for frame in segment_frames {
285 segment_output.push(EncodableFrame::new(
286 frame.frame.image.clone(),
287 *delay_cs,
288 ));
289 }
290 }
291 _ => {}
292 }
293 } else {
294 for (i, frame) in segment_frames.iter().enumerate() {
296 let frame_op = frame_ops.get(&(segment.id, i)).unwrap_or(&FrameOp::Keep);
297
298 match frame_op {
299 FrameOp::Keep | FrameOp::SplitAfter => {
300 segment_output.push(EncodableFrame::from_decoded(&frame.frame));
301 }
302 FrameOp::Remove => {}
303 }
304 }
305 }
306 segment_output
307 })
308 .flatten()
309 .collect()
310 }
311
312 #[cfg(not(feature = "parallel"))]
313 {
314 let mut output = Vec::new();
315
316 for segment in segments {
317 let seg_op = segment_ops.get(&segment.id).unwrap_or(&SegmentOp::Keep);
318 let segment_frames = &frames[segment.frame_range.clone()];
319
320 if matches!(seg_op, SegmentOp::Remove) {
322 continue;
323 }
324
325 if !matches!(seg_op, SegmentOp::Keep) {
327 match seg_op {
328 SegmentOp::Collapse { delay_cs } => {
329 if let Some(first) = segment_frames.first() {
330 output.push(EncodableFrame::new(first.frame.image.clone(), *delay_cs));
331 }
332 }
333 SegmentOp::SetDuration { total_cs } => {
334 let frame_count = segment_frames.len();
335 if frame_count > 0 {
336 let per_frame_delay = *total_cs / frame_count as u16;
337 let remainder = *total_cs % frame_count as u16;
338 for (i, frame) in segment_frames.iter().enumerate() {
339 let delay = if (i as u16) < remainder {
340 per_frame_delay + 1
341 } else {
342 per_frame_delay
343 };
344 output.push(EncodableFrame::new(frame.frame.image.clone(), delay));
345 }
346 }
347 }
348 SegmentOp::Scale { factor } => {
349 for frame in segment_frames {
350 let original_delay = frame.frame.delay_centiseconds as f64;
351 let new_delay = (original_delay * factor).round() as u16;
352 let new_delay = new_delay.max(1);
353 output.push(EncodableFrame::new(frame.frame.image.clone(), new_delay));
354 }
355 }
356 SegmentOp::SetFrameDelay { delay_cs } => {
357 for frame in segment_frames {
358 output.push(EncodableFrame::new(frame.frame.image.clone(), *delay_cs));
359 }
360 }
361 _ => {}
362 }
363 continue;
364 }
365
366 for (i, frame) in segment_frames.iter().enumerate() {
368 let frame_op = frame_ops.get(&(segment.id, i)).unwrap_or(&FrameOp::Keep);
369
370 match frame_op {
371 FrameOp::Keep | FrameOp::SplitAfter => {
372 output.push(EncodableFrame::from_decoded(&frame.frame));
374 }
375 FrameOp::Remove => {
376 }
378 }
379 }
380 }
381
382 output
383 }
384}
385
386pub fn dry_run_all_operations<H: Sync + Send>(
390 frames: &[AnalyzedFrame<H>],
391 segments: &[Segment],
392 segment_ops: &SegmentOps,
393 frame_ops: &FrameOps,
394) -> (usize, u64) {
395 let mut total_frames = 0;
396 let mut total_duration_cs: u64 = 0;
397
398 for segment in segments {
399 let seg_op = segment_ops.get(&segment.id).unwrap_or(&SegmentOp::Keep);
400 let segment_frames = &frames[segment.frame_range.clone()];
401
402 if matches!(seg_op, SegmentOp::Remove) {
404 continue;
405 }
406
407 if !matches!(seg_op, SegmentOp::Keep) {
409 match seg_op {
410 SegmentOp::Collapse { delay_cs } => {
411 if !segment_frames.is_empty() {
412 total_frames += 1;
413 total_duration_cs += *delay_cs as u64;
414 }
415 }
416 SegmentOp::SetDuration { total_cs } => {
417 let frame_count = segment_frames.len();
418 if frame_count > 0 {
419 total_frames += frame_count;
420 total_duration_cs += *total_cs as u64;
421 }
422 }
423 SegmentOp::Scale { factor } => {
424 for frame in segment_frames {
425 let original_delay = frame.frame.delay_centiseconds as f64;
426 let new_delay = (original_delay * factor).round() as u16;
427 total_frames += 1;
428 total_duration_cs += new_delay.max(1) as u64;
429 }
430 }
431 SegmentOp::SetFrameDelay { delay_cs } => {
432 for _ in segment_frames {
433 total_frames += 1;
434 total_duration_cs += *delay_cs as u64;
435 }
436 }
437 _ => {}
438 }
439 continue;
440 }
441
442 for (i, frame) in segment_frames.iter().enumerate() {
444 let frame_op = frame_ops.get(&(segment.id, i)).unwrap_or(&FrameOp::Keep);
445
446 match frame_op {
447 FrameOp::Keep | FrameOp::SplitAfter => {
448 total_frames += 1;
449 total_duration_cs += frame.frame.delay_centiseconds as u64;
450 }
451 FrameOp::Remove => {}
452 }
453 }
454 }
455
456 (total_frames, total_duration_cs)
457}
458
459pub fn split_segments_at_points<H: Clone>(
463 frames: &[AnalyzedFrame<H>],
464 segments: &[Segment],
465 frame_ops: &FrameOps,
466) -> (Vec<AnalyzedFrame<H>>, Vec<Segment>) {
467 let mut new_frames = frames.to_vec();
468 let mut new_segments = Vec::new();
469 let mut current_segment_id = 0;
470
471 for segment in segments {
472 let mut sub_segment_start = segment.frame_range.start;
473
474 for i in 0..segment.frame_count() {
475 let abs_idx = segment.frame_range.start + i;
476 let op = frame_ops.get(&(segment.id, i)).unwrap_or(&FrameOp::Keep);
477 let is_last_frame = i == segment.frame_count() - 1;
478
479 if matches!(op, FrameOp::SplitAfter) || is_last_frame {
480 let sub_segment_end = abs_idx + 1;
482
483 if sub_segment_start < sub_segment_end {
485 let sub_range = sub_segment_start..sub_segment_end;
486
487 let sub_frames = &new_frames[sub_range.clone()];
489 let total_duration_cs: u16 = sub_frames.iter().map(|f| f.delay_cs()).sum();
490
491 let is_static = segment.is_static;
493
494 new_segments.push(Segment {
496 id: current_segment_id,
497 frame_range: sub_range.clone(),
498 total_duration_cs,
499 avg_distance: segment.avg_distance,
500 is_static,
501 });
502
503 for f in &mut new_frames[sub_range] {
505 f.segment_id = Some(current_segment_id);
506 }
507
508 current_segment_id += 1;
509 sub_segment_start = sub_segment_end;
510 }
511 }
512 }
513 }
514
515 (new_frames, new_segments)
516}
517
518#[derive(Debug, Clone, Default)]
520pub struct SegmentStats {
521 pub total_segments: usize,
523 pub static_segments: usize,
525 pub total_frames: usize,
527 pub total_duration_cs: u64,
529 pub avg_frames_per_segment: f64,
531 pub avg_duration_cs: f64,
533}
534
535impl SegmentStats {
536 pub fn from_segments(segments: &[Segment]) -> Self {
538 if segments.is_empty() {
539 return Self::default();
540 }
541
542 let total_segments = segments.len();
543 let static_segments = segments.iter().filter(|s| s.is_static).count();
544 let total_frames: usize = segments.iter().map(|s| s.frame_count()).sum();
545 let total_duration_cs: u64 = segments.iter().map(|s| s.total_duration_cs as u64).sum();
546
547 Self {
548 total_segments,
549 static_segments,
550 total_frames,
551 total_duration_cs,
552 avg_frames_per_segment: total_frames as f64 / total_segments as f64,
553 avg_duration_cs: total_duration_cs as f64 / total_segments as f64,
554 }
555 }
556
557 pub fn total_duration_ms(&self) -> u64 {
559 self.total_duration_cs * 10
560 }
561}
562
563pub fn find_pause_segments(segments: &[Segment], min_duration_cs: u16) -> Vec<usize> {
567 segments
568 .iter()
569 .filter(|s| s.is_static && s.total_duration_cs >= min_duration_cs)
570 .map(|s| s.id)
571 .collect()
572}
573
574pub fn find_longest_segment(segments: &[Segment]) -> Option<&Segment> {
576 segments.iter().max_by_key(|s| s.total_duration_cs)
577}
578
579pub fn suggest_compression_ops(
589 segments: &[Segment],
590 target_reduction: f64,
591 max_static_duration_cs: u16,
592) -> SegmentOps {
593 let mut ops = SegmentOps::new();
594
595 for segment in segments {
596 if segment.is_static && segment.total_duration_cs > max_static_duration_cs {
597 let new_duration = ((segment.total_duration_cs as f64 * target_reduction) as u16)
599 .max(max_static_duration_cs.min(segment.total_duration_cs));
600 ops.insert(
601 segment.id,
602 SegmentOp::Collapse {
603 delay_cs: new_duration,
604 },
605 );
606 }
607 }
608
609 ops
610}
611
612#[cfg(test)]
613mod tests {
614 use super::*;
615
616 #[test]
617 fn test_segment_stats_empty() {
618 let stats = SegmentStats::from_segments(&[]);
619 assert_eq!(stats.total_segments, 0);
620 assert_eq!(stats.total_frames, 0);
621 }
622
623 #[test]
624 fn test_find_pause_segments() {
625 let segments = vec![
626 Segment {
627 id: 0,
628 frame_range: 0..5,
629 total_duration_cs: 50,
630 avg_distance: 0.0,
631 is_static: true,
632 },
633 Segment {
634 id: 1,
635 frame_range: 5..10,
636 total_duration_cs: 100,
637 avg_distance: 2.0,
638 is_static: false,
639 },
640 Segment {
641 id: 2,
642 frame_range: 10..15,
643 total_duration_cs: 200,
644 avg_distance: 0.0,
645 is_static: true,
646 },
647 ];
648
649 let pauses = find_pause_segments(&segments, 100);
650 assert_eq!(pauses, vec![2]);
651 }
652
653 #[test]
654 fn test_split_segments_at_points() {
655 use crate::types::DecodedFrame;
656 use image::RgbaImage;
657
658 let frames: Vec<AnalyzedFrame<()>> = (0..10)
659 .map(|i| {
660 AnalyzedFrame::new(
661 DecodedFrame {
662 index: i,
663 image: RgbaImage::new(1, 1),
664 delay_centiseconds: 10,
665 disposal: crate::types::DisposalMethod::Keep,
666 left: 0,
667 top: 0,
668 },
669 (),
670 )
671 })
672 .collect();
673
674 let segments = vec![Segment {
675 id: 0,
676 frame_range: 0..10,
677 total_duration_cs: 100,
678 avg_distance: 0.0,
679 is_static: true,
680 }];
681
682 let mut frame_ops = FrameOps::new();
683 frame_ops.insert((0, 2), FrameOp::SplitAfter);
685
686 let (new_frames, new_segments) = split_segments_at_points(&frames, &segments, &frame_ops);
687
688 assert_eq!(new_segments.len(), 2);
689 assert_eq!(new_segments[0].id, 0);
690 assert_eq!(new_segments[0].frame_range, 0..3);
691 assert_eq!(new_segments[0].total_duration_cs, 30);
692
693 assert_eq!(new_segments[1].id, 1);
694 assert_eq!(new_segments[1].frame_range, 3..10);
695 assert_eq!(new_segments[1].total_duration_cs, 70);
696
697 assert_eq!(new_frames[0].segment_id, Some(0));
699 assert_eq!(new_frames[2].segment_id, Some(0));
700 assert_eq!(new_frames[3].segment_id, Some(1));
701 assert_eq!(new_frames[9].segment_id, Some(1));
702 }
703}