ez_ffmpeg/core/context/output.rs
1use crate::filter::frame_pipeline::FramePipeline;
2use ffmpeg_sys_next::{AVRational, AVSampleFormat};
3use std::collections::HashMap;
4
5// Note: Output is Send if all callback fields are Send.
6// We require `+ Send` on callback types to ensure this.
7// Output is !Sync because FnMut callbacks require exclusive access.
8
9pub struct Output {
10 /// The URL of the output destination.
11 ///
12 /// This specifies where the output stream will be written. It can be:
13 /// - A local file path (e.g., `file:///path/to/output.mp4`).
14 /// - A network destination (e.g., `rtmp://example.com/live/stream`).
15 /// - Any other URL supported by FFmpeg (e.g., `udp://...`, `http://...`).
16 ///
17 /// The URL must be valid. If the URL is invalid or unsupported, the library will
18 /// return an error when attempting to initialize the output stream.
19 pub(crate) url: Option<String>,
20
21 /// A callback function for custom data writing.
22 ///
23 /// The `write_callback` function allows you to provide custom logic for handling data
24 /// output from the encoding process. This is useful for scenarios where the output is
25 /// not written directly to a standard destination (like a file or URL), but instead to
26 /// a custom data sink, such as an in-memory buffer or a custom network destination.
27 ///
28 /// The callback receives a buffer of encoded data (`buf: &[u8]`) and should return the number of bytes
29 /// successfully written. If an error occurs, a negative value should be returned. For example:
30 /// - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)` indicates an I/O error.
31 ///
32 /// ### Parameters:
33 /// - `buf: &[u8]`: A buffer containing the encoded data to be written.
34 ///
35 /// ### Return Value:
36 /// - **Positive Value**: The number of bytes successfully written.
37 /// - **Negative Value**: Indicates an error occurred, such as:
38 /// - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
39 /// - Custom-defined error codes depending on your implementation.
40 ///
41 /// ### Example:
42 /// ```rust,ignore
43 /// fn custom_write_callback(buf: &[u8]) -> i32 {
44 /// println!("Writing data: {} bytes", buf.len());
45 /// buf.len() as i32 // Return the number of bytes successfully written
46 /// }
47 /// ```
48 /// ### Note:
49 /// It is recommended to set the `format` field to the desired output format (e.g., `mp4`, `flv`, etc.)
50 /// when using a custom `write_callback`. The `format` ensures that FFmpeg processes the output
51 /// correctly for the specified format.
52 pub(crate) write_callback: Option<Box<dyn FnMut(&[u8]) -> i32 + Send>>,
53
54 /// A callback function for custom seeking within the output stream.
55 ///
56 /// The `seek_callback` function allows custom logic for adjusting the write position in
57 /// the output stream. This is essential for formats that require seeking, such as `mp4`
58 /// and `mkv`, where metadata or index information must be updated at specific positions.
59 ///
60 /// If the output format requires seeking but no `seek_callback` is provided, the operation
61 /// may fail, resulting in errors such as:
62 /// ```text
63 /// [mp4 @ 0x...] muxer does not support non seekable output
64 /// ```
65 ///
66 /// **FFmpeg may invoke `seek_callback` from different threads, so thread safety is required.**
67 /// If the destination is a `File`, **wrap it in `Arc<Mutex<File>>`** to ensure safe access.
68 ///
69 /// ### Parameters:
70 /// - `offset: i64`: The target position in the output stream where seeking should occur.
71 /// - `whence: i32`: The seek mode, which determines how `offset` should be interpreted:
72 /// - `ffmpeg_sys_next::SEEK_SET` (0) - Seek to an absolute position.
73 /// - `ffmpeg_sys_next::SEEK_CUR` (1) - Seek relative to the current position.
74 /// - `ffmpeg_sys_next::SEEK_END` (2) - Seek relative to the end of the output.
75 /// - `ffmpeg_sys_next::AVSEEK_FLAG_BYTE` (2) - Seek using **byte offset** instead of timestamps.
76 /// - `ffmpeg_sys_next::AVSEEK_SIZE` (65536) - Query the **total size** of the stream.
77 /// - `ffmpeg_sys_next::AVSEEK_FORCE` (131072) - **Force seeking, even if normally restricted.**
78 ///
79 /// ### Return Value:
80 /// - **Positive Value**: The new offset position after seeking.
81 /// - **Negative Value**: An error occurred. Common errors include:
82 /// - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE)`: Seek is not supported.
83 /// - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
84 ///
85 /// ### Example (Thread-safe seek callback using `Arc<Mutex<File>>`):
86 /// Since `FFmpeg` may call `write_callback` and `seek_callback` from different threads,
87 /// **use `Arc<Mutex<File>>` to ensure safe concurrent access.**
88 ///
89 /// ```rust,ignore
90 /// use std::fs::File;
91 /// use std::io::{Seek, SeekFrom};
92 /// use std::sync::{Arc, Mutex};
93 ///
94 /// let file = Arc::new(Mutex::new(File::create("output.mp4").expect("Failed to create file")));
95 ///
96 /// let seek_callback = {
97 /// let file = Arc::clone(&file);
98 /// Box::new(move |offset: i64, whence: i32| -> i64 {
99 /// let mut file = file.lock().unwrap();
100 ///
101 /// // ✅ Handle AVSEEK_SIZE: Return total file size
102 /// if whence == ffmpeg_sys_next::AVSEEK_SIZE {
103 /// if let Ok(size) = file.metadata().map(|m| m.len() as i64) {
104 /// println!("FFmpeg requested stream size: {}", size);
105 /// return size;
106 /// }
107 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
108 /// }
109 ///
110 /// // ✅ Ignore AVSEEK_FORCE flag
111 /// let actual_whence = whence & !ffmpeg_sys_next::AVSEEK_FORCE;
112 ///
113 /// // ✅ Handle AVSEEK_FLAG_BYTE: Perform byte-based seek
114 /// if actual_whence & ffmpeg_sys_next::AVSEEK_FLAG_BYTE != 0 {
115 /// println!("FFmpeg requested byte-based seeking. Seeking to byte offset: {}", offset);
116 /// if let Ok(new_pos) = file.seek(SeekFrom::Start(offset as u64)) {
117 /// return new_pos as i64;
118 /// }
119 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
120 /// }
121 ///
122 /// // ✅ Standard seek modes
123 /// let seek_result = match actual_whence {
124 /// ffmpeg_sys_next::SEEK_SET => file.seek(SeekFrom::Start(offset as u64)),
125 /// ffmpeg_sys_next::SEEK_CUR => file.seek(SeekFrom::Current(offset)),
126 /// ffmpeg_sys_next::SEEK_END => file.seek(SeekFrom::End(offset)),
127 /// _ => {
128 /// println!("Unsupported seek mode: {}", whence);
129 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
130 /// }
131 /// };
132 ///
133 /// match seek_result {
134 /// Ok(new_pos) => {
135 /// println!("Seek successful, new position: {}", new_pos);
136 /// new_pos as i64
137 /// }
138 /// Err(e) => {
139 /// println!("Seek failed: {}", e);
140 /// ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64
141 /// }
142 /// }
143 /// })
144 /// };
145 /// ```
146 pub(crate) seek_callback: Option<Box<dyn FnMut(i64, i32) -> i64 + Send>>,
147
148 /// A pipeline specifying how frames will be processed **before encoding**.
149 ///
150 /// Once input data is decoded into [`Frame`]s, these frames pass through
151 /// this pipeline on their way to the encoder. The pipeline is composed of
152 /// one or more [`FrameFilter`]s, each providing a specific transformation,
153 /// effect, or filter (e.g., resizing, color correction, OpenGL shader
154 /// effects, etc.).
155 ///
156 /// If set to [`None`], no additional processing is applied — frames
157 /// are sent to the encoder as they are.
158 pub(crate) frame_pipelines: Option<Vec<FramePipeline>>,
159
160 /// Unparsed stream map specifications (user input stage)
161 /// These get parsed and expanded into stream_maps during outputs_bind()
162 pub(crate) stream_map_specs: Vec<StreamMapSpec>,
163
164 /// Expanded stream maps (FFmpeg-compatible, ready for use)
165 /// Each entry maps exactly one input stream to one output stream
166 pub(crate) stream_maps: Vec<StreamMap>,
167
168 /// The output format for the container.
169 ///
170 /// This field specifies the desired output format, such as `mp4`, `flv`, or `mkv`. If `None`, FFmpeg
171 /// will attempt to automatically detect the format based on the output URL or filename extension.
172 ///
173 /// The format can be specified explicitly for scenarios where the format detection is insufficient or
174 /// where you want to force a particular container format regardless of the URL or extension.
175 pub(crate) format: Option<String>,
176
177 /// The codec to be used for **video** encoding.
178 ///
179 /// If this field is `None`, FFmpeg will try to select an appropriate video codec based on the
180 /// output format or other settings. By setting this field to a specific codec (e.g., `"h264"`, `"hevc"`, etc.),
181 /// you can override FFmpeg’s default codec selection. If the specified codec is not available
182 /// in your FFmpeg build, an error will be returned during initialization.
183 pub(crate) video_codec: Option<String>,
184
185 /// The codec to be used for **audio** encoding.
186 ///
187 /// If this field is `None`, FFmpeg will try to select an appropriate audio codec based on the
188 /// output format or other settings. By providing a value (e.g., `"aac"`, `"mp3"`, etc.),
189 /// you override FFmpeg’s default codec choice. If the specified codec is not available
190 /// in your FFmpeg build, an error will be returned during initialization.
191 pub(crate) audio_codec: Option<String>,
192
193 /// The codec to be used for **subtitle** encoding.
194 ///
195 /// If this field is `None`, FFmpeg will try to select an appropriate subtitle codec based on
196 /// the output format or other settings. Setting this field (e.g., `"mov_text"` for MP4 subtitles)
197 /// forces FFmpeg to use the specified codec. If the chosen codec is not supported by your build of FFmpeg,
198 /// an error will be returned during initialization.
199 pub(crate) subtitle_codec: Option<String>,
200 pub(crate) start_time_us: Option<i64>,
201 pub(crate) recording_time_us: Option<i64>,
202 pub(crate) stop_time_us: Option<i64>,
203 pub(crate) framerate: Option<AVRational>,
204 pub(crate) vsync_method: VSyncMethod,
205 pub(crate) bits_per_raw_sample: Option<i32>,
206 pub(crate) audio_sample_rate: Option<i32>,
207 pub(crate) audio_channels: Option<i32>,
208 pub(crate) audio_sample_fmt: Option<AVSampleFormat>,
209
210 // -q:v
211 // use fixed quality scale (VBR)
212 pub(crate) video_qscale: Option<i32>,
213
214 // -q:a
215 // set audio quality (codec-specific)
216 pub(crate) audio_qscale: Option<i32>,
217
218 /// Maximum number of **video** frames to encode (equivalent to `-frames:v` in FFmpeg).
219 ///
220 /// This option limits the number of **video** frames processed by the encoder.
221 ///
222 /// **Equivalent FFmpeg Command:**
223 /// ```sh
224 /// ffmpeg -i input.mp4 -frames:v 100 output.mp4
225 /// ```
226 ///
227 /// **Example Usage:**
228 /// ```rust,ignore
229 /// let output = Output::from("some_url")
230 /// .set_max_video_frames(300);
231 /// ```
232 pub(crate) max_video_frames: Option<i64>,
233
234 /// Maximum number of **audio** frames to encode (equivalent to `-frames:a` in FFmpeg).
235 ///
236 /// This option limits the number of **audio** frames processed by the encoder.
237 ///
238 /// **Equivalent FFmpeg Command:**
239 /// ```sh
240 /// ffmpeg -i input.mp4 -frames:a 500 output.mp4
241 /// ```
242 ///
243 /// **Example Usage:**
244 /// ```rust,ignore
245 /// let output = Output::from("some_url")
246 /// .set_max_audio_frames(500);
247 /// ```
248 pub(crate) max_audio_frames: Option<i64>,
249
250 /// Maximum number of **subtitle** frames to encode (equivalent to `-frames:s` in FFmpeg).
251 ///
252 /// This option limits the number of **subtitle** frames processed by the encoder.
253 ///
254 /// **Equivalent FFmpeg Command:**
255 /// ```sh
256 /// ffmpeg -i input.mp4 -frames:s 200 output.mp4
257 /// ```
258 ///
259 /// **Example Usage:**
260 /// ```rust,ignore
261 /// let output = Output::from("some_url")
262 /// .set_max_subtitle_frames(200);
263 /// ```
264 pub(crate) max_subtitle_frames: Option<i64>,
265
266 /// Video encoder-specific options.
267 ///
268 /// This field stores key-value pairs for configuring the **video encoder**.
269 /// These options are passed to the video encoder before encoding begins.
270 ///
271 /// **Common Examples:**
272 /// - `crf=0` (for lossless quality in x264/x265)
273 /// - `preset=ultrafast` (for faster encoding speed in H.264)
274 /// - `tune=zerolatency` (for real-time streaming)
275 pub(crate) video_codec_opts: Option<HashMap<String, String>>,
276
277 /// Audio encoder-specific options.
278 ///
279 /// This field stores key-value pairs for configuring the **audio encoder**.
280 /// These options are passed to the audio encoder before encoding begins.
281 ///
282 /// **Common Examples:**
283 /// - `b=192k` (for setting bitrate in AAC/MP3)
284 /// - `ar=44100` (for setting sample rate)
285 pub(crate) audio_codec_opts: Option<HashMap<String, String>>,
286
287 /// Subtitle encoder-specific options.
288 ///
289 /// This field stores key-value pairs for configuring the **subtitle encoder**.
290 /// These options are passed to the subtitle encoder before encoding begins.
291 ///
292 /// **Common Examples:**
293 /// - `mov_text` (for MP4 subtitles)
294 /// - `srt` (for subtitle format)
295 pub(crate) subtitle_codec_opts: Option<HashMap<String, String>>,
296
297 /// The output format options for the container.
298 ///
299 /// This field stores additional format-specific options that are passed to the FFmpeg muxer.
300 /// It is a collection of key-value pairs that can modify the behavior of the output format.
301 ///
302 /// Common examples include:
303 /// - `movflags=faststart` (for MP4 files)
304 /// - `flvflags=no_duration_filesize` (for FLV files)
305 ///
306 /// These options are used when initializing the FFmpeg output format.
307 ///
308 /// **Example Usage:**
309 /// ```rust,ignore
310 /// let output = Output::from("some_url")
311 /// .set_format_opt("movflags", "faststart");
312 /// ```
313 pub(crate) format_opts: Option<HashMap<String, String>>,
314
315 // ========== Metadata Fields ==========
316 /// Global metadata for the entire output file
317 pub(crate) global_metadata: Option<HashMap<String, String>>,
318
319 /// Stream-specific metadata with stream specifiers
320 /// Key: stream specifier string (e.g., "v:0", "a", "s:0")
321 /// Value: metadata key-value pairs for matching streams
322 /// During output initialization, each specifier is matched against actual streams
323 pub(crate) stream_metadata: Vec<(String, String, String)>, // (spec, key, value) tuples
324
325 /// Chapter-specific metadata, indexed by chapter index
326 pub(crate) chapter_metadata: HashMap<usize, HashMap<String, String>>,
327
328 /// Program-specific metadata, indexed by program index
329 pub(crate) program_metadata: HashMap<usize, HashMap<String, String>>,
330
331 /// Metadata mappings from input files
332 pub(crate) metadata_map: Vec<crate::core::metadata::MetadataMapping>,
333
334 /// Whether to automatically copy metadata from input files (default: true)
335 /// Replicates FFmpeg's default behavior of copying global and stream metadata
336 pub(crate) auto_copy_metadata: bool,
337
338 // ========== Stream Disable Flags (P1 Features) ==========
339 /// Disable video stream mapping (equivalent to `-vn` in FFmpeg).
340 /// When true, video streams will be excluded from automatic stream mapping.
341 pub(crate) video_disable: bool,
342
343 /// Disable audio stream mapping (equivalent to `-an` in FFmpeg).
344 /// When true, audio streams will be excluded from automatic stream mapping.
345 pub(crate) audio_disable: bool,
346
347 /// Disable subtitle stream mapping (equivalent to `-sn` in FFmpeg).
348 /// When true, subtitle streams will be excluded from automatic stream mapping.
349 pub(crate) subtitle_disable: bool,
350
351 /// Disable data stream mapping (equivalent to `-dn` in FFmpeg).
352 /// When true, data streams will be excluded from automatic stream mapping.
353 /// Data streams include things like timed metadata, chapter markers, etc.
354 pub(crate) data_disable: bool,
355
356 /// Output pixel format (equivalent to `-pix_fmt` in FFmpeg).
357 /// When set, forces the output video to use the specified pixel format.
358 /// Only effective when re-encoding (not when using stream copy).
359 pub(crate) pix_fmt: Option<String>,
360}
361
362#[derive(Copy, Clone, PartialEq)]
363pub enum VSyncMethod {
364 VsyncAuto,
365 VsyncCfr,
366 VsyncVfr,
367 VsyncPassthrough,
368 VsyncVscfr,
369}
370
371impl Output {
372 pub fn new(url: impl Into<String>) -> Self {
373 url.into().into()
374 }
375
376 /// Creates a new `Output` instance with a custom write callback and format string.
377 ///
378 /// This method initializes an `Output` object that uses a provided `write_callback` function
379 /// to handle the encoded data being written to the output stream. You can optionally specify
380 /// the desired output format via the `format` method.
381 ///
382 /// ### Parameters:
383 /// - `write_callback: fn(buf: &[u8]) -> i32`: A function that processes the provided buffer of
384 /// encoded data and writes it to the destination. The function should return the number of bytes
385 /// successfully written (positive value) or a negative value in case of error.
386 ///
387 /// ### Return Value:
388 /// - Returns a new `Output` instance configured with the specified `write_callback` function.
389 ///
390 /// ### Behavior of `write_callback`:
391 /// - **Positive Value**: Indicates the number of bytes successfully written.
392 /// - **Negative Value**: Indicates an error occurred. For example:
393 /// - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: Represents an input/output error.
394 /// - Other custom-defined error codes can also be returned to signal specific issues.
395 ///
396 /// ### Example:
397 /// ```rust,ignore
398 /// let output = Output::new_by_write_callback(move |buf| {
399 /// println!("Processing {} bytes of data for output", buf.len());
400 /// buf.len() as i32 // Return the number of bytes processed
401 /// })
402 /// .set_format("mp4");
403 /// ```
404 pub fn new_by_write_callback<F>(write_callback: F) -> Self
405 where
406 F: FnMut(&[u8]) -> i32 + Send + 'static,
407 {
408 (Box::new(write_callback) as Box<dyn FnMut(&[u8]) -> i32 + Send>).into()
409 }
410
411 /// Sets a custom seek callback for the output stream.
412 ///
413 /// This function assigns a user-defined function that handles seeking within the output stream.
414 /// Seeking is required for certain formats (e.g., `mp4`, `mkv`) where metadata or index information
415 /// needs to be updated at specific positions in the file.
416 ///
417 /// **Why is `seek_callback` necessary?**
418 /// - Some formats (e.g., MP4) require `seek` operations to update metadata (`moov`, `mdat`).
419 /// - If no `seek_callback` is provided for formats that require seeking, FFmpeg will fail with:
420 /// ```text
421 /// [mp4 @ 0x...] muxer does not support non seekable output
422 /// ```
423 /// - For streaming formats (`flv`, `ts`, `rtmp`, `hls`), seeking is **not required**.
424 ///
425 /// **FFmpeg may invoke `seek_callback` from different threads.**
426 /// - If using a `File` as the output, **wrap it in `Arc<Mutex<File>>`** to ensure thread-safe access.
427 ///
428 /// ### Parameters:
429 /// - `seek_callback: FnMut(i64, i32) -> i64`
430 /// - `offset: i64`: The target seek position in the stream.
431 /// - `whence: i32`: The seek mode determining how `offset` should be interpreted:
432 /// - `ffmpeg_sys_next::SEEK_SET` (0): Seek to an absolute position.
433 /// - `ffmpeg_sys_next::SEEK_CUR` (1): Seek relative to the current position.
434 /// - `ffmpeg_sys_next::SEEK_END` (2): Seek relative to the end of the output.
435 /// - `ffmpeg_sys_next::AVSEEK_FLAG_BYTE` (2): Seek using **byte offset** instead of timestamps.
436 /// - `ffmpeg_sys_next::AVSEEK_SIZE` (65536): Query the **total size** of the stream.
437 /// - `ffmpeg_sys_next::AVSEEK_FORCE` (131072): **Force seeking, even if normally restricted.**
438 ///
439 /// ### Return Value:
440 /// - **Positive Value**: The new offset position after seeking.
441 /// - **Negative Value**: An error occurred. Common errors include:
442 /// - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE)`: Seek is not supported.
443 /// - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
444 ///
445 /// ### Example (Thread-safe seek callback using `Arc<Mutex<File>>`):
446 /// Since `FFmpeg` may call `write_callback` and `seek_callback` from different threads,
447 /// **use `Arc<Mutex<File>>` to ensure safe concurrent access.**
448 ///
449 /// ```rust,ignore
450 /// use std::fs::File;
451 /// use std::io::{Seek, SeekFrom, Write};
452 /// use std::sync::{Arc, Mutex};
453 ///
454 /// // ✅ Create a thread-safe file handle
455 /// let file = Arc::new(Mutex::new(File::create("output.mp4").expect("Failed to create file")));
456 ///
457 /// // ✅ Define the write callback (data writing logic)
458 /// let write_callback = {
459 /// let file = Arc::clone(&file);
460 /// move |buf: &[u8]| -> i32 {
461 /// let mut file = file.lock().unwrap();
462 /// match file.write_all(buf) {
463 /// Ok(_) => buf.len() as i32,
464 /// Err(e) => {
465 /// println!("Write error: {}", e);
466 /// ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i32
467 /// }
468 /// }
469 /// }
470 /// };
471 ///
472 /// // ✅ Define the seek callback (position adjustment logic)
473 /// let seek_callback = {
474 /// let file = Arc::clone(&file);
475 /// Box::new(move |offset: i64, whence: i32| -> i64 {
476 /// let mut file = file.lock().unwrap();
477 ///
478 /// match whence {
479 /// // ✅ Handle AVSEEK_SIZE: Return total file size
480 /// ffmpeg_sys_next::AVSEEK_SIZE => {
481 /// if let Ok(size) = file.metadata().map(|m| m.len() as i64) {
482 /// println!("FFmpeg requested stream size: {}", size);
483 /// return size;
484 /// }
485 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
486 /// }
487 ///
488 /// // ✅ Handle AVSEEK_FLAG_BYTE: Seek using byte offset
489 /// ffmpeg_sys_next::AVSEEK_FLAG_BYTE => {
490 /// println!("FFmpeg requested byte-based seeking. Seeking to byte offset: {}", offset);
491 /// if let Ok(new_pos) = file.seek(SeekFrom::Start(offset as u64)) {
492 /// return new_pos as i64;
493 /// }
494 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
495 /// }
496 ///
497 /// // ✅ Standard seek modes
498 /// ffmpeg_sys_next::SEEK_SET => file.seek(SeekFrom::Start(offset as u64)),
499 /// ffmpeg_sys_next::SEEK_CUR => file.seek(SeekFrom::Current(offset)),
500 /// ffmpeg_sys_next::SEEK_END => file.seek(SeekFrom::End(offset)),
501 /// _ => Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Unsupported seek mode")),
502 /// }.map_or(ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64, |pos| pos as i64)
503 /// })
504 /// };
505 ///
506 /// // ✅ Create an output with both callbacks
507 /// let output = Output::new_by_write_callback(write_callback, "mp4")
508 /// .set_seek_callback(seek_callback);
509 /// ```
510 pub fn set_seek_callback<F>(mut self, seek_callback: F) -> Self
511 where
512 F: FnMut(i64, i32) -> i64 + Send + 'static,
513 {
514 self.seek_callback = Some(Box::new(seek_callback) as Box<dyn FnMut(i64, i32) -> i64 + Send>);
515 self
516 }
517
518 /// Sets the output format for the container.
519 ///
520 /// This method allows you to specify the output format for the container. If no format is specified,
521 /// FFmpeg will attempt to detect it automatically based on the file extension or output URL.
522 ///
523 /// ### Parameters:
524 /// - `format: &str`: A string specifying the desired output format (e.g., `mp4`, `flv`, `mkv`).
525 ///
526 /// ### Return Value:
527 /// - Returns the `Output` instance with the newly set format.
528 pub fn set_format(mut self, format: impl Into<String>) -> Self {
529 self.format = Some(format.into());
530 self
531 }
532
533 /// Sets the **video codec** to be used for encoding.
534 ///
535 /// # Arguments
536 /// * `video_codec` - A string slice representing the desired video codec (e.g., `"h264"`, `"hevc"`).
537 ///
538 /// # Returns
539 /// * `Self` - Returns the modified `Output` struct, allowing for method chaining.
540 ///
541 /// # Examples
542 /// ```rust,ignore
543 /// let output = Output::from("rtmp://localhost/live/stream")
544 /// .set_video_codec("h264");
545 /// ```
546 pub fn set_video_codec(mut self, video_codec: impl Into<String>) -> Self {
547 self.video_codec = Some(video_codec.into());
548 self
549 }
550
551 /// Sets the **audio codec** to be used for encoding.
552 ///
553 /// # Arguments
554 /// * `audio_codec` - A string slice representing the desired audio codec (e.g., `"aac"`, `"mp3"`).
555 ///
556 /// # Returns
557 /// * `Self` - Returns the modified `Output` struct, allowing for method chaining.
558 ///
559 /// # Examples
560 /// ```rust,ignore
561 /// let output = Output::from("rtmp://localhost/live/stream")
562 /// .set_audio_codec("aac");
563 /// ```
564 pub fn set_audio_codec(mut self, audio_codec: impl Into<String>) -> Self {
565 self.audio_codec = Some(audio_codec.into());
566 self
567 }
568
569 /// Sets the **subtitle codec** to be used for encoding.
570 ///
571 /// # Arguments
572 /// * `subtitle_codec` - A string slice representing the desired subtitle codec
573 /// (e.g., `"mov_text"`, `"webvtt"`).
574 ///
575 /// # Returns
576 /// * `Self` - Returns the modified `Output` struct, allowing for method chaining.
577 ///
578 /// # Examples
579 /// ```rust,ignore
580 /// let output = Output::from("rtmp://localhost/live/stream")
581 /// .set_subtitle_codec("mov_text");
582 /// ```
583 pub fn set_subtitle_codec(mut self, subtitle_codec: impl Into<String>) -> Self {
584 self.subtitle_codec = Some(subtitle_codec.into());
585 self
586 }
587
588 /// Replaces the entire frame-processing pipeline with a new sequence
589 /// of transformations for **pre-encoding** frames on this `Output`.
590 ///
591 /// This method clears any previously set pipelines and replaces them with the provided list.
592 ///
593 /// # Parameters
594 /// * `frame_pipelines` - A list of [`FramePipeline`] instances defining the
595 /// transformations to apply before encoding.
596 ///
597 /// # Returns
598 /// * `Self` - Returns the modified `Output`, enabling method chaining.
599 ///
600 /// # Example
601 /// ```rust,ignore
602 /// let output = Output::from("some_url")
603 /// .set_frame_pipelines(vec![
604 /// FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_VIDEO).filter("opengl", Box::new(my_filter)),
605 /// // Additional pipelines...
606 /// ]);
607 /// ```
608 pub fn set_frame_pipelines(mut self, frame_pipelines: Vec<impl Into<FramePipeline>>) -> Self {
609 self.frame_pipelines = Some(
610 frame_pipelines
611 .into_iter()
612 .map(|frame_pipeline| frame_pipeline.into())
613 .collect(),
614 );
615 self
616 }
617
618 /// Adds a single [`FramePipeline`] to the existing pipeline list.
619 ///
620 /// If no pipelines are currently defined, this method creates a new pipeline list.
621 /// Otherwise, it appends the provided pipeline to the existing transformations.
622 ///
623 /// # Parameters
624 /// * `frame_pipeline` - A [`FramePipeline`] defining a transformation.
625 ///
626 /// # Returns
627 /// * `Self` - Returns the modified `Output`, enabling method chaining.
628 ///
629 /// # Example
630 /// ```rust,ignore
631 /// let output = Output::from("some_url")
632 /// .add_frame_pipeline(FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_VIDEO).filter("opengl", Box::new(my_filter)).build())
633 /// .add_frame_pipeline(FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_AUDIO).filter("my_custom_filter1", Box::new(...)).filter("my_custom_filter2", Box::new(...)));
634 /// ```
635 pub fn add_frame_pipeline(mut self, frame_pipeline: impl Into<FramePipeline>) -> Self {
636 if self.frame_pipelines.is_none() {
637 self.frame_pipelines = Some(vec![frame_pipeline.into()]);
638 } else {
639 self.frame_pipelines
640 .as_mut()
641 .unwrap()
642 .push(frame_pipeline.into());
643 }
644 self
645 }
646
647 /// Adds a **stream mapping** for a specific stream or stream type,
648 /// **re-encoding** it according to this output’s codec settings.
649 ///
650 /// # Linklabel (FFmpeg-like Specifier)
651 ///
652 /// This string typically follows `"<input_index>:<media_type>"` syntax:
653 /// - **`"0:v"`** – the video stream(s) from input #0.
654 /// - **`"1:a?"`** – audio from input #1, **ignore** if none present (due to `?`).
655 /// - Other possibilities include `"0:s"`, `"0:d"`, etc. for subtitles/data, optionally with `?`.
656 ///
657 /// By calling `add_stream_map`, **you force re-encoding** of the chosen stream(s).
658 /// If the user wants a bit-for-bit copy, see [`add_stream_map_with_copy`](Self::add_stream_map_with_copy).
659 ///
660 /// # Parameters
661 /// - `linklabel`: An FFmpeg-style specifier referencing the desired input index and
662 /// media type, like `"0:v"`, `"1:a?"`, etc.
663 ///
664 /// # Returns
665 /// * `Self` - for chained method calls.
666 ///
667 /// # Example
668 /// ```rust,ignore
669 /// // Re-encode the video stream from input #0 (fail if no video).
670 /// let output = Output::from("output.mp4")
671 /// .add_stream_map("0:v");
672 /// ```
673 pub fn add_stream_map(mut self, linklabel: impl Into<String>) -> Self {
674 self.stream_map_specs.push(linklabel.into().into());
675 self
676 }
677
678 /// Adds a **stream mapping** for a specific stream or stream type,
679 /// **copying** it bit-for-bit from the source without re-encoding.
680 ///
681 /// # Linklabel (FFmpeg-like Specifier)
682 ///
683 /// Follows the same `"<input_index>:<media_type>"` pattern as [`add_stream_map`](Self::add_stream_map):
684 /// - **`"0:a"`** – audio stream(s) from input #0.
685 /// - **`"0:a?"`** – same, but ignore errors if no audio exists.
686 /// - And so on for video (`v`), subtitles (`s`), attachments (`t`), etc.
687 ///
688 /// # Copy vs. Re-encode
689 ///
690 /// Here, `copy = true` by default, meaning the chosen stream(s) are passed through
691 /// **without** decoding/encoding. This generally **only** works if the source’s codec
692 /// is compatible with the container/format you’re outputting to.
693 /// If you require re-encoding (e.g., to ensure compatibility or apply filters),
694 /// use [`add_stream_map`](Self::add_stream_map).
695 ///
696 /// # Parameters
697 /// - `linklabel`: An FFmpeg-style specifier referencing the desired input index and
698 /// media type, like `"0:v?"`.
699 ///
700 /// # Returns
701 /// * `Self` - for chained method calls.
702 ///
703 /// # Example
704 /// ```rust,ignore
705 /// // Copy the audio stream(s) from input #0 if present, no re-encode:
706 /// let output = Output::from("output.mkv")
707 /// .add_stream_map_with_copy("0:a?");
708 /// ```
709 pub fn add_stream_map_with_copy(mut self, linklabel: impl Into<String>) -> Self {
710 self.stream_map_specs.push(StreamMapSpec {
711 linklabel: linklabel.into(),
712 copy: true,
713 });
714 self
715 }
716
717 /// Sets the **start time** (in microseconds) for output encoding.
718 ///
719 /// If this is set, FFmpeg will attempt to start encoding from the specified
720 /// timestamp in the input stream. This can be used to skip initial content.
721 ///
722 /// # Parameters
723 /// * `start_time_us` - The start time in microseconds.
724 ///
725 /// # Returns
726 /// * `Self` - The modified `Output`, allowing method chaining.
727 ///
728 /// # Example
729 /// ```rust,ignore
730 /// let output = Output::from("output.mp4")
731 /// .set_start_time_us(2_000_000); // Start at 2 seconds
732 /// ```
733 pub fn set_start_time_us(mut self, start_time_us: i64) -> Self {
734 self.start_time_us = Some(start_time_us);
735 self
736 }
737
738 /// Sets the **recording time** (in microseconds) for output encoding.
739 ///
740 /// This indicates how many microseconds of data should be processed
741 /// (i.e., maximum duration to encode). Once this time is reached,
742 /// FFmpeg will stop encoding.
743 ///
744 /// # Parameters
745 /// * `recording_time_us` - The maximum duration (in microseconds) to process.
746 ///
747 /// # Returns
748 /// * `Self` - The modified `Output`, allowing method chaining.
749 ///
750 /// # Example
751 /// ```rust,ignore
752 /// let output = Output::from("output.mp4")
753 /// .set_recording_time_us(5_000_000); // Record for 5 seconds
754 /// ```
755 pub fn set_recording_time_us(mut self, recording_time_us: i64) -> Self {
756 self.recording_time_us = Some(recording_time_us);
757 self
758 }
759
760 /// Sets a **stop time** (in microseconds) for output encoding.
761 ///
762 /// If set, FFmpeg will stop encoding once the input’s timestamp
763 /// surpasses this value. Effectively, encoding ends at this timestamp
764 /// regardless of remaining data.
765 ///
766 /// # Parameters
767 /// * `stop_time_us` - The timestamp (in microseconds) at which to stop.
768 ///
769 /// # Returns
770 /// * `Self` - The modified `Output`, allowing method chaining.
771 ///
772 /// # Example
773 /// ```rust,ignore
774 /// let output = Output::from("output.mp4")
775 /// .set_stop_time_us(10_000_000); // Stop at 10 seconds
776 /// ```
777 pub fn set_stop_time_us(mut self, stop_time_us: i64) -> Self {
778 self.stop_time_us = Some(stop_time_us);
779 self
780 }
781
782 /// Sets a **target frame rate** (`AVRational`) for output encoding.
783 ///
784 /// This can force the output to use a specific frame rate (e.g., 30/1 for 30 FPS).
785 /// If unset, FFmpeg typically preserves the source frame rate or uses defaults
786 /// based on the selected codec/container.
787 ///
788 /// # Parameters
789 /// * `framerate` - An `AVRational` representing the desired frame rate
790 /// numerator/denominator (e.g., `AVRational { num: 30, den: 1 }` for 30fps).
791 ///
792 /// # Returns
793 /// * `Self` - The modified `Output`, allowing method chaining.
794 ///
795 /// # Example
796 /// ```rust,ignore
797 /// use ffmpeg_sys_next::AVRational;
798 /// let output = Output::from("output.mp4")
799 /// .set_framerate(AVRational { num: 30, den: 1 });
800 /// ```
801 pub fn set_framerate(mut self, framerate: AVRational) -> Self {
802 self.framerate = Some(framerate);
803 self
804 }
805
806 /// Sets the **video sync method** to be used during encoding.
807 ///
808 /// FFmpeg uses a variety of vsync policies to handle frame presentation times,
809 /// dropping/duplicating frames as needed. Adjusting this can be useful when
810 /// you need strict CFR (constant frame rate), or to pass frames through
811 /// without modification (`VsyncPassthrough`).
812 ///
813 /// # Parameters
814 /// * `method` - A variant of [`VSyncMethod`], such as `VsyncCfr` or `VsyncVfr`.
815 ///
816 /// # Returns
817 /// * `Self` - The modified `Output`, allowing method chaining.
818 ///
819 /// # Example
820 /// ```rust,ignore
821 /// let output = Output::from("output.mp4")
822 /// .set_vsync_method(VSyncMethod::VsyncCfr);
823 /// ```
824 pub fn set_vsync_method(mut self, method: VSyncMethod) -> Self {
825 self.vsync_method = method;
826 self
827 }
828
829 /// Sets the **bits per raw sample** for video encoding.
830 ///
831 /// This value can influence quality or color depth when dealing with
832 /// certain pixel formats. Commonly used for high-bit-depth workflows
833 /// or specialized encoding scenarios.
834 ///
835 /// # Parameters
836 /// * `bits` - The bits per raw sample (e.g., 8, 10, 12).
837 ///
838 /// # Returns
839 /// * `Self` - The modified `Output`, allowing method chaining.
840 ///
841 /// # Example
842 /// ```rust,ignore
843 /// let output = Output::from("output.mkv")
844 /// .set_bits_per_raw_sample(10); // e.g., 10-bit
845 /// ```
846 pub fn set_bits_per_raw_sample(mut self, bits: i32) -> Self {
847 self.bits_per_raw_sample = Some(bits);
848 self
849 }
850
851 /// Sets the **audio sample rate** (in Hz) for output encoding.
852 ///
853 /// This method allows you to specify the desired audio sample rate for the output.
854 /// Common values include 44100 (CD quality), 48000 (standard for digital video),
855 /// and 22050 or 16000 (for lower bitrate applications).
856 ///
857 /// # Parameters
858 /// * `audio_sample_rate` - The sample rate in Hertz (e.g., 44100, 48000).
859 ///
860 /// # Returns
861 /// * `Self` - The modified `Output`, allowing method chaining.
862 ///
863 /// # Example
864 /// ```rust,ignore
865 /// let output = Output::from("output.mp4")
866 /// .set_audio_sample_rate(48000); // Set to 48kHz
867 /// ```
868 pub fn set_audio_sample_rate(mut self, audio_sample_rate: i32) -> Self {
869 self.audio_sample_rate = Some(audio_sample_rate);
870 self
871 }
872
873 /// Sets the number of **audio channels** for output encoding.
874 ///
875 /// Common values include 1 (mono), 2 (stereo), 5.1 (6 channels), and 7.1 (8 channels).
876 /// This setting affects the spatial audio characteristics of the output.
877 ///
878 /// # Parameters
879 /// * `audio_channels` - The number of audio channels (e.g., 1 for mono, 2 for stereo).
880 ///
881 /// # Returns
882 /// * `Self` - The modified `Output`, allowing method chaining.
883 ///
884 /// # Example
885 /// ```rust,ignore
886 /// let output = Output::from("output.mp4")
887 /// .set_audio_channels(2); // Set to stereo
888 /// ```
889 pub fn set_audio_channels(mut self, audio_channels: i32) -> Self {
890 self.audio_channels = Some(audio_channels);
891 self
892 }
893
894 /// Sets the **audio sample format** for output encoding.
895 ///
896 /// This method allows you to specify the audio sample format, which affects
897 /// how audio samples are represented. Common formats include:
898 /// - `AV_SAMPLE_FMT_S16` (signed 16-bit)
899 /// - `AV_SAMPLE_FMT_S32` (signed 32-bit)
900 /// - `AV_SAMPLE_FMT_FLT` (32-bit float)
901 /// - `AV_SAMPLE_FMT_FLTP` (32-bit float, planar)
902 ///
903 /// The format choice can impact quality, processing requirements, and compatibility.
904 ///
905 /// # Parameters
906 /// * `sample_fmt` - An `AVSampleFormat` enum value specifying the desired sample format.
907 ///
908 /// # Returns
909 /// * `Self` - The modified `Output`, allowing method chaining.
910 ///
911 /// # Example
912 /// ```rust,ignore
913 /// use ffmpeg_sys_next::AVSampleFormat::AV_SAMPLE_FMT_S16;
914 ///
915 /// let output = Output::from("output.mp4")
916 /// .set_audio_sample_fmt(AV_SAMPLE_FMT_S16); // Set to signed 16-bit
917 /// ```
918 pub fn set_audio_sample_fmt(mut self, sample_fmt: AVSampleFormat) -> Self {
919 self.audio_sample_fmt = Some(sample_fmt);
920 self
921 }
922
923 /// Sets the **video quality scale** (VBR) for encoding.
924 ///
925 /// This method configures a fixed quality scale for variable bitrate (VBR) video encoding.
926 /// Lower values result in higher quality but larger file sizes, while higher values
927 /// produce lower quality with smaller file sizes.
928 ///
929 /// # Note on Modern Usage
930 /// While still supported, using fixed quality scale (`-q:v`) is generally not recommended
931 /// for modern video encoding workflows with codecs like H.264 and H.265. Instead, consider:
932 /// * For H.264/H.265: Use CRF (Constant Rate Factor) via `-crf` parameter
933 /// * For two-pass encoding: Use target bitrate settings
934 ///
935 /// This parameter is primarily useful for older codecs or specific scenarios where
936 /// direct quality scale control is needed.
937 ///
938 /// # Quality Scale Ranges by Codec
939 /// * **H.264/H.265**: 0-51 (if needed: 17-28)
940 /// - 17-18: Visually lossless
941 /// - 23: High quality
942 /// - 28: Good quality with reasonable file size
943 /// * **MPEG-4/MPEG-2**: 2-31 (recommended: 2-6)
944 /// - Lower values = higher quality
945 /// * **VP9**: 0-63 (if needed: 15-35)
946 ///
947 /// # Parameters
948 /// * `video_qscale` - The quality scale value for video encoding.
949 ///
950 /// # Returns
951 /// * `Self` - The modified `Output`, allowing method chaining.
952 ///
953 /// # Example
954 /// ```rust,ignore
955 /// // For MJPEG encoding of image sequences
956 /// let output = Output::from("output.jpg")
957 /// .set_video_qscale(2); // High quality JPEG images
958 ///
959 /// // For legacy image format conversion
960 /// let output = Output::from("output.png")
961 /// .set_video_qscale(3); // Controls compression level
962 /// ```
963 pub fn set_video_qscale(mut self, video_qscale: i32) -> Self {
964 self.video_qscale = Some(video_qscale);
965 self
966 }
967
968 /// Sets the **audio quality scale** for encoding.
969 ///
970 /// This method configures codec-specific audio quality settings. The range, behavior,
971 /// and optimal values depend entirely on the audio codec being used.
972 ///
973 /// # Quality Scale Ranges by Codec
974 /// * **MP3 (libmp3lame)**: 0-9 (recommended: 2-5)
975 /// - 0: Highest quality
976 /// - 2: Near-transparent quality (~190-200 kbps)
977 /// - 5: Good quality (~130 kbps)
978 /// - 9: Lowest quality
979 /// * **AAC**: 0.1-255 (recommended: 1-5)
980 /// - 1: Highest quality (~250 kbps)
981 /// - 3: Good quality (~160 kbps)
982 /// - 5: Medium quality (~100 kbps)
983 /// * **Vorbis**: -1 to 10 (recommended: 3-8)
984 /// - 10: Highest quality
985 /// - 5: Good quality
986 /// - 3: Medium quality
987 ///
988 /// # Parameters
989 /// * `audio_qscale` - The quality scale value for audio encoding.
990 ///
991 /// # Returns
992 /// * `Self` - The modified `Output`, allowing method chaining.
993 ///
994 /// # Example
995 /// ```rust,ignore
996 /// // For MP3 encoding at high quality
997 /// let output = Output::from("output.mp3")
998 /// .set_audio_codec("libmp3lame")
999 /// .set_audio_qscale(2);
1000 ///
1001 /// // For AAC encoding at good quality
1002 /// let output = Output::from("output.m4a")
1003 /// .set_audio_codec("aac")
1004 /// .set_audio_qscale(3);
1005 ///
1006 /// // For Vorbis encoding at high quality
1007 /// let output = Output::from("output.ogg")
1008 /// .set_audio_codec("libvorbis")
1009 /// .set_audio_qscale(7);
1010 /// ```
1011 pub fn set_audio_qscale(mut self, audio_qscale: i32) -> Self {
1012 self.audio_qscale = Some(audio_qscale);
1013 self
1014 }
1015
1016 /// **Sets the maximum number of video frames to encode (`-frames:v`).**
1017 ///
1018 /// **Equivalent FFmpeg Command:**
1019 /// ```sh
1020 /// ffmpeg -i input.mp4 -frames:v 100 output.mp4
1021 /// ```
1022 ///
1023 /// **Example Usage:**
1024 /// ```rust,ignore
1025 /// let output = Output::from("some_url")
1026 /// .set_max_video_frames(500);
1027 /// ```
1028 pub fn set_max_video_frames(mut self, max_frames: impl Into<Option<i64>>) -> Self {
1029 self.max_video_frames = max_frames.into();
1030 self
1031 }
1032
1033 /// **Sets the maximum number of audio frames to encode (`-frames:a`).**
1034 ///
1035 /// **Equivalent FFmpeg Command:**
1036 /// ```sh
1037 /// ffmpeg -i input.mp4 -frames:a 500 output.mp4
1038 /// ```
1039 ///
1040 /// **Example Usage:**
1041 /// ```rust,ignore
1042 /// let output = Output::from("some_url")
1043 /// .set_max_audio_frames(500);
1044 /// ```
1045 pub fn set_max_audio_frames(mut self, max_frames: impl Into<Option<i64>>) -> Self {
1046 self.max_audio_frames = max_frames.into();
1047 self
1048 }
1049
1050 /// **Sets the maximum number of subtitle frames to encode (`-frames:s`).**
1051 ///
1052 /// **Equivalent FFmpeg Command:**
1053 /// ```sh
1054 /// ffmpeg -i input.mp4 -frames:s 200 output.mp4
1055 /// ```
1056 ///
1057 /// **Example Usage:**
1058 /// ```rust,ignore
1059 /// let output = Output::from("some_url")
1060 /// .set_max_subtitle_frames(200);
1061 /// ```
1062 pub fn set_max_subtitle_frames(mut self, max_frames: impl Into<Option<i64>>) -> Self {
1063 self.max_subtitle_frames = max_frames.into();
1064 self
1065 }
1066
1067 /// Sets a **video codec-specific option**.
1068 ///
1069 /// These options control **video encoding parameters** such as compression, quality, and speed.
1070 ///
1071 /// **Supported Parameters:**
1072 /// | Parameter | Description |
1073 /// |-----------|-------------|
1074 /// | `crf=0-51` | Quality level for x264/x265, lower means higher quality (`0` is lossless) |
1075 /// | `preset=ultrafast, superfast, fast, medium, slow, veryslow` | Encoding speed, affects compression efficiency |
1076 /// | `tune=film, animation, grain, stillimage, fastdecode, zerolatency` | Optimizations for specific types of content |
1077 /// | `b=4M` | Bitrate (e.g., `4M` for 4 Mbps) |
1078 /// | `g=50` | GOP (Group of Pictures) size, affects keyframe frequency |
1079 ///
1080 /// **Example Usage:**
1081 /// ```rust,ignore
1082 /// let output = Output::from("some_url")
1083 /// .set_video_codec_opt("crf", "18")
1084 /// .set_video_codec_opt("preset", "fast");
1085 /// ```
1086 pub fn set_video_codec_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1087 if let Some(ref mut opts) = self.video_codec_opts {
1088 opts.insert(key.into(), value.into());
1089 } else {
1090 let mut opts = HashMap::new();
1091 opts.insert(key.into(), value.into());
1092 self.video_codec_opts = Some(opts);
1093 }
1094 self
1095 }
1096
1097 /// **Sets multiple video codec options at once.**
1098 ///
1099 /// **Example Usage:**
1100 /// ```rust,ignore
1101 /// let output = Output::from("some_url")
1102 /// .set_video_codec_opts(vec![
1103 /// ("crf", "18"),
1104 /// ("preset", "fast")
1105 /// ]);
1106 /// ```
1107 pub fn set_video_codec_opts(
1108 mut self,
1109 opts: Vec<(impl Into<String>, impl Into<String>)>,
1110 ) -> Self {
1111 let video_opts = self.video_codec_opts.get_or_insert_with(HashMap::new);
1112 for (key, value) in opts {
1113 video_opts.insert(key.into(), value.into());
1114 }
1115 self
1116 }
1117
1118 /// Sets a **audio codec-specific option**.
1119 ///
1120 /// These options control **audio encoding parameters** such as bitrate, sample rate, and format.
1121 ///
1122 /// **Supported Parameters:**
1123 /// | Parameter | Description |
1124 /// |-----------|-------------|
1125 /// | `b=192k` | Bitrate (e.g., `128k` for 128 Kbps, `320k` for 320 Kbps) |
1126 /// | `compression_level=0-12` | Compression efficiency for formats like FLAC |
1127 ///
1128 /// **Example Usage:**
1129 /// ```rust,ignore
1130 /// let output = Output::from("some_url")
1131 /// .set_audio_codec_opt("b", "320k")
1132 /// .set_audio_codec_opt("compression_level", "6");
1133 /// ```
1134 pub fn set_audio_codec_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1135 if let Some(ref mut opts) = self.audio_codec_opts {
1136 opts.insert(key.into(), value.into());
1137 } else {
1138 let mut opts = HashMap::new();
1139 opts.insert(key.into(), value.into());
1140 self.audio_codec_opts = Some(opts);
1141 }
1142 self
1143 }
1144
1145 /// **Sets multiple audio codec options at once.**
1146 ///
1147 /// **Example Usage:**
1148 /// ```rust,ignore
1149 /// let output = Output::from("some_url")
1150 /// .set_audio_codec_opts(vec![
1151 /// ("b", "320k"),
1152 /// ("compression_level", "6")
1153 /// ]);
1154 /// ```
1155 pub fn set_audio_codec_opts(
1156 mut self,
1157 opts: Vec<(impl Into<String>, impl Into<String>)>,
1158 ) -> Self {
1159 let audio_opts = self.audio_codec_opts.get_or_insert_with(HashMap::new);
1160 for (key, value) in opts {
1161 audio_opts.insert(key.into(), value.into());
1162 }
1163 self
1164 }
1165
1166 /// Sets a **subtitle codec-specific option**.
1167 ///
1168 /// These options control **subtitle encoding parameters** such as format and character encoding.
1169 ///
1170 /// **Supported Parameters:**
1171 /// | Parameter | Description |
1172 /// |-----------|-------------|
1173 /// | `mov_text` | Subtitle format for MP4 files |
1174 /// | `srt` | Subtitle format for `.srt` files |
1175 /// | `ass` | Advanced SubStation Alpha (ASS) subtitle format |
1176 /// | `forced_subs=1` | Forces the subtitles to always be displayed |
1177 ///
1178 /// **Example Usage:**
1179 /// ```rust,ignore
1180 /// let output = Output::from("some_url")
1181 /// .set_subtitle_codec_opt("mov_text", "");
1182 /// ```
1183 pub fn set_subtitle_codec_opt(
1184 mut self,
1185 key: impl Into<String>,
1186 value: impl Into<String>,
1187 ) -> Self {
1188 if let Some(ref mut opts) = self.subtitle_codec_opts {
1189 opts.insert(key.into(), value.into());
1190 } else {
1191 let mut opts = HashMap::new();
1192 opts.insert(key.into(), value.into());
1193 self.subtitle_codec_opts = Some(opts);
1194 }
1195 self
1196 }
1197
1198 /// **Sets multiple subtitle codec options at once.**
1199 ///
1200 /// **Example Usage:**
1201 /// ```rust,ignore
1202 /// let output = Output::from("some_url")
1203 /// .set_subtitle_codec_opts(vec![
1204 /// ("mov_text", ""),
1205 /// ("forced_subs", "1")
1206 /// ]);
1207 /// ```
1208 pub fn set_subtitle_codec_opts(
1209 mut self,
1210 opts: Vec<(impl Into<String>, impl Into<String>)>,
1211 ) -> Self {
1212 let subtitle_opts = self.subtitle_codec_opts.get_or_insert_with(HashMap::new);
1213 for (key, value) in opts {
1214 subtitle_opts.insert(key.into(), value.into());
1215 }
1216 self
1217 }
1218
1219 /// Sets a format-specific option for the output container.
1220 ///
1221 /// FFmpeg supports various format-specific options that can be passed to the muxer.
1222 /// These options allow fine-tuning of the output container’s behavior.
1223 ///
1224 /// **Example Usage:**
1225 /// ```rust,ignore
1226 /// let output = Output::from("some_url")
1227 /// .set_format_opt("movflags", "faststart")
1228 /// .set_format_opt("flvflags", "no_duration_filesize");
1229 /// ```
1230 ///
1231 /// ### Common Format Options:
1232 /// | Format | Option | Description |
1233 /// |--------|--------|-------------|
1234 /// | `mp4` | `movflags=faststart` | Moves moov atom to the beginning of the file for faster playback start |
1235 /// | `flv` | `flvflags=no_duration_filesize` | Removes duration/size metadata for live streaming |
1236 ///
1237 /// **Parameters:**
1238 /// - `key`: The format option name (e.g., `"movflags"`, `"flvflags"`).
1239 /// - `value`: The value to set (e.g., `"faststart"`, `"no_duration_filesize"`).
1240 ///
1241 /// Returns the modified `Output` struct for chaining.
1242 pub fn set_format_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1243 if let Some(ref mut opts) = self.format_opts {
1244 opts.insert(key.into(), value.into());
1245 } else {
1246 let mut opts = HashMap::new();
1247 opts.insert(key.into(), value.into());
1248 self.format_opts = Some(opts);
1249 }
1250 self
1251 }
1252
1253 /// Sets multiple format-specific options at once.
1254 ///
1255 /// This method allows setting multiple format options in a single call.
1256 ///
1257 /// **Example Usage:**
1258 /// ```rust,ignore
1259 /// let output = Output::from("some_url")
1260 /// .set_format_opts(vec![
1261 /// ("movflags", "faststart"),
1262 /// ("flvflags", "no_duration_filesize")
1263 /// ]);
1264 /// ```
1265 ///
1266 /// **Parameters:**
1267 /// - `opts`: A vector of key-value pairs representing format options.
1268 ///
1269 /// Returns the modified `Output` struct for chaining.
1270 pub fn set_format_opts(mut self, opts: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
1271 if let Some(ref mut format_opts) = self.format_opts {
1272 for (key, value) in opts {
1273 format_opts.insert(key.into(), value.into());
1274 }
1275 } else {
1276 let mut format_opts = HashMap::new();
1277 for (key, value) in opts {
1278 format_opts.insert(key.into(), value.into());
1279 }
1280 self.format_opts = Some(format_opts);
1281 }
1282 self
1283 }
1284
1285 // ========== Metadata API Methods ==========
1286 // The following helpers mirror FFmpeg's command-line metadata options as implemented in
1287 // fftools/ffmpeg_opt.c (opt_metadata / opt_map_metadata) and the automatic propagation rules
1288 // in fftools/ffmpeg_mux_init.c:3050-3070. Each method references the corresponding FFmpeg
1289 // behavior so callers can cross-check the C implementation when needed.
1290
1291 /// Add or update global metadata for the output file.
1292 ///
1293 /// If value is empty string, the key will be removed (FFmpeg behavior).
1294 /// Replicates FFmpeg's `-metadata key=value` option.
1295 ///
1296 /// FFmpeg reference: fftools/ffmpeg_opt.c (`opt_metadata()` handles `-metadata key=value`).
1297 ///
1298 /// # Examples
1299 /// ```rust,ignore
1300 /// let output = Output::from("output.mp4")
1301 /// .add_metadata("title", "My Video")
1302 /// .add_metadata("author", "John Doe");
1303 /// ```
1304 pub fn add_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1305 let key = key.into();
1306 let value = value.into();
1307
1308 if value.is_empty() {
1309 // Empty value means remove the key (FFmpeg behavior)
1310 if let Some(ref mut metadata) = self.global_metadata {
1311 metadata.remove(&key);
1312 }
1313 } else {
1314 self.global_metadata
1315 .get_or_insert_with(HashMap::new)
1316 .insert(key, value);
1317 }
1318 self
1319 }
1320
1321 /// Add multiple global metadata entries at once.
1322 ///
1323 /// FFmpeg reference: fftools/ffmpeg_opt.c (consecutive `-metadata` invocations append to the
1324 /// same dictionary; this helper simply batches the calls on the Rust side).
1325 ///
1326 /// # Examples
1327 /// ```rust,ignore
1328 /// let mut metadata = HashMap::new();
1329 /// metadata.insert("title".to_string(), "My Video".to_string());
1330 /// metadata.insert("author".to_string(), "John Doe".to_string());
1331 ///
1332 /// let output = Output::from("output.mp4")
1333 /// .add_metadata_map(metadata);
1334 /// ```
1335 pub fn add_metadata_map(mut self, metadata: HashMap<String, String>) -> Self {
1336 for (key, value) in metadata {
1337 self = self.add_metadata(key, value);
1338 }
1339 self
1340 }
1341
1342 /// Remove a global metadata key.
1343 ///
1344 /// FFmpeg reference: fftools/ffmpeg_opt.c (`-metadata key=` deletes the key when value is
1345 /// empty; we follow the same rule by interpreting an empty string as removal).
1346 ///
1347 /// # Examples
1348 /// ```rust,ignore
1349 /// let output = Output::from("output.mp4")
1350 /// .add_metadata("title", "My Video")
1351 /// .remove_metadata("title"); // Remove the title
1352 /// ```
1353 pub fn remove_metadata(mut self, key: &str) -> Self {
1354 if let Some(ref mut metadata) = self.global_metadata {
1355 metadata.remove(key);
1356 }
1357 self
1358 }
1359
1360 /// Clear all metadata (global, stream, chapter, program) and mappings.
1361 ///
1362 /// Useful when you want to start fresh without any metadata.
1363 ///
1364 /// FFmpeg reference: fftools/ffmpeg_opt.c (users typically issue `-map_metadata -1` and then
1365 /// reapply `-metadata` options; this helper emulates that workflow programmatically).
1366 ///
1367 /// # Examples
1368 /// ```rust,ignore
1369 /// let output = Output::from("output.mp4")
1370 /// .add_metadata("title", "My Video")
1371 /// .clear_all_metadata(); // Remove all metadata
1372 /// ```
1373 pub fn clear_all_metadata(mut self) -> Self {
1374 self.global_metadata = None;
1375 self.stream_metadata.clear();
1376 self.chapter_metadata.clear();
1377 self.program_metadata.clear();
1378 self.metadata_map.clear();
1379 self
1380 }
1381
1382
1383 /// Disable automatic metadata copying from input files.
1384 ///
1385 /// By default, FFmpeg automatically copies global and stream metadata
1386 /// from input files to output. This method disables that behavior,
1387 /// similar to FFmpeg's `-map_metadata -1` option.
1388 /// FFmpeg reference: ffmpeg_mux_init.c (`copy_meta()` sets metadata_global_manual when
1389 /// `-map_metadata -1` is used; `auto_copy_metadata` mirrors the same flag).
1390 ///
1391 /// # Examples
1392 /// ```rust,ignore
1393 /// let output = Output::from("output.mp4")
1394 /// .disable_auto_copy_metadata() // Don't copy any metadata from input
1395 /// .add_metadata("title", "New Title"); // Only use explicitly set metadata
1396 /// ```
1397 pub fn disable_auto_copy_metadata(mut self) -> Self {
1398 self.auto_copy_metadata = false;
1399 self
1400 }
1401
1402 /// Add or update stream-specific metadata.
1403 ///
1404 /// Uses FFmpeg's stream specifier syntax to identify target streams.
1405 /// If value is empty string, the key will be removed (FFmpeg behavior).
1406 /// Replicates FFmpeg's `-metadata:s:spec key=value` option.
1407 ///
1408 /// FFmpeg reference: fftools/ffmpeg_opt.c (`opt_metadata()` with stream specifiers, lines
1409 /// 2465-2520 in FFmpeg 7.x).
1410 ///
1411 /// # Stream Specifier Syntax
1412 /// - `"v:0"` - First video stream
1413 /// - `"a:1"` - Second audio stream
1414 /// - `"s"` - All subtitle streams
1415 /// - `"v"` - All video streams
1416 /// - `"p:0:v"` - Video streams in program 0
1417 /// - `"#0x100"` or `"i:256"` - Stream with specific ID
1418 /// - `"m:language:eng"` - Streams with metadata language=eng
1419 /// - `"u"` - Usable streams only
1420 /// - `"disp:default"` - Streams with default disposition
1421 ///
1422 /// # Examples
1423 /// ```rust,ignore
1424 /// let output = Output::from("output.mp4")
1425 /// .add_stream_metadata("v:0", "language", "eng")
1426 /// .add_stream_metadata("a:0", "title", "Main Audio");
1427 /// ```
1428 ///
1429 /// # Errors
1430 /// Returns error if the stream specifier syntax is invalid.
1431 pub fn add_stream_metadata(
1432 mut self,
1433 stream_spec: impl Into<String>,
1434 key: impl Into<String>,
1435 value: impl Into<String>,
1436 ) -> Result<Self, String> {
1437 use crate::core::metadata::StreamSpecifier;
1438
1439 let stream_spec_str = stream_spec.into();
1440 let key = key.into();
1441 let value = value.into();
1442
1443 // Parse and validate stream specifier
1444 let _specifier = StreamSpecifier::parse(&stream_spec_str)?;
1445
1446 // Store as (spec, key, value) tuple
1447 // During output initialization, this will be matched against actual streams
1448 // using StreamSpecifier::matches and applied to all matching streams
1449 // Replicates FFmpeg's of_add_metadata behavior
1450 self.stream_metadata.push((stream_spec_str, key, value));
1451
1452 Ok(self)
1453 }
1454
1455 /// Add or update chapter-specific metadata.
1456 ///
1457 /// Chapters are used for DVD-like navigation points in media files.
1458 /// If value is empty string, the key will be removed (FFmpeg behavior).
1459 /// Replicates FFmpeg's `-metadata:c:N key=value` option.
1460 /// FFmpeg reference: fftools/ffmpeg_opt.c (`opt_metadata()` handles the `c:` target selector).
1461 ///
1462 /// # Examples
1463 /// ```rust,ignore
1464 /// let output = Output::from("output.mp4")
1465 /// .add_chapter_metadata(0, "title", "Introduction")
1466 /// .add_chapter_metadata(1, "title", "Main Content");
1467 /// ```
1468 pub fn add_chapter_metadata(
1469 mut self,
1470 chapter_index: usize,
1471 key: impl Into<String>,
1472 value: impl Into<String>,
1473 ) -> Self {
1474 let key = key.into();
1475 let value = value.into();
1476
1477 if value.is_empty() {
1478 // Empty value means remove the key (FFmpeg behavior)
1479 if let Some(metadata) = self.chapter_metadata.get_mut(&chapter_index) {
1480 metadata.remove(&key);
1481 }
1482 } else {
1483 self.chapter_metadata
1484 .entry(chapter_index)
1485 .or_default()
1486 .insert(key, value);
1487 }
1488 self
1489 }
1490
1491 /// Add or update program-specific metadata.
1492 ///
1493 /// Programs are used in multi-program transport streams (e.g., MPEG-TS).
1494 /// If value is empty string, the key will be removed (FFmpeg behavior).
1495 /// Replicates FFmpeg's `-metadata:p:N key=value` option.
1496 /// FFmpeg reference: fftools/ffmpeg_opt.c (`opt_metadata()` with `p:` selector).
1497 ///
1498 /// # Examples
1499 /// ```rust,ignore
1500 /// let output = Output::from("output.ts")
1501 /// .add_program_metadata(0, "service_name", "Channel 1")
1502 /// .add_program_metadata(1, "service_name", "Channel 2");
1503 /// ```
1504 pub fn add_program_metadata(
1505 mut self,
1506 program_index: usize,
1507 key: impl Into<String>,
1508 value: impl Into<String>,
1509 ) -> Self {
1510 let key = key.into();
1511 let value = value.into();
1512
1513 if value.is_empty() {
1514 // Empty value means remove the key (FFmpeg behavior)
1515 if let Some(metadata) = self.program_metadata.get_mut(&program_index) {
1516 metadata.remove(&key);
1517 }
1518 } else {
1519 self.program_metadata
1520 .entry(program_index)
1521 .or_default()
1522 .insert(key, value);
1523 }
1524 self
1525 }
1526
1527 /// Map metadata from an input file to this output.
1528 ///
1529 /// Replicates FFmpeg's `-map_metadata [src_file_idx]:src_type:dst_type` option.
1530 /// This allows copying metadata from specific locations in input files to
1531 /// specific locations in the output file.
1532 ///
1533 /// # Type Specifiers
1534 /// - `"g"` or `""` - Global metadata
1535 /// - `"s"` or `"s:spec"` - Stream metadata (with optional stream specifier)
1536 /// - `"c:N"` - Chapter N metadata
1537 /// - `"p:N"` - Program N metadata
1538 ///
1539 /// # Examples
1540 /// ```rust,ignore
1541 /// use ez_ffmpeg::core::metadata::{MetadataType, MetadataMapping};
1542 ///
1543 /// let output = Output::from("output.mp4")
1544 /// // Copy global metadata from input 0 to output global
1545 /// .map_metadata_from_input(0, "g", "g")?
1546 /// // Copy first video stream metadata from input 1 to output first video stream
1547 /// .map_metadata_from_input(1, "s:v:0", "s:v:0")?;
1548 /// ```
1549 ///
1550 /// # Errors
1551 /// Returns error if the type specifier syntax is invalid.
1552 /// FFmpeg reference: fftools/ffmpeg_opt.c (`opt_map_metadata()` parses the same
1553 /// `[file][:type]` triplet and feeds it into `MetadataMapping`).
1554 pub fn map_metadata_from_input(
1555 mut self,
1556 input_index: usize,
1557 src_type_spec: impl Into<String>,
1558 dst_type_spec: impl Into<String>,
1559 ) -> Result<Self, String> {
1560 use crate::core::metadata::{MetadataMapping, MetadataType};
1561
1562 let src_type = MetadataType::parse(&src_type_spec.into())?;
1563 let dst_type = MetadataType::parse(&dst_type_spec.into())?;
1564
1565 self.metadata_map.push(MetadataMapping {
1566 src_type,
1567 dst_type,
1568 input_index,
1569 });
1570
1571 Ok(self)
1572 }
1573
1574 // ========== Stream Disable & Format API Methods (P1 Features) ==========
1575 // These methods replicate FFmpeg's `-vn`, `-an`, `-sn`, `-b:v`, `-b:a`, and `-pix_fmt` options.
1576
1577 /// Disables video stream mapping (equivalent to `-vn` in FFmpeg).
1578 ///
1579 /// Video streams will be excluded from automatic stream mapping.
1580 /// This is useful when you want to extract only audio from a video file.
1581 ///
1582 /// **Equivalent FFmpeg Command:**
1583 /// ```sh
1584 /// ffmpeg -i input.mp4 -vn output.mp3
1585 /// ```
1586 ///
1587 /// # Examples
1588 /// ```rust,ignore
1589 /// // Extract audio only, no video
1590 /// let output = Output::from("output.mp3")
1591 /// .disable_video();
1592 /// ```
1593 pub fn disable_video(mut self) -> Self {
1594 self.video_disable = true;
1595 self
1596 }
1597
1598 /// Disables audio stream mapping (equivalent to `-an` in FFmpeg).
1599 ///
1600 /// Audio streams will be excluded from automatic stream mapping.
1601 /// This is useful when you want to create a silent video.
1602 ///
1603 /// **Equivalent FFmpeg Command:**
1604 /// ```sh
1605 /// ffmpeg -i input.mp4 -an output.mp4
1606 /// ```
1607 ///
1608 /// # Examples
1609 /// ```rust,ignore
1610 /// // Create video without audio
1611 /// let output = Output::from("output.mp4")
1612 /// .disable_audio();
1613 /// ```
1614 pub fn disable_audio(mut self) -> Self {
1615 self.audio_disable = true;
1616 self
1617 }
1618
1619 /// Disables subtitle stream mapping (equivalent to `-sn` in FFmpeg).
1620 ///
1621 /// Subtitle streams will be excluded from automatic stream mapping.
1622 ///
1623 /// **Equivalent FFmpeg Command:**
1624 /// ```sh
1625 /// ffmpeg -i input.mkv -sn output.mkv
1626 /// ```
1627 ///
1628 /// # Examples
1629 /// ```rust,ignore
1630 /// // Copy video and audio, but exclude subtitles
1631 /// let output = Output::from("output.mkv")
1632 /// .disable_subtitle();
1633 /// ```
1634 pub fn disable_subtitle(mut self) -> Self {
1635 self.subtitle_disable = true;
1636 self
1637 }
1638
1639 /// Disables data stream mapping (equivalent to `-dn` in FFmpeg).
1640 ///
1641 /// Data streams (timed metadata, chapter markers, etc.) will be
1642 /// excluded from automatic stream mapping.
1643 ///
1644 /// **Equivalent FFmpeg Command:**
1645 /// ```sh
1646 /// ffmpeg -i input.mkv -dn output.mp4
1647 /// ```
1648 ///
1649 /// # Examples
1650 /// ```rust,ignore
1651 /// // Copy video and audio, but exclude data streams
1652 /// let output = Output::from("output.mp4")
1653 /// .disable_data();
1654 /// ```
1655 pub fn disable_data(mut self) -> Self {
1656 self.data_disable = true;
1657 self
1658 }
1659
1660 /// Sets the video bitrate (equivalent to `-b:v` in FFmpeg).
1661 ///
1662 /// The bitrate string follows FFmpeg conventions:
1663 /// - `"1M"` or `"1000k"` for 1 Mbps
1664 /// - `"500k"` for 500 Kbps
1665 /// - `"2M"` for 2 Mbps
1666 ///
1667 /// **Equivalent FFmpeg Command:**
1668 /// ```sh
1669 /// ffmpeg -i input.mp4 -b:v 2M output.mp4
1670 /// ```
1671 ///
1672 /// # Examples
1673 /// ```rust,ignore
1674 /// let output = Output::from("output.mp4")
1675 /// .set_video_bitrate("2M");
1676 /// ```
1677 pub fn set_video_bitrate(self, bitrate: impl Into<String>) -> Self {
1678 self.set_video_codec_opt("b", bitrate)
1679 }
1680
1681 /// Sets the audio bitrate (equivalent to `-b:a` in FFmpeg).
1682 ///
1683 /// The bitrate string follows FFmpeg conventions:
1684 /// - `"128k"` for 128 Kbps
1685 /// - `"192k"` for 192 Kbps
1686 /// - `"320k"` for 320 Kbps
1687 ///
1688 /// **Equivalent FFmpeg Command:**
1689 /// ```sh
1690 /// ffmpeg -i input.mp4 -b:a 192k output.mp4
1691 /// ```
1692 ///
1693 /// # Examples
1694 /// ```rust,ignore
1695 /// let output = Output::from("output.mp4")
1696 /// .set_audio_bitrate("192k");
1697 /// ```
1698 pub fn set_audio_bitrate(self, bitrate: impl Into<String>) -> Self {
1699 self.set_audio_codec_opt("b", bitrate)
1700 }
1701
1702 /// Sets the output pixel format (equivalent to `-pix_fmt` in FFmpeg).
1703 ///
1704 /// Common pixel formats include:
1705 /// - `"yuv420p"` - Most compatible format for H.264
1706 /// - `"yuv444p"` - Higher quality, less compatible
1707 /// - `"rgb24"` - RGB format
1708 /// - `"nv12"` - Common for hardware encoding
1709 ///
1710 /// To see all available formats, run: `ffmpeg -pix_fmts`
1711 ///
1712 /// # Behavior
1713 ///
1714 /// - **Unknown format name**: Returns [`OpenOutputError::UnknownPixelFormat`] error.
1715 /// This matches FFmpeg CLI behavior (e.g., `ffmpeg -pix_fmt foobar` also fails).
1716 /// - **Format incompatible with encoder**: The filter graph automatically converts
1717 /// to a compatible format. For example, specifying `rgb48be` with libx264 will
1718 /// auto-convert to `yuv420p`.
1719 /// - **Stream copy mode**: This setting has no effect when using `-c:v copy`.
1720 ///
1721 /// **Equivalent FFmpeg Command:**
1722 /// ```sh
1723 /// ffmpeg -i input.mp4 -pix_fmt yuv420p output.mp4
1724 /// ```
1725 ///
1726 /// # Examples
1727 /// ```rust,ignore
1728 /// let output = Output::from("output.mp4")
1729 /// .set_pix_fmt("yuv420p");
1730 /// ```
1731 ///
1732 /// [`OpenOutputError::UnknownPixelFormat`]: crate::error::OpenOutputError::UnknownPixelFormat
1733 pub fn set_pix_fmt(mut self, pix_fmt: impl Into<String>) -> Self {
1734 self.pix_fmt = Some(pix_fmt.into());
1735 self
1736 }
1737}
1738
1739impl From<Box<dyn FnMut(&[u8]) -> i32 + Send>> for Output {
1740 fn from(write_callback_and_format: Box<dyn FnMut(&[u8]) -> i32 + Send>) -> Self {
1741 Self {
1742 url: None,
1743 write_callback: Some(write_callback_and_format),
1744 seek_callback: None,
1745 frame_pipelines: None,
1746 stream_map_specs: vec![],
1747 stream_maps: vec![],
1748 format: None,
1749 video_codec: None,
1750 audio_codec: None,
1751 subtitle_codec: None,
1752 start_time_us: None,
1753 recording_time_us: None,
1754 stop_time_us: None,
1755 framerate: None,
1756 vsync_method: VSyncMethod::VsyncAuto,
1757 bits_per_raw_sample: None,
1758 audio_sample_rate: None,
1759 audio_channels: None,
1760 audio_sample_fmt: None,
1761 video_qscale: None,
1762 audio_qscale: None,
1763 max_video_frames: None,
1764 max_audio_frames: None,
1765 max_subtitle_frames: None,
1766 video_codec_opts: None,
1767 audio_codec_opts: None,
1768 subtitle_codec_opts: None,
1769 format_opts: None,
1770 // Metadata fields - initialized with defaults
1771 global_metadata: None,
1772 stream_metadata: Vec::new(),
1773 chapter_metadata: HashMap::new(),
1774 program_metadata: HashMap::new(),
1775 metadata_map: Vec::new(),
1776 auto_copy_metadata: true, // FFmpeg default: auto-copy enabled
1777 // Stream disable flags - initialized to false (all streams enabled)
1778 video_disable: false,
1779 audio_disable: false,
1780 subtitle_disable: false,
1781 data_disable: false,
1782 pix_fmt: None,
1783 }
1784 }
1785}
1786
1787impl From<String> for Output {
1788 fn from(url: String) -> Self {
1789 Self {
1790 url: Some(url),
1791 write_callback: None,
1792 seek_callback: None,
1793 frame_pipelines: None,
1794 stream_map_specs: vec![],
1795 stream_maps: vec![],
1796 format: None,
1797 video_codec: None,
1798 audio_codec: None,
1799 subtitle_codec: None,
1800 start_time_us: None,
1801 recording_time_us: None,
1802 stop_time_us: None,
1803 framerate: None,
1804 vsync_method: VSyncMethod::VsyncAuto,
1805 bits_per_raw_sample: None,
1806 audio_sample_rate: None,
1807 audio_channels: None,
1808 audio_sample_fmt: None,
1809 video_qscale: None,
1810 audio_qscale: None,
1811 max_video_frames: None,
1812 max_audio_frames: None,
1813 max_subtitle_frames: None,
1814 video_codec_opts: None,
1815 audio_codec_opts: None,
1816 subtitle_codec_opts: None,
1817 format_opts: None,
1818 // Metadata fields - initialized with defaults
1819 global_metadata: None,
1820 stream_metadata: Vec::new(),
1821 chapter_metadata: HashMap::new(),
1822 program_metadata: HashMap::new(),
1823 metadata_map: Vec::new(),
1824 auto_copy_metadata: true, // FFmpeg default: auto-copy enabled
1825 // Stream disable flags - initialized to false (all streams enabled)
1826 video_disable: false,
1827 audio_disable: false,
1828 subtitle_disable: false,
1829 data_disable: false,
1830 pix_fmt: None,
1831 }
1832 }
1833}
1834
1835impl From<&str> for Output {
1836 fn from(url: &str) -> Self {
1837 Self::from(String::from(url))
1838 }
1839}
1840
1841/// Temporary storage for unparsed stream map specifications (user input stage)
1842/// Equivalent to FFmpeg's command-line parsing before opt_map() expansion
1843#[derive(Debug, Clone)]
1844pub(crate) struct StreamMapSpec {
1845 /// Stream specifier string: "0:v", "1:a:0", "0:v?", "[label]", etc.
1846 pub(crate) linklabel: String,
1847 /// Stream copy flag (-c copy)
1848 pub(crate) copy: bool,
1849}
1850
1851impl<T: Into<String>> From<T> for StreamMapSpec {
1852 fn from(linklabel: T) -> Self {
1853 Self {
1854 linklabel: linklabel.into(),
1855 copy: false,
1856 }
1857 }
1858}
1859
1860/// Final expanded stream map (matches FFmpeg's StreamMap structure)
1861/// Created after parsing and expansion in outputs_bind()
1862/// FFmpeg reference: fftools/ffmpeg.h:134-141
1863#[derive(Debug, Clone)]
1864pub(crate) struct StreamMap {
1865 /// 1 if this mapping is disabled by a negative map (-map -0:v)
1866 pub(crate) disabled: bool,
1867 /// Input file index
1868 pub(crate) file_index: usize,
1869 /// Input stream index within the file
1870 pub(crate) stream_index: usize,
1871 /// Name of an output link, for mapping lavfi outputs (e.g., "[v]", "myout")
1872 pub(crate) linklabel: Option<String>,
1873 /// Stream copy flag (-c copy)
1874 pub(crate) copy: bool,
1875}