use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum OperationType {
FrameExtraction,
AudioExtraction,
SceneDetection,
Validation,
Remuxing,
SubtitleExtraction,
Transcoding,
ThumbnailGeneration,
GifExport,
WaveformGeneration,
LoudnessAnalysis,
StreamCopy,
}
#[derive(Debug, Clone)]
pub struct ProgressInfo {
pub operation: OperationType,
pub current: u64,
pub total: Option<u64>,
pub percentage: Option<f32>,
pub elapsed: Duration,
pub estimated_remaining: Option<Duration>,
pub current_frame: Option<u64>,
pub current_timestamp: Option<Duration>,
}
pub trait ProgressCallback: Send + Sync {
fn on_progress(&self, info: &ProgressInfo);
}
pub(crate) struct NoOpProgress;
impl ProgressCallback for NoOpProgress {
fn on_progress(&self, _info: &ProgressInfo) {}
}
#[derive(Debug, Clone)]
pub struct CancellationToken {
cancelled: Arc<AtomicBool>,
}
impl CancellationToken {
pub fn new() -> Self {
Self {
cancelled: Arc::new(AtomicBool::new(false)),
}
}
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::Release);
}
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::Acquire)
}
}
impl Default for CancellationToken {
fn default() -> Self {
Self::new()
}
}
pub(crate) struct ProgressTracker {
callback: Arc<dyn ProgressCallback>,
operation: OperationType,
total: Option<u64>,
current: u64,
batch_size: u64,
start_time: Instant,
items_since_last_report: u64,
}
impl ProgressTracker {
pub(crate) fn new(
callback: Arc<dyn ProgressCallback>,
operation: OperationType,
total: Option<u64>,
batch_size: u64,
) -> Self {
Self {
callback,
operation,
total,
current: 0,
batch_size: batch_size.max(1),
start_time: Instant::now(),
items_since_last_report: 0,
}
}
pub(crate) fn advance(&mut self, frame_number: Option<u64>, timestamp: Option<Duration>) {
self.current += 1;
self.items_since_last_report += 1;
if self.items_since_last_report >= self.batch_size {
self.report(frame_number, timestamp);
self.items_since_last_report = 0;
}
}
pub(crate) fn finish(&mut self) {
self.report(None, None);
}
fn report(&self, frame_number: Option<u64>, timestamp: Option<Duration>) {
let elapsed = self.start_time.elapsed();
let percentage = self
.total
.filter(|&t| t > 0)
.map(|t| (self.current as f32 / t as f32) * 100.0);
let estimated_remaining = if self.current > 0 {
self.total.map(|t| {
let remaining = t.saturating_sub(self.current);
let per_item = elapsed / self.current as u32;
per_item * remaining as u32
})
} else {
None
};
let info = ProgressInfo {
operation: self.operation,
current: self.current,
total: self.total,
percentage,
elapsed,
estimated_remaining,
current_frame: frame_number,
current_timestamp: timestamp,
};
self.callback.on_progress(&info);
}
}