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