ez_ffmpeg/core/context/
output.rs

1use std::collections::HashMap;
2use crate::core::filter::frame_pipeline_builder::FramePipelineBuilder;
3use ffmpeg_sys_next::{AVRational, AVSampleFormat};
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
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
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<FramePipelineBuilder>>,
157
158    pub(crate) stream_maps: Vec<StreamMap>,
159
160    /// The output format for the container.
161    ///
162    /// This field specifies the desired output format, such as `mp4`, `flv`, or `mkv`. If `None`, FFmpeg
163    /// will attempt to automatically detect the format based on the output URL or filename extension.
164    ///
165    /// The format can be specified explicitly for scenarios where the format detection is insufficient or
166    /// where you want to force a particular container format regardless of the URL or extension.
167    pub(crate) format: Option<String>,
168
169    /// The codec to be used for **video** encoding.
170    ///
171    /// If this field is `None`, FFmpeg will try to select an appropriate video codec based on the
172    /// output format or other settings. By setting this field to a specific codec (e.g., `"h264"`, `"hevc"`, etc.),
173    /// you can override FFmpeg’s default codec selection. If the specified codec is not available
174    /// in your FFmpeg build, an error will be returned during initialization.
175    pub(crate) video_codec: Option<String>,
176
177    /// The codec to be used for **audio** encoding.
178    ///
179    /// If this field is `None`, FFmpeg will try to select an appropriate audio codec based on the
180    /// output format or other settings. By providing a value (e.g., `"aac"`, `"mp3"`, etc.),
181    /// you override FFmpeg’s default codec choice. If the specified codec is not available
182    /// in your FFmpeg build, an error will be returned during initialization.
183    pub(crate) audio_codec: Option<String>,
184
185    /// The codec to be used for **subtitle** encoding.
186    ///
187    /// If this field is `None`, FFmpeg will try to select an appropriate subtitle codec based on
188    /// the output format or other settings. Setting this field (e.g., `"mov_text"` for MP4 subtitles)
189    /// forces FFmpeg to use the specified codec. If the chosen codec is not supported by your build of FFmpeg,
190    /// an error will be returned during initialization.
191    pub(crate) subtitle_codec: Option<String>,
192    pub(crate) start_time_us: Option<i64>,
193    pub(crate) recording_time_us: Option<i64>,
194    pub(crate) stop_time_us: Option<i64>,
195    pub(crate) framerate: Option<AVRational>,
196    pub(crate) vsync_method: Option<VSyncMethod>,
197    pub(crate) bits_per_raw_sample: Option<i32>,
198    pub(crate) audio_sample_rate: Option<i32>,
199    pub(crate) audio_channels: Option<i32>,
200    pub(crate) audio_sample_fmt: Option<AVSampleFormat>,
201
202    // -q:v
203    // use fixed quality scale (VBR)
204    pub(crate) video_qscale: Option<i32>,
205
206    // -q:a
207    // set audio quality (codec-specific)
208    pub(crate) audio_qscale: Option<i32>,
209
210    /// Maximum number of **video** frames to encode (equivalent to `-frames:v` in FFmpeg).
211    ///
212    /// This option limits the number of **video** frames processed by the encoder.
213    ///
214    /// **Equivalent FFmpeg Command:**
215    /// ```sh
216    /// ffmpeg -i input.mp4 -frames:v 100 output.mp4
217    /// ```
218    ///
219    /// **Example Usage:**
220    /// ```rust
221    /// let output = Output::from("some_url")
222    ///     .set_max_video_frames(300);
223    /// ```
224    pub(crate) max_video_frames: Option<i64>,
225
226    /// Maximum number of **audio** frames to encode (equivalent to `-frames:a` in FFmpeg).
227    ///
228    /// This option limits the number of **audio** frames processed by the encoder.
229    ///
230    /// **Equivalent FFmpeg Command:**
231    /// ```sh
232    /// ffmpeg -i input.mp4 -frames:a 500 output.mp4
233    /// ```
234    ///
235    /// **Example Usage:**
236    /// ```rust
237    /// let output = Output::from("some_url")
238    ///     .set_max_audio_frames(500);
239    /// ```
240    pub(crate) max_audio_frames: Option<i64>,
241
242    /// Maximum number of **subtitle** frames to encode (equivalent to `-frames:s` in FFmpeg).
243    ///
244    /// This option limits the number of **subtitle** frames processed by the encoder.
245    ///
246    /// **Equivalent FFmpeg Command:**
247    /// ```sh
248    /// ffmpeg -i input.mp4 -frames:s 200 output.mp4
249    /// ```
250    ///
251    /// **Example Usage:**
252    /// ```rust
253    /// let output = Output::from("some_url")
254    ///     .set_max_subtitle_frames(200);
255    /// ```
256    pub(crate) max_subtitle_frames: Option<i64>,
257
258    /// Video encoder-specific options.
259    ///
260    /// This field stores key-value pairs for configuring the **video encoder**.
261    /// These options are passed to the video encoder before encoding begins.
262    ///
263    /// **Common Examples:**
264    /// - `crf=0` (for lossless quality in x264/x265)
265    /// - `preset=ultrafast` (for faster encoding speed in H.264)
266    /// - `tune=zerolatency` (for real-time streaming)
267    pub(crate) video_codec_opts: Option<HashMap<String, String>>,
268
269    /// Audio encoder-specific options.
270    ///
271    /// This field stores key-value pairs for configuring the **audio encoder**.
272    /// These options are passed to the audio encoder before encoding begins.
273    ///
274    /// **Common Examples:**
275    /// - `b=192k` (for setting bitrate in AAC/MP3)
276    /// - `ar=44100` (for setting sample rate)
277    pub(crate) audio_codec_opts: Option<HashMap<String, String>>,
278
279    /// Subtitle encoder-specific options.
280    ///
281    /// This field stores key-value pairs for configuring the **subtitle encoder**.
282    /// These options are passed to the subtitle encoder before encoding begins.
283    ///
284    /// **Common Examples:**
285    /// - `mov_text` (for MP4 subtitles)
286    /// - `srt` (for subtitle format)
287    pub(crate) subtitle_codec_opts: Option<HashMap<String, String>>,
288
289    /// The output format options for the container.
290    ///
291    /// This field stores additional format-specific options that are passed to the FFmpeg muxer.
292    /// It is a collection of key-value pairs that can modify the behavior of the output format.
293    ///
294    /// Common examples include:
295    /// - `movflags=faststart` (for MP4 files)
296    /// - `flvflags=no_duration_filesize` (for FLV files)
297    ///
298    /// These options are used when initializing the FFmpeg output format.
299    ///
300    /// **Example Usage:**
301    /// ```rust
302    /// let output = Output::from("some_url")
303    ///     .set_format_opt("movflags", "faststart");
304    /// ```
305    pub(crate) format_opts: Option<HashMap<String, String>>,
306
307}
308
309#[derive(Copy, Clone, PartialEq)]
310pub enum VSyncMethod {
311    VsyncAuto,
312    VsyncCfr,
313    VsyncVfr,
314    VsyncPassthrough,
315    VsyncVscfr,
316}
317
318impl Output {
319    pub fn new(url: impl Into<String>) -> Self {
320        url.into().into()
321    }
322
323    /// Creates a new `Output` instance with a custom write callback and format string.
324    ///
325    /// This method initializes an `Output` object that uses a provided `write_callback` function
326    /// to handle the encoded data being written to the output stream. You can optionally specify
327    /// the desired output format via the `format` method.
328    ///
329    /// ### Parameters:
330    /// - `write_callback: fn(buf: &[u8]) -> i32`: A function that processes the provided buffer of
331    ///   encoded data and writes it to the destination. The function should return the number of bytes
332    ///   successfully written (positive value) or a negative value in case of error.
333    ///
334    /// ### Return Value:
335    /// - Returns a new `Output` instance configured with the specified `write_callback` function.
336    ///
337    /// ### Behavior of `write_callback`:
338    /// - **Positive Value**: Indicates the number of bytes successfully written.
339    /// - **Negative Value**: Indicates an error occurred. For example:
340    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: Represents an input/output error.
341    ///   - Other custom-defined error codes can also be returned to signal specific issues.
342    ///
343    /// ### Example:
344    /// ```rust
345    /// let output = Output::new_by_write_callback(move |buf| {
346    ///     println!("Processing {} bytes of data for output", buf.len());
347    ///     buf.len() as i32 // Return the number of bytes processed
348    /// })
349    /// .set_format("mp4");
350    /// ```
351    pub fn new_by_write_callback<F>(write_callback: F) -> Self
352    where
353        F: FnMut(&[u8]) -> i32 + 'static,
354    {
355        (Box::new(write_callback) as Box<dyn FnMut(&[u8]) -> i32>).into()
356    }
357
358    /// Sets a custom seek callback for the output stream.
359    ///
360    /// This function assigns a user-defined function that handles seeking within the output stream.
361    /// Seeking is required for certain formats (e.g., `mp4`, `mkv`) where metadata or index information
362    /// needs to be updated at specific positions in the file.
363    ///
364    /// **Why is `seek_callback` necessary?**
365    /// - Some formats (e.g., MP4) require `seek` operations to update metadata (`moov`, `mdat`).
366    /// - If no `seek_callback` is provided for formats that require seeking, FFmpeg will fail with:
367    ///   ```text
368    ///   [mp4 @ 0x...] muxer does not support non seekable output
369    ///   ```
370    /// - For streaming formats (`flv`, `ts`, `rtmp`, `hls`), seeking is **not required**.
371    ///
372    /// **FFmpeg may invoke `seek_callback` from different threads.**
373    /// - If using a `File` as the output, **wrap it in `Arc<Mutex<File>>`** to ensure thread-safe access.
374    ///
375    /// ### Parameters:
376    /// - `seek_callback: FnMut(i64, i32) -> i64`
377    ///   - `offset: i64`: The target seek position in the stream.
378    ///   - `whence: i32`: The seek mode determining how `offset` should be interpreted:
379    ///     - `ffmpeg_sys_next::SEEK_SET` (0): Seek to an absolute position.
380    ///     - `ffmpeg_sys_next::SEEK_CUR` (1): Seek relative to the current position.
381    ///     - `ffmpeg_sys_next::SEEK_END` (2): Seek relative to the end of the output.
382    ///     - `ffmpeg_sys_next::AVSEEK_FLAG_BYTE` (2): Seek using **byte offset** instead of timestamps.
383    ///     - `ffmpeg_sys_next::AVSEEK_SIZE` (65536): Query the **total size** of the stream.
384    ///     - `ffmpeg_sys_next::AVSEEK_FORCE` (131072): **Force seeking, even if normally restricted.**
385    ///
386    /// ### Return Value:
387    /// - **Positive Value**: The new offset position after seeking.
388    /// - **Negative Value**: An error occurred. Common errors include:
389    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE)`: Seek is not supported.
390    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
391    ///
392    /// ### Example (Thread-safe seek callback using `Arc<Mutex<File>>`):
393    /// Since `FFmpeg` may call `write_callback` and `seek_callback` from different threads,
394    /// **use `Arc<Mutex<File>>` to ensure safe concurrent access.**
395    ///
396    /// ```rust
397    /// use std::fs::File;
398    /// use std::io::{Seek, SeekFrom, Write};
399    /// use std::sync::{Arc, Mutex};
400    ///
401    /// // ✅ Create a thread-safe file handle
402    /// let file = Arc::new(Mutex::new(File::create("output.mp4").expect("Failed to create file")));
403    ///
404    /// // ✅ Define the write callback (data writing logic)
405    /// let write_callback = {
406    ///     let file = Arc::clone(&file);
407    ///     move |buf: &[u8]| -> i32 {
408    ///         let mut file = file.lock().unwrap();
409    ///         match file.write_all(buf) {
410    ///             Ok(_) => buf.len() as i32,
411    ///             Err(e) => {
412    ///                 println!("Write error: {}", e);
413    ///                 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i32
414    ///             }
415    ///         }
416    ///     }
417    /// };
418    ///
419    /// // ✅ Define the seek callback (position adjustment logic)
420    /// let seek_callback = {
421    ///     let file = Arc::clone(&file);
422    ///     Box::new(move |offset: i64, whence: i32| -> i64 {
423    ///         let mut file = file.lock().unwrap();
424    ///
425    ///         match whence {
426    ///             // ✅ Handle AVSEEK_SIZE: Return total file size
427    ///             ffmpeg_sys_next::AVSEEK_SIZE => {
428    ///                 if let Ok(size) = file.metadata().map(|m| m.len() as i64) {
429    ///                     println!("FFmpeg requested stream size: {}", size);
430    ///                     return size;
431    ///                 }
432    ///                 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
433    ///             }
434    ///
435    ///             // ✅ Handle AVSEEK_FLAG_BYTE: Seek using byte offset
436    ///             ffmpeg_sys_next::AVSEEK_FLAG_BYTE => {
437    ///                 println!("FFmpeg requested byte-based seeking. Seeking to byte offset: {}", offset);
438    ///                 if let Ok(new_pos) = file.seek(SeekFrom::Start(offset as u64)) {
439    ///                     return new_pos as i64;
440    ///                 }
441    ///                 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
442    ///             }
443    ///
444    ///             // ✅ Standard seek modes
445    ///             ffmpeg_sys_next::SEEK_SET => file.seek(SeekFrom::Start(offset as u64)),
446    ///             ffmpeg_sys_next::SEEK_CUR => file.seek(SeekFrom::Current(offset)),
447    ///             ffmpeg_sys_next::SEEK_END => file.seek(SeekFrom::End(offset)),
448    ///             _ => Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Unsupported seek mode")),
449    ///         }.map_or(ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64, |pos| pos as i64)
450    ///     })
451    /// };
452    ///
453    /// // ✅ Create an output with both callbacks
454    /// let output = Output::new_by_write_callback(write_callback, "mp4")
455    ///     .set_seek_callback(seek_callback);
456    /// ```
457    pub fn set_seek_callback<F>(mut self, seek_callback: F) -> Self
458    where
459        F: FnMut(i64, i32) -> i64 + 'static,
460    {
461        self.seek_callback = Some(Box::new(seek_callback) as Box<dyn FnMut(i64, i32) -> i64>);
462        self
463    }
464
465    /// Sets the output format for the container.
466    ///
467    /// This method allows you to specify the output format for the container. If no format is specified,
468    /// FFmpeg will attempt to detect it automatically based on the file extension or output URL.
469    ///
470    /// ### Parameters:
471    /// - `format: &str`: A string specifying the desired output format (e.g., `mp4`, `flv`, `mkv`).
472    ///
473    /// ### Return Value:
474    /// - Returns the `Output` instance with the newly set format.
475    pub fn set_format(mut self, format: impl Into<String>) -> Self {
476        self.format = Some(format.into());
477        self
478    }
479
480    /// Sets the **video codec** to be used for encoding.
481    ///
482    /// # Arguments
483    /// * `video_codec` - A string slice representing the desired video codec (e.g., `"h264"`, `"hevc"`).
484    ///
485    /// # Returns
486    /// * `Self` - Returns the modified `Output` struct, allowing for method chaining.
487    ///
488    /// # Examples
489    /// ```rust
490    /// let output = Output::from("rtmp://localhost/live/stream")
491    ///     .set_video_codec("h264");
492    /// ```
493    pub fn set_video_codec(mut self, video_codec: impl Into<String>) -> Self {
494        self.video_codec = Some(video_codec.into());
495        self
496    }
497
498    /// Sets the **audio codec** to be used for encoding.
499    ///
500    /// # Arguments
501    /// * `audio_codec` - A string slice representing the desired audio codec (e.g., `"aac"`, `"mp3"`).
502    ///
503    /// # Returns
504    /// * `Self` - Returns the modified `Output` struct, allowing for method chaining.
505    ///
506    /// # Examples
507    /// ```rust
508    /// let output = Output::from("rtmp://localhost/live/stream")
509    ///     .set_audio_codec("aac");
510    /// ```
511    pub fn set_audio_codec(mut self, audio_codec: impl Into<String>) -> Self {
512        self.audio_codec = Some(audio_codec.into());
513        self
514    }
515
516    /// Sets the **subtitle codec** to be used for encoding.
517    ///
518    /// # Arguments
519    /// * `subtitle_codec` - A string slice representing the desired subtitle codec
520    ///   (e.g., `"mov_text"`, `"webvtt"`).
521    ///
522    /// # Returns
523    /// * `Self` - Returns the modified `Output` struct, allowing for method chaining.
524    ///
525    /// # Examples
526    /// ```rust
527    /// let output = Output::from("rtmp://localhost/live/stream")
528    ///     .set_subtitle_codec("mov_text");
529    /// ```
530    pub fn set_subtitle_codec(mut self, subtitle_codec: impl Into<String>) -> Self {
531        self.subtitle_codec = Some(subtitle_codec.into());
532        self
533    }
534
535    /// Replaces the entire frame-processing pipeline with a new sequence
536    /// of transformations for **pre-encoding** frames on this `Output`.
537    ///
538    /// This method clears any previously set pipelines and replaces them with the provided list.
539    ///
540    /// **Note:** This method accepts [`FramePipelineBuilder`] instead of [`FramePipeline`](crate::core::filter::frame_pipeline::FramePipeline).
541    /// For details on why [`FramePipelineBuilder`] is used, see its documentation.
542    ///
543    /// # Parameters
544    /// * `frame_pipelines` - A list of [`FramePipelineBuilder`] instances defining the
545    ///   transformations to apply before encoding.
546    ///
547    /// # Returns
548    /// * `Self` - Returns the modified `Output`, enabling method chaining.
549    ///
550    /// # Example
551    /// ```rust
552    /// let output = Output::from("some_url")
553    ///     .set_frame_pipelines(vec![
554    ///         FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_VIDEO).filter("opengl", Box::new(my_filter)),
555    ///         // Additional pipelines...
556    ///     ]);
557    /// ```
558    pub fn set_frame_pipelines(mut self, frame_pipelines: Vec<FramePipelineBuilder>) -> Self {
559        self.frame_pipelines = Some(frame_pipelines);
560        self
561    }
562
563    /// Adds a single [`FramePipelineBuilder`] to the existing pipeline list.
564    ///
565    /// If no pipelines are currently defined, this method creates a new pipeline list.
566    /// Otherwise, it appends the provided pipeline to the existing transformations.
567    ///
568    /// **Note:** This method accepts [`FramePipelineBuilder`] instead of [`FramePipeline`](crate::core::filter::frame_pipeline::FramePipeline).
569    /// For details on why [`FramePipelineBuilder`] is used, see its documentation.
570    ///
571    /// # Parameters
572    /// * `frame_pipeline` - A [`FramePipelineBuilder`] defining a transformation.
573    ///
574    /// # Returns
575    /// * `Self` - Returns the modified `Output`, enabling method chaining.
576    ///
577    /// # Example
578    /// ```rust
579    /// let output = Output::from("some_url")
580    ///     .add_frame_pipeline(FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_VIDEO).filter("opengl", Box::new(my_filter)))
581    ///     .add_frame_pipeline(FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_AUDIO).filter("my_custom_filter1", Box::new(...)).filter("my_custom_filter2", Box::new(...)));
582    /// ```
583    pub fn add_frame_pipeline(mut self, frame_pipeline: FramePipelineBuilder) -> Self {
584        if self.frame_pipelines.is_none() {
585            self.frame_pipelines = Some(vec![frame_pipeline]);
586        } else {
587            self.frame_pipelines
588                .as_mut()
589                .unwrap()
590                .push(frame_pipeline);
591        }
592        self
593    }
594
595    /// Adds a **stream mapping** for a specific stream or stream type,
596    /// **re-encoding** it according to this output’s codec settings.
597    ///
598    /// # Linklabel (FFmpeg-like Specifier)
599    ///
600    /// This string typically follows `"<input_index>:<media_type>"` syntax:
601    /// - **`"0:v"`** – the video stream(s) from input #0.
602    /// - **`"1:a?"`** – audio from input #1, **ignore** if none present (due to `?`).
603    /// - Other possibilities include `"0:s"`, `"0:d"`, etc. for subtitles/data, optionally with `?`.
604    ///
605    /// By calling `add_stream_map`, **you force re-encoding** of the chosen stream(s).
606    /// If the user wants a bit-for-bit copy, see [`add_stream_map_with_copy`](Self::add_stream_map_with_copy).
607    ///
608    /// # Parameters
609    /// - `linklabel`: An FFmpeg-style specifier referencing the desired input index and
610    ///   media type, like `"0:v"`, `"1:a?"`, etc.
611    ///
612    /// # Returns
613    /// * `Self` - for chained method calls.
614    ///
615    /// # Example
616    /// ```rust
617    /// // Re-encode the video stream from input #0 (fail if no video).
618    /// let output = Output::from("output.mp4")
619    ///     .add_stream_map("0:v");
620    /// ```
621    pub fn add_stream_map(mut self, linklabel: impl Into<String>) -> Self {
622        self.stream_maps.push(linklabel.into().into());
623        self
624    }
625
626    /// Adds a **stream mapping** for a specific stream or stream type,
627    /// **copying** it bit-for-bit from the source without re-encoding.
628    ///
629    /// # Linklabel (FFmpeg-like Specifier)
630    ///
631    /// Follows the same `"<input_index>:<media_type>"` pattern as [`add_stream_map`](Self::add_stream_map):
632    /// - **`"0:a"`** – audio stream(s) from input #0.
633    /// - **`"0:a?"`** – same, but ignore errors if no audio exists.
634    /// - And so on for video (`v`), subtitles (`s`), attachments (`t`), etc.
635    ///
636    /// # Copy vs. Re-encode
637    ///
638    /// Here, `copy = true` by default, meaning the chosen stream(s) are passed through
639    /// **without** decoding/encoding. This generally **only** works if the source’s codec
640    /// is compatible with the container/format you’re outputting to.
641    /// If you require re-encoding (e.g., to ensure compatibility or apply filters),
642    /// use [`add_stream_map`](Self::add_stream_map).
643    ///
644    /// # Parameters
645    /// - `linklabel`: An FFmpeg-style specifier referencing the desired input index and
646    ///   media type, like `"0:v?"`.
647    ///
648    /// # Returns
649    /// * `Self` - for chained method calls.
650    ///
651    /// # Example
652    /// ```rust
653    /// // Copy the audio stream(s) from input #0 if present, no re-encode:
654    /// let output = Output::from("output.mkv")
655    ///     .add_stream_map_with_copy("0:a?");
656    /// ```
657    pub fn add_stream_map_with_copy(mut self, linklabel: impl Into<String>) -> Self {
658        self.stream_maps.push(StreamMap {
659            linklabel: linklabel.into(),
660            copy: true,
661        });
662        self
663    }
664
665    /// Sets the **start time** (in microseconds) for output encoding.
666    ///
667    /// If this is set, FFmpeg will attempt to start encoding from the specified
668    /// timestamp in the input stream. This can be used to skip initial content.
669    ///
670    /// # Parameters
671    /// * `start_time_us` - The start time in microseconds.
672    ///
673    /// # Returns
674    /// * `Self` - The modified `Output`, allowing method chaining.
675    ///
676    /// # Example
677    /// ```rust
678    /// let output = Output::from("output.mp4")
679    ///     .set_start_time_us(2_000_000); // Start at 2 seconds
680    /// ```
681    pub fn set_start_time_us(mut self, start_time_us: i64) -> Self {
682        self.start_time_us = Some(start_time_us);
683        self
684    }
685
686    /// Sets the **recording time** (in microseconds) for output encoding.
687    ///
688    /// This indicates how many microseconds of data should be processed
689    /// (i.e., maximum duration to encode). Once this time is reached,
690    /// FFmpeg will stop encoding.
691    ///
692    /// # Parameters
693    /// * `recording_time_us` - The maximum duration (in microseconds) to process.
694    ///
695    /// # Returns
696    /// * `Self` - The modified `Output`, allowing method chaining.
697    ///
698    /// # Example
699    /// ```rust
700    /// let output = Output::from("output.mp4")
701    ///     .set_recording_time_us(5_000_000); // Record for 5 seconds
702    /// ```
703    pub fn set_recording_time_us(mut self, recording_time_us: i64) -> Self {
704        self.recording_time_us = Some(recording_time_us);
705        self
706    }
707
708    /// Sets a **stop time** (in microseconds) for output encoding.
709    ///
710    /// If set, FFmpeg will stop encoding once the input’s timestamp
711    /// surpasses this value. Effectively, encoding ends at this timestamp
712    /// regardless of remaining data.
713    ///
714    /// # Parameters
715    /// * `stop_time_us` - The timestamp (in microseconds) at which to stop.
716    ///
717    /// # Returns
718    /// * `Self` - The modified `Output`, allowing method chaining.
719    ///
720    /// # Example
721    /// ```rust
722    /// let output = Output::from("output.mp4")
723    ///     .set_stop_time_us(10_000_000); // Stop at 10 seconds
724    /// ```
725    pub fn set_stop_time_us(mut self, stop_time_us: i64) -> Self {
726        self.stop_time_us = Some(stop_time_us);
727        self
728    }
729
730    /// Sets a **target frame rate** (`AVRational`) for output encoding.
731    ///
732    /// This can force the output to use a specific frame rate (e.g., 30/1 for 30 FPS).
733    /// If unset, FFmpeg typically preserves the source frame rate or uses defaults
734    /// based on the selected codec/container.
735    ///
736    /// # Parameters
737    /// * `framerate` - An `AVRational` representing the desired frame rate
738    ///   numerator/denominator (e.g., `AVRational { num: 30, den: 1 }` for 30fps).
739    ///
740    /// # Returns
741    /// * `Self` - The modified `Output`, allowing method chaining.
742    ///
743    /// # Example
744    /// ```rust
745    /// use ffmpeg_sys_next::AVRational;
746    /// let output = Output::from("output.mp4")
747    ///     .set_framerate(AVRational { num: 30, den: 1 });
748    /// ```
749    pub fn set_framerate(mut self, framerate: AVRational) -> Self {
750        self.framerate = Some(framerate);
751        self
752    }
753
754    /// Sets the **video sync method** to be used during encoding.
755    ///
756    /// FFmpeg uses a variety of vsync policies to handle frame presentation times,
757    /// dropping/duplicating frames as needed. Adjusting this can be useful when
758    /// you need strict CFR (constant frame rate), or to pass frames through
759    /// without modification (`VsyncPassthrough`).
760    ///
761    /// # Parameters
762    /// * `method` - A variant of [`VSyncMethod`], such as `VsyncCfr` or `VsyncVfr`.
763    ///
764    /// # Returns
765    /// * `Self` - The modified `Output`, allowing method chaining.
766    ///
767    /// # Example
768    /// ```rust
769    /// let output = Output::from("output.mp4")
770    ///     .set_vsync_method(VSyncMethod::VsyncCfr);
771    /// ```
772    pub fn set_vsync_method(mut self, method: VSyncMethod) -> Self {
773        self.vsync_method = Some(method);
774        self
775    }
776
777    /// Sets the **bits per raw sample** for video encoding.
778    ///
779    /// This value can influence quality or color depth when dealing with
780    /// certain pixel formats. Commonly used for high-bit-depth workflows
781    /// or specialized encoding scenarios.
782    ///
783    /// # Parameters
784    /// * `bits` - The bits per raw sample (e.g., 8, 10, 12).
785    ///
786    /// # Returns
787    /// * `Self` - The modified `Output`, allowing method chaining.
788    ///
789    /// # Example
790    /// ```rust
791    /// let output = Output::from("output.mkv")
792    ///     .set_bits_per_raw_sample(10); // e.g., 10-bit
793    /// ```
794    pub fn set_bits_per_raw_sample(mut self, bits: i32) -> Self {
795        self.bits_per_raw_sample = Some(bits);
796        self
797    }
798
799    /// Sets the **audio sample rate** (in Hz) for output encoding.
800    ///
801    /// This method allows you to specify the desired audio sample rate for the output.
802    /// Common values include 44100 (CD quality), 48000 (standard for digital video),
803    /// and 22050 or 16000 (for lower bitrate applications).
804    ///
805    /// # Parameters
806    /// * `audio_sample_rate` - The sample rate in Hertz (e.g., 44100, 48000).
807    ///
808    /// # Returns
809    /// * `Self` - The modified `Output`, allowing method chaining.
810    ///
811    /// # Example
812    /// ```rust
813    /// let output = Output::from("output.mp4")
814    ///     .set_audio_sample_rate(48000); // Set to 48kHz
815    /// ```
816    pub fn set_audio_sample_rate(mut self, audio_sample_rate: i32) -> Self {
817        self.audio_sample_rate = Some(audio_sample_rate);
818        self
819    }
820
821    /// Sets the number of **audio channels** for output encoding.
822    ///
823    /// Common values include 1 (mono), 2 (stereo), 5.1 (6 channels), and 7.1 (8 channels).
824    /// This setting affects the spatial audio characteristics of the output.
825    ///
826    /// # Parameters
827    /// * `audio_channels` - The number of audio channels (e.g., 1 for mono, 2 for stereo).
828    ///
829    /// # Returns
830    /// * `Self` - The modified `Output`, allowing method chaining.
831    ///
832    /// # Example
833    /// ```rust
834    /// let output = Output::from("output.mp4")
835    ///     .set_audio_channels(2); // Set to stereo
836    /// ```
837    pub fn set_audio_channels(mut self, audio_channels: i32) -> Self {
838        self.audio_channels = Some(audio_channels);
839        self
840    }
841
842    /// Sets the **audio sample format** for output encoding.
843    ///
844    /// This method allows you to specify the audio sample format, which affects
845    /// how audio samples are represented. Common formats include:
846    /// - `AV_SAMPLE_FMT_S16` (signed 16-bit)
847    /// - `AV_SAMPLE_FMT_S32` (signed 32-bit)
848    /// - `AV_SAMPLE_FMT_FLT` (32-bit float)
849    /// - `AV_SAMPLE_FMT_FLTP` (32-bit float, planar)
850    ///
851    /// The format choice can impact quality, processing requirements, and compatibility.
852    ///
853    /// # Parameters
854    /// * `sample_fmt` - An `AVSampleFormat` enum value specifying the desired sample format.
855    ///
856    /// # Returns
857    /// * `Self` - The modified `Output`, allowing method chaining.
858    ///
859    /// # Example
860    /// ```rust
861    /// use ffmpeg_sys_next::AVSampleFormat::AV_SAMPLE_FMT_S16;
862    ///
863    /// let output = Output::from("output.mp4")
864    ///     .set_audio_sample_fmt(AV_SAMPLE_FMT_S16); // Set to signed 16-bit
865    /// ```
866    pub fn set_audio_sample_fmt(mut self, sample_fmt: AVSampleFormat) -> Self {
867        self.audio_sample_fmt = Some(sample_fmt);
868        self
869    }
870
871    /// Sets the **video quality scale** (VBR) for encoding.
872    ///
873    /// This method configures a fixed quality scale for variable bitrate (VBR) video encoding.
874    /// Lower values result in higher quality but larger file sizes, while higher values
875    /// produce lower quality with smaller file sizes.
876    ///
877    /// # Note on Modern Usage
878    /// While still supported, using fixed quality scale (`-q:v`) is generally not recommended
879    /// for modern video encoding workflows with codecs like H.264 and H.265. Instead, consider:
880    /// * For H.264/H.265: Use CRF (Constant Rate Factor) via `-crf` parameter
881    /// * For two-pass encoding: Use target bitrate settings
882    ///
883    /// This parameter is primarily useful for older codecs or specific scenarios where
884    /// direct quality scale control is needed.
885    ///
886    /// # Quality Scale Ranges by Codec
887    /// * **H.264/H.265**: 0-51 (if needed: 17-28)
888    ///   - 17-18: Visually lossless
889    ///   - 23: High quality
890    ///   - 28: Good quality with reasonable file size
891    /// * **MPEG-4/MPEG-2**: 2-31 (recommended: 2-6)
892    ///   - Lower values = higher quality
893    /// * **VP9**: 0-63 (if needed: 15-35)
894    ///
895    /// # Parameters
896    /// * `video_qscale` - The quality scale value for video encoding.
897    ///
898    /// # Returns
899    /// * `Self` - The modified `Output`, allowing method chaining.
900    ///
901    /// # Example
902    /// ```rust
903    /// // For MJPEG encoding of image sequences
904    /// let output = Output::from("output.jpg")
905    ///     .set_video_qscale(2);  // High quality JPEG images
906    ///
907    /// // For legacy image format conversion
908    /// let output = Output::from("output.png")
909    ///     .set_video_qscale(3);  // Controls compression level
910    /// ```
911    pub fn set_video_qscale(mut self, video_qscale: i32) -> Self {
912        self.video_qscale = Some(video_qscale);
913        self
914    }
915
916    /// Sets the **audio quality scale** for encoding.
917    ///
918    /// This method configures codec-specific audio quality settings. The range, behavior,
919    /// and optimal values depend entirely on the audio codec being used.
920    ///
921    /// # Quality Scale Ranges by Codec
922    /// * **MP3 (libmp3lame)**: 0-9 (recommended: 2-5)
923    ///   - 0: Highest quality
924    ///   - 2: Near-transparent quality (~190-200 kbps)
925    ///   - 5: Good quality (~130 kbps)
926    ///   - 9: Lowest quality
927    /// * **AAC**: 0.1-255 (recommended: 1-5)
928    ///   - 1: Highest quality (~250 kbps)
929    ///   - 3: Good quality (~160 kbps)
930    ///   - 5: Medium quality (~100 kbps)
931    /// * **Vorbis**: -1 to 10 (recommended: 3-8)
932    ///   - 10: Highest quality
933    ///   - 5: Good quality
934    ///   - 3: Medium quality
935    ///
936    /// # Parameters
937    /// * `audio_qscale` - The quality scale value for audio encoding.
938    ///
939    /// # Returns
940    /// * `Self` - The modified `Output`, allowing method chaining.
941    ///
942    /// # Example
943    /// ```rust
944    /// // For MP3 encoding at high quality
945    /// let output = Output::from("output.mp3")
946    ///     .set_audio_codec("libmp3lame")
947    ///     .set_audio_qscale(2);
948    ///
949    /// // For AAC encoding at good quality
950    /// let output = Output::from("output.m4a")
951    ///     .set_audio_codec("aac")
952    ///     .set_audio_qscale(3);
953    ///
954    /// // For Vorbis encoding at high quality
955    /// let output = Output::from("output.ogg")
956    ///     .set_audio_codec("libvorbis")
957    ///     .set_audio_qscale(7);
958    /// ```
959    pub fn set_audio_qscale(mut self, audio_qscale: i32) -> Self {
960        self.audio_qscale = Some(audio_qscale);
961        self
962    }
963
964    /// **Sets the maximum number of video frames to encode (`-frames:v`).**
965    ///
966    /// **Equivalent FFmpeg Command:**
967    /// ```sh
968    /// ffmpeg -i input.mp4 -frames:v 100 output.mp4
969    /// ```
970    ///
971    /// **Example Usage:**
972    /// ```rust
973    /// let output = Output::from("some_url")
974    ///     .set_max_video_frames(500);
975    /// ```
976    pub fn set_max_video_frames(mut self, max_frames: impl Into<Option<i64>>) -> Self {
977        self.max_video_frames = max_frames.into();
978        self
979    }
980
981    /// **Sets the maximum number of audio frames to encode (`-frames:a`).**
982    ///
983    /// **Equivalent FFmpeg Command:**
984    /// ```sh
985    /// ffmpeg -i input.mp4 -frames:a 500 output.mp4
986    /// ```
987    ///
988    /// **Example Usage:**
989    /// ```rust
990    /// let output = Output::from("some_url")
991    ///     .set_max_audio_frames(500);
992    /// ```
993    pub fn set_max_audio_frames(mut self, max_frames: impl Into<Option<i64>>) -> Self {
994        self.max_audio_frames = max_frames.into();
995        self
996    }
997
998    /// **Sets the maximum number of subtitle frames to encode (`-frames:s`).**
999    ///
1000    /// **Equivalent FFmpeg Command:**
1001    /// ```sh
1002    /// ffmpeg -i input.mp4 -frames:s 200 output.mp4
1003    /// ```
1004    ///
1005    /// **Example Usage:**
1006    /// ```rust
1007    /// let output = Output::from("some_url")
1008    ///     .set_max_subtitle_frames(200);
1009    /// ```
1010    pub fn set_max_subtitle_frames(mut self, max_frames: impl Into<Option<i64>>) -> Self {
1011        self.max_subtitle_frames = max_frames.into();
1012        self
1013    }
1014
1015    /// Sets a **video codec-specific option**.
1016    ///
1017    /// These options control **video encoding parameters** such as compression, quality, and speed.
1018    ///
1019    /// **Supported Parameters:**
1020    /// | Parameter | Description |
1021    /// |-----------|-------------|
1022    /// | `crf=0-51` | Quality level for x264/x265, lower means higher quality (`0` is lossless) |
1023    /// | `preset=ultrafast, superfast, fast, medium, slow, veryslow` | Encoding speed, affects compression efficiency |
1024    /// | `tune=film, animation, grain, stillimage, fastdecode, zerolatency` | Optimizations for specific types of content |
1025    /// | `b=4M` | Bitrate (e.g., `4M` for 4 Mbps) |
1026    /// | `g=50` | GOP (Group of Pictures) size, affects keyframe frequency |
1027    ///
1028    /// **Example Usage:**
1029    /// ```rust
1030    /// let output = Output::from("some_url")
1031    ///     .set_video_codec_opt("crf", "18")
1032    ///     .set_video_codec_opt("preset", "fast");
1033    /// ```
1034    pub fn set_video_codec_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1035        if let Some(ref mut opts) = self.video_codec_opts {
1036            opts.insert(key.into(), value.into());
1037        } else {
1038            let mut opts = HashMap::new();
1039            opts.insert(key.into(), value.into());
1040            self.video_codec_opts = Some(opts);
1041        }
1042        self
1043    }
1044
1045    /// **Sets multiple video codec options at once.**
1046    ///
1047    /// **Example Usage:**
1048    /// ```rust
1049    /// let output = Output::from("some_url")
1050    ///     .set_video_codec_opts(vec![
1051    ///         ("crf", "18"),
1052    ///         ("preset", "fast")
1053    ///     ]);
1054    /// ```
1055    pub fn set_video_codec_opts(mut self, opts: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
1056        let video_opts = self.video_codec_opts.get_or_insert_with(HashMap::new);
1057        for (key, value) in opts {
1058            video_opts.insert(key.into(), value.into());
1059        }
1060        self
1061    }
1062
1063    /// Sets a **audio codec-specific option**.
1064    ///
1065    /// These options control **audio encoding parameters** such as bitrate, sample rate, and format.
1066    ///
1067    /// **Supported Parameters:**
1068    /// | Parameter | Description |
1069    /// |-----------|-------------|
1070    /// | `b=192k` | Bitrate (e.g., `128k` for 128 Kbps, `320k` for 320 Kbps) |
1071    /// | `compression_level=0-12` | Compression efficiency for formats like FLAC |
1072    ///
1073    /// **Example Usage:**
1074    /// ```rust
1075    /// let output = Output::from("some_url")
1076    ///     .set_audio_codec_opt("b", "320k")
1077    ///     .set_audio_codec_opt("compression_level", "6");
1078    /// ```
1079    pub fn set_audio_codec_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1080        if let Some(ref mut opts) = self.audio_codec_opts {
1081            opts.insert(key.into(), value.into());
1082        } else {
1083            let mut opts = HashMap::new();
1084            opts.insert(key.into(), value.into());
1085            self.audio_codec_opts = Some(opts);
1086        }
1087        self
1088    }
1089
1090    /// **Sets multiple audio codec options at once.**
1091    ///
1092    /// **Example Usage:**
1093    /// ```rust
1094    /// let output = Output::from("some_url")
1095    ///     .set_audio_codec_opts(vec![
1096    ///         ("b", "320k"),
1097    ///         ("compression_level", "6")
1098    ///     ]);
1099    /// ```
1100    pub fn set_audio_codec_opts(mut self, opts: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
1101        let audio_opts = self.audio_codec_opts.get_or_insert_with(HashMap::new);
1102        for (key, value) in opts {
1103            audio_opts.insert(key.into(), value.into());
1104        }
1105        self
1106    }
1107
1108    /// Sets a **subtitle codec-specific option**.
1109    ///
1110    /// These options control **subtitle encoding parameters** such as format and character encoding.
1111    ///
1112    /// **Supported Parameters:**
1113    /// | Parameter | Description |
1114    /// |-----------|-------------|
1115    /// | `mov_text` | Subtitle format for MP4 files |
1116    /// | `srt` | Subtitle format for `.srt` files |
1117    /// | `ass` | Advanced SubStation Alpha (ASS) subtitle format |
1118    /// | `forced_subs=1` | Forces the subtitles to always be displayed |
1119    ///
1120    /// **Example Usage:**
1121    /// ```rust
1122    /// let output = Output::from("some_url")
1123    ///     .set_subtitle_codec_opt("mov_text", "");
1124    /// ```
1125    pub fn set_subtitle_codec_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1126        if let Some(ref mut opts) = self.subtitle_codec_opts {
1127            opts.insert(key.into(), value.into());
1128        } else {
1129            let mut opts = HashMap::new();
1130            opts.insert(key.into(), value.into());
1131            self.subtitle_codec_opts = Some(opts);
1132        }
1133        self
1134    }
1135
1136    /// **Sets multiple subtitle codec options at once.**
1137    ///
1138    /// **Example Usage:**
1139    /// ```rust
1140    /// let output = Output::from("some_url")
1141    ///     .set_subtitle_codec_opts(vec![
1142    ///         ("mov_text", ""),
1143    ///         ("forced_subs", "1")
1144    ///     ]);
1145    /// ```
1146    pub fn set_subtitle_codec_opts(mut self, opts: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
1147        let subtitle_opts = self.subtitle_codec_opts.get_or_insert_with(HashMap::new);
1148        for (key, value) in opts {
1149            subtitle_opts.insert(key.into(), value.into());
1150        }
1151        self
1152    }
1153
1154    /// Sets a format-specific option for the output container.
1155    ///
1156    /// FFmpeg supports various format-specific options that can be passed to the muxer.
1157    /// These options allow fine-tuning of the output container’s behavior.
1158    ///
1159    /// **Example Usage:**
1160    /// ```rust
1161    /// let output = Output::from("some_url")
1162    ///     .set_format_opt("movflags", "faststart")
1163    ///     .set_format_opt("flvflags", "no_duration_filesize");
1164    /// ```
1165    ///
1166    /// ### Common Format Options:
1167    /// | Format | Option | Description |
1168    /// |--------|--------|-------------|
1169    /// | `mp4`  | `movflags=faststart` | Moves moov atom to the beginning of the file for faster playback start |
1170    /// | `flv`  | `flvflags=no_duration_filesize` | Removes duration/size metadata for live streaming |
1171    ///
1172    /// **Parameters:**
1173    /// - `key`: The format option name (e.g., `"movflags"`, `"flvflags"`).
1174    /// - `value`: The value to set (e.g., `"faststart"`, `"no_duration_filesize"`).
1175    ///
1176    /// Returns the modified `Output` struct for chaining.
1177    pub fn set_format_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1178        if let Some(ref mut opts) = self.format_opts {
1179            opts.insert(key.into(), value.into());
1180        } else {
1181            let mut opts = HashMap::new();
1182            opts.insert(key.into(), value.into());
1183            self.format_opts = Some(opts);
1184        }
1185        self
1186    }
1187
1188    /// Sets multiple format-specific options at once.
1189    ///
1190    /// This method allows setting multiple format options in a single call.
1191    ///
1192    /// **Example Usage:**
1193    /// ```rust
1194    /// let output = Output::from("some_url")
1195    ///     .set_format_opts(vec![
1196    ///         ("movflags", "faststart"),
1197    ///         ("flvflags", "no_duration_filesize")
1198    ///     ]);
1199    /// ```
1200    ///
1201    /// **Parameters:**
1202    /// - `opts`: A vector of key-value pairs representing format options.
1203    ///
1204    /// Returns the modified `Output` struct for chaining.
1205    pub fn set_format_opts(mut self, opts: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
1206        if let Some(ref mut format_opts) = self.format_opts {
1207            for (key, value) in opts {
1208                format_opts.insert(key.into(), value.into());
1209            }
1210        } else {
1211            let mut format_opts = HashMap::new();
1212            for (key, value) in opts {
1213                format_opts.insert(key.into(), value.into());
1214            }
1215            self.format_opts = Some(format_opts);
1216        }
1217        self
1218    }
1219}
1220
1221impl From<Box<dyn FnMut(&[u8]) -> i32>> for Output {
1222    fn from(write_callback_and_format: Box<dyn FnMut(&[u8]) -> i32>) -> Self {
1223        Self {
1224            url: None,
1225            write_callback: Some(write_callback_and_format),
1226            seek_callback: None,
1227            frame_pipelines: None,
1228            stream_maps: vec![],
1229            format: None,
1230            video_codec: None,
1231            audio_codec: None,
1232            subtitle_codec: None,
1233            start_time_us: None,
1234            recording_time_us: None,
1235            stop_time_us: None,
1236            framerate: None,
1237            vsync_method: None,
1238            bits_per_raw_sample: None,
1239            audio_sample_rate: None,
1240            audio_channels: None,
1241            audio_sample_fmt: None,
1242            video_qscale: None,
1243            audio_qscale: None,
1244            max_video_frames: None,
1245            max_audio_frames: None,
1246            max_subtitle_frames: None,
1247            video_codec_opts: None,
1248            audio_codec_opts: None,
1249            subtitle_codec_opts: None,
1250            format_opts: None,
1251        }
1252    }
1253}
1254
1255impl From<String> for Output {
1256    fn from(url: String) -> Self {
1257        Self {
1258            url: Some(url),
1259            write_callback: None,
1260            seek_callback: None,
1261            frame_pipelines: None,
1262            stream_maps: vec![],
1263            format: None,
1264            video_codec: None,
1265            audio_codec: None,
1266            subtitle_codec: None,
1267            start_time_us: None,
1268            recording_time_us: None,
1269            stop_time_us: None,
1270            framerate: None,
1271            vsync_method: None,
1272            bits_per_raw_sample: None,
1273            audio_sample_rate: None,
1274            audio_channels: None,
1275            audio_sample_fmt: None,
1276            video_qscale: None,
1277            audio_qscale: None,
1278            max_video_frames: None,
1279            max_audio_frames: None,
1280            max_subtitle_frames: None,
1281            video_codec_opts: None,
1282            audio_codec_opts: None,
1283            subtitle_codec_opts: None,
1284            format_opts: None,
1285        }
1286    }
1287}
1288
1289impl From<&str> for Output {
1290    fn from(url: &str) -> Self {
1291        Self::from(String::from(url))
1292    }
1293}
1294
1295#[derive(Debug, Clone)]
1296pub(crate) struct StreamMap {
1297    pub(crate) linklabel: String,
1298    pub(crate) copy: bool,
1299}
1300
1301impl<T: Into<String>> From<T> for StreamMap {
1302    fn from(linklabel: T) -> Self {
1303        Self {
1304            linklabel: linklabel.into(),
1305            copy: false,
1306        }
1307    }
1308}