ffcine_render/
pipeline.rs1use std::path::{Path, PathBuf};
4use std::time::Duration;
5
6use crate::{EncoderSettings, Error, Grade, Layer, MediaInfo, Overlay, Rational, Result};
7use ffcine_core::color::named_enum;
8use std::fmt;
9use std::str::FromStr;
10
11named_enum!(
12 #[derive(Default)]
15 Decode {
16 #[default]
17 Vulkan = "vulkan",
18 Cuda = "cuda",
19 }
20);
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24#[non_exhaustive]
25pub enum Device {
26 #[default]
28 Auto,
29 Cuda(u32),
32 Vulkan(u32),
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
38pub struct FrameContext {
39 pub before: u64,
40 pub after: u64,
41}
42
43#[derive(Debug, Clone, PartialEq)]
45#[non_exhaustive]
46pub enum Stage {
47 Grade(Grade),
49}
50
51#[derive(Debug, Clone, PartialEq)]
53pub struct Pipeline {
54 pub input: PathBuf,
55 pub output: PathBuf,
56 pub start: Duration,
58 pub frames: Option<u64>,
60 pub context: FrameContext,
64 pub frame_rate: Option<Rational>,
66 pub stages: Vec<Stage>,
67 pub encoder: EncoderSettings,
68 pub device: Device,
69 pub decode: Decode,
70 pub layers: Vec<Layer>,
73 pub overlays: Vec<Overlay>,
75}
76
77impl Pipeline {
78 pub fn new(input: impl Into<PathBuf>, output: impl Into<PathBuf>) -> Self {
79 Self {
80 input: input.into(),
81 output: output.into(),
82 start: Duration::ZERO,
83 frames: None,
84 context: FrameContext::default(),
85 frame_rate: None,
86 stages: Vec::new(),
87 encoder: EncoderSettings::default(),
88 device: Device::Auto,
89 decode: Decode::default(),
90 layers: Vec::new(),
91 overlays: Vec::new(),
92 }
93 }
94
95 #[must_use]
96 pub fn stage(mut self, stage: Stage) -> Self {
97 self.stages.push(stage);
98 self
99 }
100
101 #[must_use]
102 pub fn grade(self, grade: Grade) -> Self {
103 self.stage(Stage::Grade(grade))
104 }
105
106 #[must_use]
108 pub fn range(mut self, start: Duration, frames: Option<u64>) -> Self {
109 self.start = start;
110 self.frames = frames;
111 self
112 }
113
114 #[must_use]
116 pub fn context(mut self, before: u64, after: u64) -> Self {
117 self.context = FrameContext { before, after };
118 self
119 }
120
121 #[must_use]
122 pub fn decode(mut self, decode: Decode) -> Self {
123 self.decode = decode;
124 self
125 }
126
127 #[must_use]
128 pub fn frame_rate(mut self, rate: Rational) -> Self {
129 self.frame_rate = Some(rate);
130 self
131 }
132
133 #[must_use]
134 pub fn encoder(mut self, encoder: EncoderSettings) -> Self {
135 self.encoder = encoder;
136 self
137 }
138
139 #[must_use]
141 pub fn overlays(mut self, overlays: Vec<Overlay>) -> Self {
142 self.overlays = overlays;
143 self
144 }
145
146 #[must_use]
148 pub fn layers(mut self, layers: Vec<Layer>) -> Self {
149 self.layers = layers;
150 self
151 }
152
153 #[must_use]
154 pub fn device(mut self, device: Device) -> Self {
155 self.device = device;
156 self
157 }
158
159 pub fn run(&self, backend: &dyn Backend) -> Result<RunStats> {
161 backend.run(self)
162 }
163}
164
165pub trait Backend {
168 fn name(&self) -> &'static str;
169 fn probe(&self, path: &Path) -> Result<MediaInfo>;
170 fn run(&self, pipeline: &Pipeline) -> Result<RunStats>;
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
175pub struct RunStats {
176 pub frames: u64,
178 pub setup: Duration,
181 pub first_frame: Duration,
183 pub wall: Duration,
185}
186
187impl RunStats {
188 #[allow(clippy::cast_precision_loss)]
190 pub fn steady_fps(&self) -> Option<f64> {
191 let t = self.wall.checked_sub(self.first_frame)?.as_secs_f64();
192 (self.frames > 1 && t > 0.0).then(|| (self.frames - 1) as f64 / t)
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn builder() {
202 let p = Pipeline::new("in.mov", "out.mkv")
203 .grade(Grade::default())
204 .device(Device::Cuda(0));
205 assert_eq!(p.stages.len(), 1);
206 assert_eq!(p.device, Device::Cuda(0));
207 assert_eq!(p.encoder, EncoderSettings::nvenc_hevc_master());
208 }
209}