ez_ffmpeg/core/context/ffmpeg_context_builder.rs
1use crate::core::context::input::Input;
2use crate::core::context::output::Output;
3use crate::core::context::ffmpeg_context::FfmpegContext;
4use crate::core::context::filter_complex::FilterComplex;
5
6/// A builder for constructing [`FfmpegContext`] objects with customized inputs,
7/// outputs, and filter configurations. Typically, you will start by calling
8/// [`FfmpegContext::builder()`], then chain methods to add inputs, outputs, or
9/// filter descriptions, and finally invoke [`build()`](FfmpegContextBuilder::build) to produce an `FfmpegContext`.
10///
11/// # Examples
12///
13/// ```rust,ignore
14/// // 1. Create a builder
15/// let builder = FfmpegContext::builder();
16///
17/// // 2. Add at least one input and one output
18/// let ffmpeg_context = builder
19/// .input("input.mp4")
20/// // 3. Optionally add filters and more inputs/outputs
21/// .filter_desc("hue=s=0") // Example FFmpeg filter
22/// .output("output.mp4")
23/// .build()
24/// .expect("Failed to build FfmpegContext");
25///
26/// // 4. Use ffmpeg_context with FfmpegScheduler (e.g., `ffmpeg_context.start()`).
27/// ```
28#[must_use]
29pub struct FfmpegContextBuilder {
30 independent_readrate: bool,
31 inputs: Vec<Input>,
32 filter_descs: Vec<FilterComplex>,
33 outputs: Vec<Output>,
34 copy_ts: bool,
35}
36
37impl Default for FfmpegContextBuilder {
38 fn default() -> Self {
39 Self::new()
40 }
41}
42
43impl FfmpegContextBuilder {
44
45 /// Creates a new, empty `FfmpegContextBuilder`. Generally, you won't call this
46 /// directly; instead, use [`FfmpegContext::builder()`] as your entry point.
47 ///
48 /// # Example
49 ///
50 /// ```rust,ignore
51 /// let builder = FfmpegContextBuilder::new();
52 /// ```
53 pub fn new() -> Self {
54 Self {
55 independent_readrate: false,
56 inputs: vec![],
57 filter_descs: vec![],
58 outputs: vec![],
59 copy_ts: false,
60 }
61 }
62
63 /// Enables independent read rate control for multiple inputs, specifically addressing issues
64 /// with sequential processing filters like 'concat'.
65 ///
66 /// # Core Problem Solved
67 ///
68 /// When processing multiple inputs sequentially with filters like 'concat', FFmpeg's default
69 /// read rate mechanism causes unintended behavior:
70 ///
71 /// 1. By default, FFmpeg initializes a single 'wallclock_start' timestamp at the beginning
72 /// of processing, which serves as the reference for calculating read speeds.
73 ///
74 /// 2. In sequential processing (like with concat filter), inputs are processed one after another:
75 /// - The first input starts immediately and is read at the specified rate
76 /// - Subsequent inputs remain locked until previous inputs finish processing
77 ///
78 /// 3. When later inputs are unlocked (which could be minutes or hours later), their read rate
79 /// is calculated using the original 'wallclock_start' time, causing them to be read far too
80 /// quickly - often at maximum speed regardless of the set readrate.
81 ///
82 /// 4. This rapid reading loads large amounts of data into memory too quickly, potentially
83 /// causing out-of-memory errors with large media files.
84 ///
85 /// # How This Fix Works
86 ///
87 /// When `independent_readrate()` is enabled:
88 /// - Each input gets its own effective 'wallclock_start' reference time when it begins processing
89 /// - This ensures each input maintains the specified read rate, regardless of when in the
90 /// sequence it's processed
91 /// - Memory usage becomes more consistent and predictable throughout the entire processing pipeline
92 ///
93 /// # Practical Example
94 ///
95 /// ```rust,ignore
96 /// let result = FfmpegContext::builder()
97 /// .independent_readrate() // Enable independent read rates
98 /// .input(Input::from("file1.mp4").set_readrate(1.0)) // First input at 1x speed
99 /// .input(Input::from("file2.mp4").set_readrate(1.0)) // Second input also at 1x speed
100 /// .input(Input::from("file3.mp4").set_readrate(1.0)) // Third input also at 1x speed
101 /// .filter_desc("[0:v][0:a][1:v][1:a][2:v][2:a]concat=n=3:v=1:a=1") // Concatenate all inputs
102 /// .output(output)
103 /// .build()
104 /// .unwrap();
105 /// ```
106 ///
107 /// In this example, without `independent_readrate()`, the second and third files would be read
108 /// much faster than intended after the first file completes. With this option enabled, each file
109 /// maintains its 1.0x read rate precisely when it begins processing.
110 ///
111 /// # When To Use
112 ///
113 /// This option is essential when:
114 /// - Using the 'concat' filter with multiple inputs
115 /// - Processing long-duration media files sequentially
116 /// - Setting specific read rates for inputs (via `set_readrate()`)
117 /// - Memory usage is a concern in your application
118 pub fn independent_readrate(mut self) -> Self {
119 self.independent_readrate = true;
120 self
121 }
122
123 /// Adds a single [`Input`] to the builder. This can be a file path, a URL,
124 /// or a custom input with callbacks.
125 ///
126 /// Calling this method multiple times adds multiple distinct inputs.
127 ///
128 /// # Parameters
129 /// - `input` - Anything convertible into an [`Input`], such as a `&str`, `String`,
130 /// or a custom callback-based `Input`.
131 ///
132 /// # Returns
133 /// A modified `FfmpegContextBuilder`, allowing method chaining.
134 ///
135 /// # Example
136 /// ```rust,ignore
137 /// let context = FfmpegContextBuilder::new()
138 /// .input("video.mp4")
139 /// .build()
140 /// .unwrap();
141 /// ```
142 pub fn input(mut self, input: impl Into<Input>) -> Self {
143 self.inputs.push(input.into());
144 self
145 }
146
147 /// Replaces the current list of inputs with a new collection.
148 ///
149 /// This method takes a `Vec` of items convertible into [`Input`]s and sets them
150 /// as the complete set of inputs for the builder. Any previously added inputs
151 /// will be discarded.
152 ///
153 /// # Parameters
154 /// - `inputs` - A vector of items (e.g. `&str`, `String`, custom callbacks)
155 /// that will be converted into `Input`s.
156 ///
157 /// # Returns
158 /// A modified `FfmpegContextBuilder`, allowing method chaining.
159 ///
160 /// # Example
161 /// ```rust,ignore
162 /// let inputs = vec!["input1.mp4", "input2.mp4"];
163 /// let context = FfmpegContextBuilder::new()
164 /// .inputs(inputs)
165 /// .build()
166 /// .unwrap();
167 /// ```
168 pub fn inputs(mut self, inputs: Vec<impl Into<Input>>) -> Self {
169 self.inputs = inputs.into_iter().map(|input| input.into()).collect();
170 self
171 }
172
173 /// Adds a single [`Output`] to the builder, representing a single output
174 /// destination (file path, URL, or custom write callback).
175 ///
176 /// Calling this multiple times adds multiple outputs (e.g., for transcoding
177 /// to different formats simultaneously).
178 ///
179 /// # Parameters
180 /// - `output` - Anything convertible into an [`Output`], such as a `&str`, `String`,
181 /// or a callback-based output.
182 ///
183 /// # Returns
184 /// A modified `FfmpegContextBuilder`, allowing method chaining.
185 ///
186 /// # Example
187 /// ```rust,ignore
188 /// let context = FfmpegContextBuilder::new()
189 /// .output("output.mp4")
190 /// .build()
191 /// .unwrap();
192 /// ```
193 pub fn output(mut self, output: impl Into<Output>) -> Self {
194 self.outputs.push(output.into());
195 self
196 }
197
198 /// Replaces the current list of outputs with a new collection.
199 ///
200 /// This method takes a `Vec` of items convertible into [`Output`] and sets them
201 /// as the complete set of outputs for the builder. Any previously added outputs
202 /// will be discarded.
203 ///
204 /// # Parameters
205 /// - `outputs` - A vector of items (e.g. `&str`, `String`, or custom callback-based
206 /// outputs) that will be converted into `Output`s.
207 ///
208 /// # Returns
209 /// A modified `FfmpegContextBuilder`, allowing method chaining.
210 ///
211 /// # Example
212 /// ```rust,ignore
213 /// let outputs = vec!["output1.mp4", "output2.mkv"];
214 /// let context = FfmpegContextBuilder::new()
215 /// .outputs(outputs)
216 /// .build()
217 /// .unwrap();
218 /// ```
219 pub fn outputs(mut self, outputs: Vec<impl Into<Output>>) -> Self {
220 self.outputs = outputs.into_iter().map(|output| output.into()).collect();
221 self
222 }
223
224 /// Adds a single filter description (e.g., `"hue=s=0"`, `"scale=1280:720"`)
225 /// that applies to one or more inputs. Each filter description can also
226 /// contain complex filter graphs.
227 ///
228 /// Internally, it's converted into a [`FilterComplex`] object. These filters
229 /// can further manipulate or route media streams before they reach the outputs.
230 ///
231 /// # Parameters
232 /// - `filter_desc` - A string or [`FilterComplex`] describing filter operations
233 /// in FFmpeg's filter syntax.
234 ///
235 /// # Returns
236 /// A modified `FfmpegContextBuilder`, allowing method chaining.
237 ///
238 /// # Example
239 /// ```rust,ignore
240 /// let context = FfmpegContextBuilder::new()
241 /// .input("input.mp4")
242 /// .filter_desc("hue=s=0") // Desaturate the video
243 /// .output("output_gray.mp4")
244 /// .build()
245 /// .unwrap();
246 /// ```
247 pub fn filter_desc(mut self, filter_desc: impl Into<FilterComplex>) -> Self {
248 self.filter_descs.push(filter_desc.into());
249 self
250 }
251
252 /// Replaces the current filter descriptions with a new list of them.
253 ///
254 /// This method takes a `Vec` of items convertible into [`FilterComplex`], allowing
255 /// you to specify multiple complex filter graphs or distinct filter operations
256 /// all at once. Any previously added filters will be discarded.
257 ///
258 /// # Parameters
259 /// - `filter_descs` - A vector of strings or [`FilterComplex`] objects that define
260 /// FFmpeg filter operations.
261 ///
262 /// # Returns
263 /// A modified `FfmpegContextBuilder`, allowing method chaining.
264 ///
265 /// # Example
266 /// ```rust,ignore
267 /// let filter_chains = vec!["scale=1280:720", "drawtext=fontfile=...:text='Watermark'"];
268 /// let context = FfmpegContextBuilder::new()
269 /// .input("input.mp4")
270 /// .filter_descs(filter_chains)
271 /// .output("output_scaled.mp4")
272 /// .build()
273 /// .unwrap();
274 /// ```
275 pub fn filter_descs(mut self, filter_descs: Vec<impl Into<FilterComplex>>) -> Self {
276 self.filter_descs = filter_descs.into_iter().map(|filter| filter.into()).collect();
277 self
278 }
279
280 /// Enables timestamp copying from input to output
281 ///
282 /// This method sets the `copy_ts` flag to true, which is equivalent to FFmpeg's `-copyts` option.
283 /// When enabled, timestamps from the input stream are preserved in the output stream without modification.
284 /// This is useful when you want to maintain the original timing information from the source media.
285 ///
286 /// # Example
287 /// ```rust,ignore
288 /// let builder = FfmpegContextBuilder::new()
289 /// .copyts();
290 /// ```
291 pub fn copyts(mut self) -> Self {
292 self.copy_ts = true;
293 self
294 }
295
296 /// Finalizes this builder, creating an [`FfmpegContext`] which can then be used
297 /// to run FFmpeg jobs via [`FfmpegContext::start()`](FfmpegContext::start) or by constructing an
298 /// [`FfmpegScheduler`](crate::FfmpegScheduler) yourself.
299 ///
300 /// # Errors
301 /// Returns an error if any configuration issues are found (e.g., invalid URL syntax,
302 /// conflicting filter settings, etc.).
303 /// (The actual validation depends on how [`FfmpegContext::new_with_independent_readrate`](FfmpegContext::new_with_independent_readrate) is implemented.)
304 ///
305 /// # Example
306 /// ```rust,ignore
307 /// let context = FfmpegContextBuilder::new()
308 /// .input("input1.mp4")
309 /// .input("input2.mp4")
310 /// .output("combined_output.mkv")
311 /// .build()
312 /// .expect("Failed to build FfmpegContext");
313 ///
314 /// // Use the context to start FFmpeg processing
315 /// let scheduler = context.start().expect("Failed to start FFmpeg job");
316 /// scheduler.wait().unwrap();
317 /// ```
318 pub fn build(self) -> crate::error::Result<FfmpegContext> {
319 FfmpegContext::new_with_options(
320 self.independent_readrate,
321 self.inputs,
322 self.filter_descs,
323 self.outputs,
324 self.copy_ts
325 )
326 }
327}