1use std::time::Duration;
4
5#[derive(Debug, Clone)]
10pub struct Progress {
11 pub frames_encoded: u64,
13
14 pub total_frames: Option<u64>,
16
17 pub bytes_written: u64,
19
20 pub current_bitrate: u64,
22
23 pub elapsed: Duration,
25
26 pub remaining: Option<Duration>,
28
29 pub current_fps: f64,
36}
37
38impl Progress {
39 #[must_use]
43 pub fn percent(&self) -> f64 {
44 match self.total_frames {
45 Some(total) if total > 0 => {
46 #[allow(clippy::cast_precision_loss)]
47 let percent = self.frames_encoded as f64 / total as f64 * 100.0;
48 percent
49 }
50 _ => 0.0,
51 }
52 }
53}
54
55pub trait ProgressCallback: Send {
85 fn on_progress(&mut self, progress: &Progress);
94
95 fn should_cancel(&self) -> bool {
108 false
109 }
110}
111
112impl<F> ProgressCallback for F
117where
118 F: FnMut(&Progress) + Send,
119{
120 fn on_progress(&mut self, progress: &Progress) {
121 self(progress);
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128
129 #[test]
130 fn test_progress_percent() {
131 let progress = Progress {
132 frames_encoded: 50,
133 total_frames: Some(100),
134 bytes_written: 1_000_000,
135 current_bitrate: 8_000_000,
136 elapsed: Duration::from_secs(2),
137 remaining: Some(Duration::from_secs(2)),
138 current_fps: 25.0,
139 };
140
141 assert!((progress.percent() - 50.0).abs() < 0.001);
142 }
143
144 #[test]
145 fn test_progress_percent_unknown_total() {
146 let progress = Progress {
147 frames_encoded: 50,
148 total_frames: None,
149 bytes_written: 1_000_000,
150 current_bitrate: 8_000_000,
151 elapsed: Duration::from_secs(2),
152 remaining: None,
153 current_fps: 25.0,
154 };
155
156 assert!((progress.percent() - 0.0).abs() < 0.001);
157 }
158
159 #[test]
160 fn test_progress_percent_zero_total() {
161 let progress = Progress {
162 frames_encoded: 0,
163 total_frames: Some(0),
164 bytes_written: 0,
165 current_bitrate: 0,
166 elapsed: Duration::from_secs(0),
167 remaining: None,
168 current_fps: 0.0,
169 };
170
171 assert!((progress.percent() - 0.0).abs() < 0.001);
172 }
173
174 #[test]
175 fn test_progress_callback_closure() {
176 let mut called = false;
177 let mut callback = |progress: &Progress| {
178 called = true;
179 assert_eq!(progress.frames_encoded, 42);
180 };
181
182 let progress = Progress {
183 frames_encoded: 42,
184 total_frames: Some(100),
185 bytes_written: 500_000,
186 current_bitrate: 4_000_000,
187 elapsed: Duration::from_secs(1),
188 remaining: Some(Duration::from_secs(1)),
189 current_fps: 42.0,
190 };
191
192 callback.on_progress(&progress);
193 assert!(called);
194 }
195
196 #[test]
197 fn test_progress_callback_should_cancel_default() {
198 let callback = |_progress: &Progress| {};
199 assert!(!callback.should_cancel());
200 }
201
202 #[test]
203 fn test_progress_callback_custom_impl() {
204 use std::sync::Arc;
205 use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
206
207 struct TestCallback {
208 counter: Arc<AtomicU64>,
209 cancelled: Arc<AtomicBool>,
210 }
211
212 impl ProgressCallback for TestCallback {
213 fn on_progress(&mut self, progress: &Progress) {
214 self.counter
215 .store(progress.frames_encoded, Ordering::Relaxed);
216 }
217
218 fn should_cancel(&self) -> bool {
219 self.cancelled.load(Ordering::Relaxed)
220 }
221 }
222
223 let counter = Arc::new(AtomicU64::new(0));
224 let cancelled = Arc::new(AtomicBool::new(false));
225
226 let mut callback = TestCallback {
227 counter: counter.clone(),
228 cancelled: cancelled.clone(),
229 };
230
231 let progress = Progress {
232 frames_encoded: 100,
233 total_frames: Some(200),
234 bytes_written: 1_000_000,
235 current_bitrate: 8_000_000,
236 elapsed: Duration::from_secs(2),
237 remaining: Some(Duration::from_secs(2)),
238 current_fps: 50.0,
239 };
240
241 callback.on_progress(&progress);
243 assert_eq!(counter.load(Ordering::Relaxed), 100);
244
245 assert!(!callback.should_cancel());
247 cancelled.store(true, Ordering::Relaxed);
248 assert!(callback.should_cancel());
249 }
250}