use std::fmt::{Debug, Formatter, Result as FmtResult};
use std::sync::Arc;
use ffmpeg_next::format::Pixel;
use crate::progress::{CancellationToken, NoOpProgress, ProgressCallback};
#[cfg(feature = "hardware")]
use crate::hardware_acceleration::HardwareAccelerationMode;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum PixelFormat {
#[default]
Rgb8,
Rgba8,
Gray8,
}
impl PixelFormat {
pub(crate) fn to_ffmpeg_pixel(self) -> Pixel {
match self {
PixelFormat::Rgb8 => Pixel::RGB24,
PixelFormat::Rgba8 => Pixel::RGBA,
PixelFormat::Gray8 => Pixel::GRAY8,
}
}
}
#[derive(Debug, Clone)]
pub struct FrameOutputOptions {
pub pixel_format: PixelFormat,
pub width: Option<u32>,
pub height: Option<u32>,
pub maintain_aspect_ratio: bool,
}
impl Default for FrameOutputOptions {
fn default() -> Self {
Self {
pixel_format: PixelFormat::Rgb8,
width: None,
height: None,
maintain_aspect_ratio: true,
}
}
}
impl FrameOutputOptions {
pub(crate) fn resolve_dimensions(&self, source_width: u32, source_height: u32) -> (u32, u32) {
match (self.width, self.height) {
(Some(w), Some(h)) => (w, h),
(Some(w), None) if self.maintain_aspect_ratio && source_width > 0 => {
let ratio = w as f64 / source_width as f64;
let h = (source_height as f64 * ratio).round() as u32;
(w, h.max(1))
}
(Some(w), None) => (w, source_height),
(None, Some(h)) if self.maintain_aspect_ratio && source_height > 0 => {
let ratio = h as f64 / source_height as f64;
let w = (source_width as f64 * ratio).round() as u32;
(w.max(1), h)
}
(None, Some(h)) => (source_width, h),
(None, None) => (source_width, source_height),
}
}
}
#[derive(Clone)]
pub struct ExtractOptions {
pub(crate) progress: Arc<dyn ProgressCallback>,
pub(crate) cancellation: Option<CancellationToken>,
pub(crate) batch_size: u64,
pub(crate) frame_output: FrameOutputOptions,
#[cfg(feature = "hardware")]
pub(crate) hardware_acceleration: HardwareAccelerationMode,
}
impl Debug for ExtractOptions {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
f.debug_struct("ExtractOptions")
.field("has_progress", &true)
.field("has_cancellation", &self.cancellation.is_some())
.field("batch_size", &self.batch_size)
.finish()
}
}
impl Default for ExtractOptions {
fn default() -> Self {
Self::new()
}
}
impl ExtractOptions {
pub fn new() -> Self {
Self {
progress: Arc::new(NoOpProgress),
cancellation: None,
batch_size: 1,
frame_output: FrameOutputOptions::default(),
#[cfg(feature = "hardware")]
hardware_acceleration: HardwareAccelerationMode::Auto,
}
}
#[must_use]
pub fn with_progress(mut self, callback: Arc<dyn ProgressCallback>) -> Self {
self.progress = callback;
self
}
#[must_use]
pub fn with_cancellation(mut self, token: CancellationToken) -> Self {
self.cancellation = Some(token);
self
}
#[must_use]
pub fn with_batch_size(mut self, size: u64) -> Self {
self.batch_size = size.max(1);
self
}
#[must_use]
pub fn with_pixel_format(mut self, format: PixelFormat) -> Self {
self.frame_output.pixel_format = format;
self
}
#[must_use]
pub fn with_resolution(mut self, width: Option<u32>, height: Option<u32>) -> Self {
self.frame_output.width = width;
self.frame_output.height = height;
self
}
#[must_use]
pub fn with_maintain_aspect_ratio(mut self, maintain: bool) -> Self {
self.frame_output.maintain_aspect_ratio = maintain;
self
}
#[must_use]
pub fn with_frame_output(mut self, config: FrameOutputOptions) -> Self {
self.frame_output = config;
self
}
#[cfg(feature = "hardware")]
#[must_use]
pub fn with_hardware_acceleration(mut self, mode: HardwareAccelerationMode) -> Self {
self.hardware_acceleration = mode;
self
}
pub(crate) fn is_cancelled(&self) -> bool {
self.cancellation
.as_ref()
.is_some_and(|token| token.is_cancelled())
}
}