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