Skip to main content

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}