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