Skip to main content

ff_encode/
progress.rs

1//! Encoding progress tracking.
2
3use std::time::Duration;
4
5/// Encoding progress information.
6///
7/// Provides real-time information about the encoding process,
8/// including frames encoded, bytes written, and time estimates.
9#[derive(Debug, Clone)]
10pub struct Progress {
11    /// Number of frames encoded so far
12    pub frames_encoded: u64,
13
14    /// Total number of frames to encode (if known)
15    pub total_frames: Option<u64>,
16
17    /// Number of bytes written to output file
18    pub bytes_written: u64,
19
20    /// Current encoding bitrate (bits per second)
21    pub current_bitrate: u64,
22
23    /// Time elapsed since encoding started
24    pub elapsed: Duration,
25
26    /// Estimated remaining time (if known)
27    pub remaining: Option<Duration>,
28
29    /// Current encoding FPS (frames per second).
30    ///
31    /// Calculated as: `frames_encoded / elapsed_seconds`
32    ///
33    /// This represents the actual encoding speed, which may differ from
34    /// the target video FPS. Higher values indicate faster encoding.
35    pub current_fps: f64,
36}
37
38impl Progress {
39    /// Calculate progress percentage (0.0 - 100.0).
40    ///
41    /// Returns 0.0 if total frames is unknown.
42    #[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
55/// Progress callback trait for monitoring encoding progress.
56///
57/// Implement this trait to receive real-time encoding progress updates
58/// and optionally support encoding cancellation.
59///
60/// # Examples
61///
62/// ```ignore
63/// use ff_encode::{Progress, ProgressCallback};
64/// use std::sync::Arc;
65/// use std::sync::atomic::{AtomicBool, Ordering};
66///
67/// struct MyProgressHandler {
68///     cancelled: Arc<AtomicBool>,
69/// }
70///
71/// impl ProgressCallback for MyProgressHandler {
72///     fn on_progress(&mut self, progress: &Progress) {
73///         println!("Encoded {} frames at {:.1} fps",
74///             progress.frames_encoded,
75///             progress.current_fps
76///         );
77///     }
78///
79///     fn should_cancel(&self) -> bool {
80///         self.cancelled.load(Ordering::Relaxed)
81///     }
82/// }
83/// ```
84pub trait ProgressCallback: Send {
85    /// Called when encoding progress is updated.
86    ///
87    /// This method is called periodically during encoding to report progress.
88    /// The frequency of calls depends on the encoding speed and frame rate.
89    ///
90    /// # Arguments
91    ///
92    /// * `progress` - Current encoding progress information
93    fn on_progress(&mut self, progress: &Progress);
94
95    /// Check if encoding should be cancelled.
96    ///
97    /// The encoder will check this method periodically during encoding.
98    /// Return `true` to request cancellation of the encoding process.
99    ///
100    /// # Returns
101    ///
102    /// `true` to cancel encoding, `false` to continue
103    ///
104    /// # Default Implementation
105    ///
106    /// The default implementation returns `false` (never cancel).
107    fn should_cancel(&self) -> bool {
108        false
109    }
110}
111
112/// Implement ProgressCallback for closures.
113///
114/// This allows using simple closures as progress callbacks without
115/// needing to define a custom struct implementing the trait.
116impl<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        // Test progress callback
242        callback.on_progress(&progress);
243        assert_eq!(counter.load(Ordering::Relaxed), 100);
244
245        // Test cancellation
246        assert!(!callback.should_cancel());
247        cancelled.store(true, Ordering::Relaxed);
248        assert!(callback.should_cancel());
249    }
250}