use std::path::{Path, PathBuf};
use std::time::Duration;
use crate::{EncoderSettings, Error, Grade, Layer, MediaInfo, Overlay, Rational, Result};
use ffcine_core::color::named_enum;
use std::fmt;
use std::str::FromStr;
named_enum!(
#[derive(Default)]
Decode {
#[default]
Vulkan = "vulkan",
Cuda = "cuda",
}
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum Device {
#[default]
Auto,
Cuda(u32),
Vulkan(u32),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FrameContext {
pub before: u64,
pub after: u64,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum Stage {
Grade(Grade),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Pipeline {
pub input: PathBuf,
pub output: PathBuf,
pub start: Duration,
pub frames: Option<u64>,
pub context: FrameContext,
pub frame_rate: Option<Rational>,
pub stages: Vec<Stage>,
pub encoder: EncoderSettings,
pub device: Device,
pub decode: Decode,
pub layers: Vec<Layer>,
pub overlays: Vec<Overlay>,
}
impl Pipeline {
pub fn new(input: impl Into<PathBuf>, output: impl Into<PathBuf>) -> Self {
Self {
input: input.into(),
output: output.into(),
start: Duration::ZERO,
frames: None,
context: FrameContext::default(),
frame_rate: None,
stages: Vec::new(),
encoder: EncoderSettings::default(),
device: Device::Auto,
decode: Decode::default(),
layers: Vec::new(),
overlays: Vec::new(),
}
}
#[must_use]
pub fn stage(mut self, stage: Stage) -> Self {
self.stages.push(stage);
self
}
#[must_use]
pub fn grade(self, grade: Grade) -> Self {
self.stage(Stage::Grade(grade))
}
#[must_use]
pub fn range(mut self, start: Duration, frames: Option<u64>) -> Self {
self.start = start;
self.frames = frames;
self
}
#[must_use]
pub fn context(mut self, before: u64, after: u64) -> Self {
self.context = FrameContext { before, after };
self
}
#[must_use]
pub fn decode(mut self, decode: Decode) -> Self {
self.decode = decode;
self
}
#[must_use]
pub fn frame_rate(mut self, rate: Rational) -> Self {
self.frame_rate = Some(rate);
self
}
#[must_use]
pub fn encoder(mut self, encoder: EncoderSettings) -> Self {
self.encoder = encoder;
self
}
#[must_use]
pub fn overlays(mut self, overlays: Vec<Overlay>) -> Self {
self.overlays = overlays;
self
}
#[must_use]
pub fn layers(mut self, layers: Vec<Layer>) -> Self {
self.layers = layers;
self
}
#[must_use]
pub fn device(mut self, device: Device) -> Self {
self.device = device;
self
}
pub fn run(&self, backend: &dyn Backend) -> Result<RunStats> {
backend.run(self)
}
}
pub trait Backend {
fn name(&self) -> &'static str;
fn probe(&self, path: &Path) -> Result<MediaInfo>;
fn run(&self, pipeline: &Pipeline) -> Result<RunStats>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct RunStats {
pub frames: u64,
pub setup: Duration,
pub first_frame: Duration,
pub wall: Duration,
}
impl RunStats {
#[allow(clippy::cast_precision_loss)]
pub fn steady_fps(&self) -> Option<f64> {
let t = self.wall.checked_sub(self.first_frame)?.as_secs_f64();
(self.frames > 1 && t > 0.0).then(|| (self.frames - 1) as f64 / t)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder() {
let p = Pipeline::new("in.mov", "out.mkv")
.grade(Grade::default())
.device(Device::Cuda(0));
assert_eq!(p.stages.len(), 1);
assert_eq!(p.device, Device::Cuda(0));
assert_eq!(p.encoder, EncoderSettings::nvenc_hevc_master());
}
}