use std::time::Duration;
#[derive(Debug, Clone)]
pub struct EncodeProgress {
pub frames_encoded: u64,
pub total_frames: Option<u64>,
pub bytes_written: u64,
pub current_bitrate: u64,
pub elapsed: Duration,
pub remaining: Option<Duration>,
pub current_fps: f64,
}
impl EncodeProgress {
#[must_use]
pub fn percent(&self) -> f64 {
match self.total_frames {
Some(total) if total > 0 => {
#[allow(clippy::cast_precision_loss)]
let percent = self.frames_encoded as f64 / total as f64 * 100.0;
percent
}
_ => 0.0,
}
}
}
pub trait EncodeProgressCallback: Send {
fn on_progress(&mut self, progress: &EncodeProgress);
fn should_cancel(&self) -> bool {
false
}
}
impl<F> EncodeProgressCallback for F
where
F: FnMut(&EncodeProgress) + Send,
{
fn on_progress(&mut self, progress: &EncodeProgress) {
self(progress);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progress_percent() {
let progress = EncodeProgress {
frames_encoded: 50,
total_frames: Some(100),
bytes_written: 1_000_000,
current_bitrate: 8_000_000,
elapsed: Duration::from_secs(2),
remaining: Some(Duration::from_secs(2)),
current_fps: 25.0,
};
assert!((progress.percent() - 50.0).abs() < 0.001);
}
#[test]
fn test_progress_percent_unknown_total() {
let progress = EncodeProgress {
frames_encoded: 50,
total_frames: None,
bytes_written: 1_000_000,
current_bitrate: 8_000_000,
elapsed: Duration::from_secs(2),
remaining: None,
current_fps: 25.0,
};
assert!((progress.percent() - 0.0).abs() < 0.001);
}
#[test]
fn test_progress_percent_zero_total() {
let progress = EncodeProgress {
frames_encoded: 0,
total_frames: Some(0),
bytes_written: 0,
current_bitrate: 0,
elapsed: Duration::from_secs(0),
remaining: None,
current_fps: 0.0,
};
assert!((progress.percent() - 0.0).abs() < 0.001);
}
#[test]
fn test_progress_callback_closure() {
let mut called = false;
let mut callback = |progress: &EncodeProgress| {
called = true;
assert_eq!(progress.frames_encoded, 42);
};
let progress = EncodeProgress {
frames_encoded: 42,
total_frames: Some(100),
bytes_written: 500_000,
current_bitrate: 4_000_000,
elapsed: Duration::from_secs(1),
remaining: Some(Duration::from_secs(1)),
current_fps: 42.0,
};
callback.on_progress(&progress);
assert!(called);
}
#[test]
fn test_progress_callback_should_cancel_default() {
let callback = |_progress: &EncodeProgress| {};
assert!(!callback.should_cancel());
}
#[test]
fn test_progress_callback_custom_impl() {
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
struct TestCallback {
counter: Arc<AtomicU64>,
cancelled: Arc<AtomicBool>,
}
impl EncodeProgressCallback for TestCallback {
fn on_progress(&mut self, progress: &EncodeProgress) {
self.counter
.store(progress.frames_encoded, Ordering::Relaxed);
}
fn should_cancel(&self) -> bool {
self.cancelled.load(Ordering::Relaxed)
}
}
let counter = Arc::new(AtomicU64::new(0));
let cancelled = Arc::new(AtomicBool::new(false));
let mut callback = TestCallback {
counter: counter.clone(),
cancelled: cancelled.clone(),
};
let progress = EncodeProgress {
frames_encoded: 100,
total_frames: Some(200),
bytes_written: 1_000_000,
current_bitrate: 8_000_000,
elapsed: Duration::from_secs(2),
remaining: Some(Duration::from_secs(2)),
current_fps: 50.0,
};
callback.on_progress(&progress);
assert_eq!(counter.load(Ordering::Relaxed), 100);
assert!(!callback.should_cancel());
cancelled.store(true, Ordering::Relaxed);
assert!(callback.should_cancel());
}
}