Skip to main content

ez_ffmpeg/core/context/
input.rs

1use crate::filter::frame_pipeline::FramePipeline;
2use std::collections::HashMap;
3
4// Note: Input is Send if all callback fields are Send.
5// We require `+ Send` on callback types to ensure this.
6// Input is !Sync because FnMut callbacks require exclusive access.
7
8pub struct Input {
9    /// The URL of the input source.
10    ///
11    /// This specifies the source from which the input stream is obtained. It can be:
12    /// - A local file path (e.g., `file:///path/to/video.mp4`).
13    /// - A network stream (e.g., `rtmp://example.com/live/stream`).
14    /// - Any other URL supported by FFmpeg (e.g., `http://example.com/video.mp4`, `udp://...`).
15    ///
16    /// The URL must be valid. If the URL is invalid or unsupported,
17    /// the library will return an error when attempting to open the input stream.
18    pub(crate) url: Option<String>,
19
20    /// A callback function for custom data reading.
21    ///
22    /// The `read_callback` function allows you to provide custom logic for feeding data into
23    /// the input stream. This is useful for scenarios where the input does not come directly
24    /// from a standard source (like a file or URL), but instead from a custom data source,
25    /// such as an in-memory buffer or a custom network stream.
26    ///
27    /// ### Parameters:
28    /// - `buf: &mut [u8]`: A mutable buffer into which the data should be written.
29    ///   The callback should fill this buffer with as much data as possible, up to its length.
30    ///
31    /// ### Return Value:
32    /// - **Positive Value**: The number of bytes successfully read into `buf`.
33    /// - **`ffmpeg_sys_next::AVERROR_EOF`**: Indicates the end of the input stream. No more data will be read.
34    /// - **Negative Value**: Indicates an error occurred, such as:
35    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
36    ///   - Custom-defined error codes depending on your implementation.
37    ///
38    /// ### Example:
39    /// ```rust,ignore
40    /// fn custom_read_callback(buf: &mut [u8]) -> i32 {
41    ///     let data = b"example data stream";
42    ///     let len = data.len().min(buf.len());
43    ///     buf[..len].copy_from_slice(&data[..len]);
44    ///     len as i32 // Return the number of bytes written into the buffer
45    /// }
46    /// ```
47    pub(crate) read_callback: Option<Box<dyn FnMut(&mut [u8]) -> i32 + Send>>,
48
49    /// Size of the AVIO buffer backing a custom `read_callback`, in bytes.
50    /// Only used when the input is a callback (no URL). Larger values reduce
51    /// Rust↔FFmpeg round-trips for sequential/network sources; the default is
52    /// [`DEFAULT_CUSTOM_IO_BUFFER_SIZE`](crate::core::context::DEFAULT_CUSTOM_IO_BUFFER_SIZE)
53    /// (64 KiB). Set via [`Input::set_io_buffer_size`].
54    pub(crate) io_buffer_size: usize,
55
56    /// A callback function for custom seeking within the input stream.
57    ///
58    /// The `seek_callback` function allows defining custom seeking behavior.
59    /// This is useful for data sources that support seeking, such as files or memory-mapped data.
60    /// For non-seekable streams (e.g., live network streams), this function may return an error.
61    ///
62    /// **FFmpeg may invoke `seek_callback` from multiple threads, so thread safety is required.**
63    /// When using a `File` as an input source, **use `Arc<Mutex<File>>` to ensure safe access.**
64    ///
65    /// ### Parameters:
66    /// - `offset: i64`: The target position in the stream for seeking.
67    /// - `whence: i32`: The seek mode defining how the `offset` should be interpreted:
68    ///   - `ffmpeg_sys_next::SEEK_SET` (0): Seek to an absolute position.
69    ///   - `ffmpeg_sys_next::SEEK_CUR` (1): Seek relative to the current position.
70    ///   - `ffmpeg_sys_next::SEEK_END` (2): Seek relative to the end of the stream.
71    ///   - `ffmpeg_sys_next::AVSEEK_SIZE` (65536): Query the **total size** of the stream
72    ///     instead of seeking.
73    ///
74    ///   `avio_seek` strips `ffmpeg_sys_next::AVSEEK_FORCE` (131072) from `whence` before
75    ///   invoking a custom callback; the example masks it anyway as cheap defense. No
76    ///   other `whence` values reach a custom seek callback.
77    ///
78    /// ### Return Value:
79    /// - **Positive Value**: The new offset position after seeking.
80    /// - **Negative Value**: An error occurred. Common errors include:
81    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE)`: Seek is not supported.
82    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
83    ///
84    /// ### Example (Handling multi-threaded access safely with `Arc<Mutex<File>>`):
85    /// Since FFmpeg may call `read_callback` and `seek_callback` from different threads,
86    /// **`Arc<Mutex<File>>` is used to ensure safe access across threads.**
87    ///
88    /// ```rust,ignore
89    /// use std::fs::File;
90    /// use std::io::{Seek, SeekFrom};
91    /// use std::sync::{Arc, Mutex};
92    ///
93    /// let file = Arc::new(Mutex::new(File::open("test.mp4").expect("Failed to open file")));
94    ///
95    /// let seek_callback = {
96    ///     let file = Arc::clone(&file);
97    ///     Box::new(move |offset: i64, whence: i32| -> i64 {
98    ///         let mut file = file.lock().unwrap(); // Acquire lock
99    ///
100    ///         // ✅ Handle AVSEEK_SIZE: FFmpeg asks for the total stream size instead of seeking
101    ///         if whence == ffmpeg_sys_next::AVSEEK_SIZE {
102    ///             if let Ok(size) = file.metadata().map(|m| m.len() as i64) {
103    ///                 return size;
104    ///             }
105    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
106    ///         }
107    ///
108    ///         // ✅ Defensive: mask AVSEEK_FORCE (avio_seek strips it before a custom callback)
109    ///         let seek_result = match whence & !ffmpeg_sys_next::AVSEEK_FORCE {
110    ///             ffmpeg_sys_next::SEEK_SET => file.seek(SeekFrom::Start(offset as u64)),
111    ///             ffmpeg_sys_next::SEEK_CUR => file.seek(SeekFrom::Current(offset)),
112    ///             ffmpeg_sys_next::SEEK_END => file.seek(SeekFrom::End(offset)),
113    ///             // The AVIO layer sends no other whence values (lseek extensions
114    ///             // like SEEK_HOLE/SEEK_DATA never reach a custom callback)
115    ///             _ => {
116    ///                 println!("Unsupported seek mode: {}", whence);
117    ///                 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
118    ///             }
119    ///         };
120    ///
121    ///         match seek_result {
122    ///             Ok(new_pos) => {
123    ///                 println!("Seek successful, new position: {}", new_pos);
124    ///                 new_pos as i64
125    ///             }
126    ///             Err(e) => {
127    ///                 println!("Seek failed: {}", e);
128    ///                 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64
129    ///             }
130    ///         }
131    ///     })
132    /// };
133    /// ```
134    pub(crate) seek_callback: Option<Box<dyn FnMut(i64, i32) -> i64 + Send>>,
135
136    /// The pipeline that provides custom processing for decoded frames.
137    ///
138    /// After the input data is decoded into `Frame` objects, these frames
139    /// are passed through the `frame_pipeline`. Each frame goes through
140    /// a series of `FrameFilter` objects in the pipeline, allowing for
141    /// customized processing (e.g., filtering, transformation, etc.).
142    ///
143    /// If `None`, no processing pipeline is applied to the decoded frames.
144    pub(crate) frame_pipelines: Option<Vec<FramePipeline>>,
145
146    /// The input format for the source.
147    ///
148    /// This field specifies which container or device format FFmpeg should use to read the input.
149    /// If `None`, FFmpeg will attempt to automatically detect the format based on the source URL,
150    /// file extension, or stream data.
151    ///
152    /// You might need to specify a format explicitly in cases where automatic detection fails or
153    /// when you must force a particular format. For example:
154    /// - When capturing from a specific device on macOS (using `avfoundation`).
155    /// - When capturing on Windows devices (using `dshow`).
156    /// - When dealing with raw streams or unusual data sources.
157    pub(crate) format: Option<String>,
158
159    /// The codec to be used for **video** decoding.
160    ///
161    /// If set, this forces FFmpeg to use the specified video codec for decoding.
162    /// Otherwise, FFmpeg will attempt to auto-detect the best available codec.
163    pub(crate) video_codec: Option<String>,
164
165    /// The codec to be used for **audio** decoding.
166    ///
167    /// If set, this forces FFmpeg to use the specified audio codec for decoding.
168    /// Otherwise, FFmpeg will attempt to auto-detect the best available codec.
169    pub(crate) audio_codec: Option<String>,
170
171    /// The codec to be used for **subtitle** decoding.
172    ///
173    /// If set, this forces FFmpeg to use the specified subtitle codec for decoding.
174    /// Otherwise, FFmpeg will attempt to auto-detect the best available codec.
175    pub(crate) subtitle_codec: Option<String>,
176
177    /// Video decoder-specific options.
178    ///
179    /// This field stores key-value pairs for configuring the **video decoder**.
180    /// These options are applied to the video decoder before decoding begins.
181    ///
182    /// **Common Examples:**
183    /// - `skip_frame=nokey` (decode only keyframes)
184    /// - `thread_type=slice` (slice-based multithreading)
185    /// - `low_delay=1` (reduce decoder latency)
186    pub(crate) video_codec_opts: Option<HashMap<String, String>>,
187
188    /// Audio decoder-specific options.
189    ///
190    /// This field stores key-value pairs for configuring the **audio decoder**.
191    /// These options are applied to the audio decoder before decoding begins.
192    ///
193    /// **Common Examples:**
194    /// - `threads=1` (single-threaded decoding)
195    /// - `drc_scale=0` (disable dynamic range compression in AC-3)
196    pub(crate) audio_codec_opts: Option<HashMap<String, String>>,
197
198    /// Subtitle decoder-specific options.
199    ///
200    /// This field stores key-value pairs for configuring the **subtitle decoder**.
201    /// These options are applied to the subtitle decoder before decoding begins.
202    ///
203    /// **Common Examples:**
204    /// - `sub_charenc=CP1252` (source subtitle character encoding)
205    pub(crate) subtitle_codec_opts: Option<HashMap<String, String>>,
206
207    pub(crate) exit_on_error: Option<bool>,
208
209    /// read input at specified rate.
210    /// when set 1. read input at native frame rate.
211    pub(crate) readrate: Option<f32>,
212    pub(crate) start_time_us: Option<i64>,
213    pub(crate) recording_time_us: Option<i64>,
214    pub(crate) stop_time_us: Option<i64>,
215
216    /// set number of times input stream shall be looped
217    pub(crate) stream_loop: Option<i32>,
218
219    /// Hardware Acceleration name
220    /// use Hardware accelerated decoding
221    pub(crate) hwaccel: Option<String>,
222    /// select a device for HW acceleration
223    pub(crate) hwaccel_device: Option<String>,
224    /// select output format used with HW accelerated decoding
225    pub(crate) hwaccel_output_format: Option<String>,
226
227    /// Log-level offset applied to this input's decoders
228    /// (`AVCodecContext.log_level_offset`).
229    pub(crate) log_level_offset: Option<i32>,
230
231    /// Input options for avformat_open_input.
232    ///
233    /// This field stores options that are passed to FFmpeg's `avformat_open_input()` function.
234    /// These options can affect different layers of the input processing pipeline:
235    ///
236    /// **Format/Demuxer options:**
237    /// - `probesize` - Maximum data to probe for format detection
238    /// - `analyzeduration` - Duration to analyze for stream info
239    /// - `fflags` - Format flags (e.g., "+genpts")
240    ///
241    /// **Protocol options:**
242    /// - `user_agent` - HTTP User-Agent header
243    /// - `timeout` - Network timeout in microseconds
244    /// - `headers` - Custom HTTP headers
245    ///
246    /// **Device options:**
247    /// - `framerate` - Input framerate (for avfoundation, dshow, etc.)
248    /// - `video_size` - Input video resolution
249    /// - `pixel_format` - Input pixel format
250    ///
251    /// **General input options:**
252    /// - `re` - Read input at native frame rate
253    ///
254    /// These options allow fine-tuning of input behavior across different components
255    /// of the FFmpeg input pipeline.
256    ///
257    /// Note: FFmpeg CLI's `thread_queue_size` is NOT an `avformat_open_input`
258    /// demuxer/protocol option, so setting it here has no effect. ez-ffmpeg's
259    /// internal scheduler queues are fixed-size today and not yet configurable.
260    pub(crate) input_opts: Option<HashMap<String, String>>,
261
262    /// Whether to probe stream information with `avformat_find_stream_info`
263    /// after opening the input (default: `true`).
264    ///
265    /// Probing reads ahead to fill in stream parameters the container header
266    /// does not carry (frame rate, pixel format, extradata, ...). Disabling it
267    /// (`false`) skips that read-ahead — useful for low-latency or
268    /// known-format inputs — but may leave `codecpar` incomplete downstream.
269    pub(crate) find_stream_info: bool,
270
271    /// Per-stream codec options used only while probing stream information
272    /// inside `avformat_find_stream_info`, keyed by stream index.
273    ///
274    /// These configure the temporary probing codec contexts (e.g.
275    /// `skip_frame`, `lowres`); they are separate from the decoder options
276    /// applied at decode time (`set_video_codec_opt` and friends).
277    pub(crate) find_stream_info_codec_opts: Option<HashMap<usize, HashMap<String, String>>>,
278
279    /// Automatically rotate video based on display matrix metadata.
280    ///
281    /// When enabled (default), videos with rotation metadata (common in smartphone
282    /// recordings) will be automatically rotated to the correct orientation using
283    /// transpose/hflip/vflip filters.
284    ///
285    /// Set to `false` to disable automatic rotation and preserve the original
286    /// video orientation.
287    ///
288    /// ## FFmpeg CLI equivalent
289    /// ```bash
290    /// # Disable autorotate
291    /// ffmpeg -autorotate 0 -i input.mp4 output.mp4
292    ///
293    /// # Enable autorotate (default)
294    /// ffmpeg -autorotate 1 -i input.mp4 output.mp4
295    /// ```
296    ///
297    /// ## FFmpeg source reference (FFmpeg 7.x)
298    /// - Default value: `ffmpeg_demux.c:1270` (`ds->autorotate = 1`)
299    /// - Flag setting: `ffmpeg_demux.c:1088` (`IFILTER_FLAG_AUTOROTATE`)
300    /// - Filter insertion: `ffmpeg_filter.c:1744-1778`
301    pub(crate) autorotate: Option<bool>,
302
303    /// Timestamp scale factor for pts/dts values.
304    ///
305    /// This multiplier is applied to packet timestamps after ts_offset addition.
306    /// Default is 1.0 (no scaling). Values must be positive.
307    ///
308    /// This is useful for fixing videos with incorrect timestamps or for
309    /// special timestamp manipulation scenarios.
310    ///
311    /// ## FFmpeg CLI equivalent
312    /// ```bash
313    /// # Scale timestamps by 2x
314    /// ffmpeg -itsscale 2.0 -i input.mp4 output.mp4
315    ///
316    /// # Scale timestamps by 0.5x (half speed effect on timestamps)
317    /// ffmpeg -itsscale 0.5 -i input.mp4 output.mp4
318    /// ```
319    ///
320    /// ## FFmpeg source reference (FFmpeg 7.x)
321    /// - Default value: `ffmpeg_demux.c:1267` (`ds->ts_scale = 1.0`)
322    /// - Application: `ffmpeg_demux.c:404-406` (applied after ts_offset)
323    pub(crate) ts_scale: Option<f64>,
324
325    /// Forced framerate for the input video stream.
326    ///
327    /// When set, this overrides the DTS estimation logic to use the specified
328    /// framerate for computing `next_dts` in the video stream. By default (None),
329    /// the actual packet duration is used for DTS estimation, matching FFmpeg CLI
330    /// behavior when `-r` is not specified.
331    ///
332    /// This affects all video DTS estimation, including recording_time cutoff
333    /// decisions during stream copy and the output stream time_base when set via
334    /// `streamcopy_init`.
335    ///
336    /// ## FFmpeg CLI equivalent
337    /// ```bash
338    /// # Force input framerate to 30fps
339    /// ffmpeg -r 30 -i input.mp4 output.mp4
340    /// ```
341    ///
342    /// ## FFmpeg source reference (FFmpeg 7.x)
343    /// - Field: `ffmpeg.h:452` (`ist->framerate`, only set with `-r`)
344    /// - Application: `ffmpeg_demux.c:329-333` (used in `ist_dts_update`)
345    pub(crate) framerate: Option<(i32, i32)>,
346}
347
348impl Input {
349    pub fn new(url: impl Into<String>) -> Self {
350        url.into().into()
351    }
352
353    /// Creates a new `Input` instance with a custom read callback.
354    ///
355    /// This method initializes an `Input` object that uses a provided `read_callback` function
356    /// to supply data to the input stream. This is particularly useful for custom data sources
357    /// such as in-memory buffers, network streams, or other non-standard input mechanisms.
358    ///
359    /// ### Parameters:
360    /// - `read_callback: fn(buf: &mut [u8]) -> i32`: A function pointer that fills the provided
361    ///   mutable buffer with data and returns the number of bytes read.
362    ///
363    /// ### Return Value:
364    /// - Returns a new `Input` instance configured with the specified `read_callback`.
365    ///
366    /// ### Behavior of `read_callback`:
367    /// - **Positive Value**: Indicates the number of bytes successfully read.
368    /// - **`ffmpeg_sys_next::AVERROR_EOF`**: Indicates the end of the stream. The library will stop requesting data.
369    /// - **Negative Value**: Indicates an error occurred. For example:
370    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: Represents an input/output error.
371    ///   - Other custom-defined error codes can also be returned to signal specific issues.
372    ///
373    /// ### Example:
374    /// ```rust,ignore
375    /// let input = Input::new_by_read_callback(move |buf| {
376    ///     let data = b"example custom data source";
377    ///     let len = data.len().min(buf.len());
378    ///     buf[..len].copy_from_slice(&data[..len]);
379    ///     len as i32 // Return the number of bytes written
380    /// });
381    /// ```
382    pub fn new_by_read_callback<F>(read_callback: F) -> Self
383    where
384        F: FnMut(&mut [u8]) -> i32 + Send + 'static,
385    {
386        (Box::new(read_callback) as Box<dyn FnMut(&mut [u8]) -> i32 + Send>).into()
387    }
388
389    /// Sets the AVIO buffer size, in bytes, for a custom `read_callback` input.
390    ///
391    /// FFmpeg fills one buffer-sized chunk per callback, so a larger buffer means
392    /// fewer Rust↔FFmpeg round-trips for sequential or network sources. Only
393    /// applies when the input is a callback (no URL); ignored otherwise. The
394    /// default is 64 KiB, which keeps first-packet latency low for live use.
395    ///
396    /// # Errors
397    /// The value is validated when the context is built:
398    /// `FfmpegContext::builder().build()` fails with
399    /// [`OpenInputError::InvalidOption`](crate::error::OpenInputError::InvalidOption)
400    /// if `size` is 0 or exceeds `i32::MAX` (FFmpeg's `avio_alloc_context`
401    /// takes an `int` buffer size).
402    pub fn set_io_buffer_size(mut self, size: usize) -> Self {
403        self.io_buffer_size = size;
404        self
405    }
406
407    /// Sets a custom seek callback for the input stream.
408    ///
409    /// This function assigns a user-defined function that handles seeking within the input stream.
410    /// It is required when using custom data sources that support random access, such as files,
411    /// memory-mapped buffers, or seekable network streams.
412    ///
413    /// **FFmpeg may invoke `seek_callback` from different threads.**
414    /// If using a `File` as the data source, **wrap it in `Arc<Mutex<File>>`** to ensure
415    /// thread-safe access across multiple threads.
416    ///
417    /// ### Parameters:
418    /// - `seek_callback: FnMut(i64, i32) -> i64`: A function that handles seek operations.
419    ///   - `offset: i64`: The target seek position in the stream.
420    ///   - `whence: i32`: The seek mode, which determines how `offset` should be interpreted:
421    ///     - `ffmpeg_sys_next::SEEK_SET` (0) - Seek to an absolute position.
422    ///     - `ffmpeg_sys_next::SEEK_CUR` (1) - Seek relative to the current position.
423    ///     - `ffmpeg_sys_next::SEEK_END` (2) - Seek relative to the end of the stream.
424    ///     - `ffmpeg_sys_next::AVSEEK_SIZE` (65536) - Query the total size of the stream
425    ///       instead of seeking.
426    ///
427    ///     `avio_seek` strips `ffmpeg_sys_next::AVSEEK_FORCE` (131072) from `whence` before
428    ///     invoking a custom callback; the example masks it anyway as cheap defense. No
429    ///     other `whence` values reach a custom seek callback.
430    ///
431    /// ### Return Value:
432    /// - Returns `Self`, allowing for method chaining.
433    ///
434    /// ### Behavior of `seek_callback`:
435    /// - **Positive Value**: The new offset position after seeking.
436    /// - **Negative Value**: An error occurred, such as:
437    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE)`: Seek is not supported.
438    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
439    ///
440    /// ### Example (Thread-safe seek callback using `Arc<Mutex<File>>`):
441    /// Since `FFmpeg` may call `read_callback` and `seek_callback` from different threads,
442    /// **use `Arc<Mutex<File>>` to ensure safe concurrent access.**
443    ///
444    /// ```rust,no_run
445    /// use ez_ffmpeg::Input;
446    /// use std::fs::File;
447    /// use std::io::{Read, Seek, SeekFrom};
448    /// use std::sync::{Arc, Mutex};
449    ///
450    /// // ✅ Wrap the file in Arc<Mutex<>> for safe shared access
451    /// let file = Arc::new(Mutex::new(File::open("test.mp4").expect("Failed to open file")));
452    ///
453    /// // ✅ Thread-safe read callback
454    /// let read_callback = {
455    ///     let file = Arc::clone(&file);
456    ///     move |buf: &mut [u8]| -> i32 {
457    ///         let mut file = file.lock().unwrap();
458    ///         match file.read(buf) {
459    ///             Ok(0) => {
460    ///                 println!("Read EOF");
461    ///                 ffmpeg_sys_next::AVERROR_EOF
462    ///             }
463    ///             Ok(bytes_read) => bytes_read as i32,
464    ///             Err(e) => {
465    ///                 println!("Read error: {}", e);
466    ///                 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)
467    ///             }
468    ///         }
469    ///     }
470    /// };
471    ///
472    /// // ✅ Thread-safe seek callback
473    /// let seek_callback = {
474    ///     let file = Arc::clone(&file);
475    ///     Box::new(move |offset: i64, whence: i32| -> i64 {
476    ///         let mut file = file.lock().unwrap();
477    ///
478    ///         // ✅ Handle AVSEEK_SIZE: FFmpeg asks for the total stream size instead of seeking
479    ///         if whence == ffmpeg_sys_next::AVSEEK_SIZE {
480    ///             if let Ok(size) = file.metadata().map(|m| m.len() as i64) {
481    ///                 return size;
482    ///             }
483    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
484    ///         }
485    ///
486    ///         // ✅ Defensive: mask AVSEEK_FORCE (avio_seek strips it before a custom callback)
487    ///         let seek_result = match whence & !ffmpeg_sys_next::AVSEEK_FORCE {
488    ///             ffmpeg_sys_next::SEEK_SET => file.seek(SeekFrom::Start(offset as u64)),
489    ///             ffmpeg_sys_next::SEEK_CUR => file.seek(SeekFrom::Current(offset)),
490    ///             ffmpeg_sys_next::SEEK_END => file.seek(SeekFrom::End(offset)),
491    ///             // The AVIO layer sends no other whence values (lseek extensions
492    ///             // like SEEK_HOLE/SEEK_DATA never reach a custom callback)
493    ///             _ => {
494    ///                 println!("Unsupported seek mode: {}", whence);
495    ///                 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
496    ///             }
497    ///         };
498    ///
499    ///         match seek_result {
500    ///             Ok(new_pos) => {
501    ///                 println!("Seek successful, new position: {}", new_pos);
502    ///                 new_pos as i64
503    ///             }
504    ///             Err(e) => {
505    ///                 println!("Seek failed: {}", e);
506    ///                 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64
507    ///             }
508    ///         }
509    ///     })
510    /// };
511    ///
512    /// let input = Input::new_by_read_callback(read_callback).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 =
519            Some(Box::new(seek_callback) as Box<dyn FnMut(i64, i32) -> i64 + Send>);
520        self
521    }
522
523    /// Replaces the entire frame-processing pipeline with a new sequence
524    /// of transformations for **post-decoding** frames on this `Input`.
525    ///
526    /// This method clears any previously set pipelines and replaces them with the provided list.
527    ///
528    /// # Parameters
529    /// * `frame_pipelines` - A list of [`FramePipeline`] instances defining the
530    ///   transformations to apply to decoded frames.
531    ///
532    /// # Returns
533    /// * `Self` - Returns the modified `Input`, enabling method chaining.
534    ///
535    /// # Example
536    /// ```rust,ignore
537    /// let input = Input::from("my_video.mp4")
538    ///     .set_frame_pipelines(vec![
539    ///         FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_VIDEO).filter("opengl", Box::new(my_filter)),
540    ///         // Additional pipelines...
541    ///     ]);
542    /// ```
543    pub fn set_frame_pipelines(mut self, frame_pipelines: Vec<impl Into<FramePipeline>>) -> Self {
544        self.frame_pipelines = Some(
545            frame_pipelines
546                .into_iter()
547                .map(|frame_pipeline| frame_pipeline.into())
548                .collect(),
549        );
550        self
551    }
552
553    /// Adds a single [`FramePipeline`] to the existing pipeline list.
554    ///
555    /// If no pipelines are currently defined, this method creates a new pipeline list.
556    /// Otherwise, it appends the provided pipeline to the existing transformations.
557    ///
558    /// # Parameters
559    /// * `frame_pipeline` - A [`FramePipeline`] defining a transformation.
560    ///
561    /// # Returns
562    /// * `Self` - Returns the modified `Input`, enabling method chaining.
563    ///
564    /// # Example
565    /// ```rust,ignore
566    /// let input = Input::from("my_video.mp4")
567    ///     .add_frame_pipeline(FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_VIDEO).filter("opengl", Box::new(my_filter)).build())
568    ///     .add_frame_pipeline(FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_AUDIO).filter("my_custom_filter1", Box::new(...)).filter("my_custom_filter2", Box::new(...)).build());
569    /// ```
570    pub fn add_frame_pipeline(mut self, frame_pipeline: impl Into<FramePipeline>) -> Self {
571        if self.frame_pipelines.is_none() {
572            self.frame_pipelines = Some(vec![frame_pipeline.into()]);
573        } else {
574            self.frame_pipelines
575                .as_mut()
576                .unwrap()
577                .push(frame_pipeline.into());
578        }
579        self
580    }
581
582    /// Sets the input format for the container or device.
583    ///
584    /// By default, if no format is specified,
585    /// FFmpeg will attempt to detect the format automatically. However, certain
586    /// use cases require specifying the format explicitly:
587    /// - Using device-specific inputs (e.g., `avfoundation` on macOS, `dshow` on Windows).
588    /// - Handling raw streams or formats that FFmpeg may not detect automatically.
589    ///
590    /// ### Parameters:
591    /// - `format`: A string specifying the desired input format (e.g., `mp4`, `flv`, `avfoundation`).
592    ///
593    /// ### Return Value:
594    /// - Returns the `Input` instance with the newly set format.
595    pub fn set_format(mut self, format: impl Into<String>) -> Self {
596        self.format = Some(format.into());
597        self
598    }
599
600    /// Sets the **video codec** to be used for decoding.
601    ///
602    /// By default, FFmpeg will automatically select an appropriate video codec
603    /// based on the input format and available decoders. However, this method
604    /// allows you to override that selection and force a specific codec.
605    ///
606    /// # Common Video Codecs:
607    /// | Codec | Description |
608    /// |-------|-------------|
609    /// | `h264` | H.264 (AVC), widely supported and efficient |
610    /// | `hevc` | H.265 (HEVC), better compression at higher complexity |
611    /// | `vp9` | VP9, open-source alternative to H.265 |
612    /// | `av1` | AV1, newer open-source codec with improved compression |
613    /// | `mpeg4` | MPEG-4 Part 2, older but still used in some cases |
614    ///
615    /// # Arguments
616    /// * `video_codec` - A string representing the desired video codec (e.g., `"h264"`, `"hevc"`).
617    ///
618    /// # Returns
619    /// * `Self` - Returns the modified `Input` struct, allowing for method chaining.
620    ///
621    /// # Example:
622    /// ```rust,ignore
623    /// let input = Input::from("video.mp4").set_video_codec("h264");
624    /// ```
625    pub fn set_video_codec(mut self, video_codec: impl Into<String>) -> Self {
626        self.video_codec = Some(video_codec.into());
627        self
628    }
629
630    /// Sets the **audio codec** to be used for decoding.
631    ///
632    /// By default, FFmpeg will automatically select an appropriate audio codec
633    /// based on the input format and available decoders. However, this method
634    /// allows you to specify a preferred codec.
635    ///
636    /// # Common Audio Codecs:
637    /// | Codec | Description |
638    /// |-------|-------------|
639    /// | `aac` | AAC, commonly used for MP4 and streaming |
640    /// | `mp3` | MP3, widely supported but lower efficiency |
641    /// | `opus` | Opus, high-quality open-source codec |
642    /// | `vorbis` | Vorbis, used in Ogg containers |
643    /// | `flac` | FLAC, lossless audio format |
644    ///
645    /// # Arguments
646    /// * `audio_codec` - A string representing the desired audio codec (e.g., `"aac"`, `"mp3"`).
647    ///
648    /// # Returns
649    /// * `Self` - Returns the modified `Input` struct, allowing for method chaining.
650    ///
651    /// # Example:
652    /// ```rust,ignore
653    /// let input = Input::from("audio.mp3").set_audio_codec("aac");
654    /// ```
655    pub fn set_audio_codec(mut self, audio_codec: impl Into<String>) -> Self {
656        self.audio_codec = Some(audio_codec.into());
657        self
658    }
659
660    /// Sets the **subtitle codec** to be used for decoding.
661    ///
662    /// By default, FFmpeg will automatically select an appropriate subtitle codec
663    /// based on the input format and available decoders. This method lets you specify
664    /// a particular subtitle codec.
665    ///
666    /// # Common Subtitle Codecs:
667    /// | Codec | Description |
668    /// |-------|-------------|
669    /// | `ass` | Advanced SubStation Alpha (ASS) subtitles |
670    /// | `srt` | SubRip Subtitle format (SRT) |
671    /// | `mov_text` | Subtitles in MP4 containers |
672    /// | `subrip` | Plain-text subtitle format |
673    ///
674    /// # Arguments
675    /// * `subtitle_codec` - A string representing the desired subtitle codec (e.g., `"mov_text"`, `"ass"`, `"srt"`).
676    ///
677    /// # Returns
678    /// * `Self` - Returns the modified `Input` struct, allowing for method chaining.
679    ///
680    /// # Example:
681    /// ```rust,ignore
682    /// let input = Input::from("movie.mkv").set_subtitle_codec("ass");
683    /// ```
684    pub fn set_subtitle_codec(mut self, subtitle_codec: impl Into<String>) -> Self {
685        self.subtitle_codec = Some(subtitle_codec.into());
686        self
687    }
688
689    /// Sets a **video codec-specific option** for decoding.
690    ///
691    /// These options control **video decoding parameters** such as frame skipping,
692    /// threading, and latency. They are applied to the video decoder before it opens.
693    ///
694    /// Note: by default ez-ffmpeg opens decoders with `threads=auto`. Providing your
695    /// own `threads` value here overrides that default instead of being overwritten.
696    ///
697    /// **Supported Parameters:**
698    /// | Parameter | Description |
699    /// |-----------|-------------|
700    /// | `skip_frame=nokey` | Decode only keyframes (fast thumbnail/scrub paths) |
701    /// | `thread_type=frame, slice` | Multithreading strategy |
702    /// | `threads=1` | Number of decoder threads (overrides the `auto` default) |
703    /// | `low_delay=1` | Reduce decoder latency for real-time streams |
704    ///
705    /// **Example Usage:**
706    /// ```rust,ignore
707    /// let input = Input::from("some_url")
708    ///     .set_video_codec_opt("skip_frame", "nokey")
709    ///     .set_video_codec_opt("threads", "1");
710    /// ```
711    pub fn set_video_codec_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
712        if let Some(ref mut opts) = self.video_codec_opts {
713            opts.insert(key.into(), value.into());
714        } else {
715            let mut opts = HashMap::new();
716            opts.insert(key.into(), value.into());
717            self.video_codec_opts = Some(opts);
718        }
719        self
720    }
721
722    /// **Sets multiple video codec options at once** for decoding.
723    ///
724    /// **Example Usage:**
725    /// ```rust,ignore
726    /// let input = Input::from("some_url")
727    ///     .set_video_codec_opts(vec![
728    ///         ("skip_frame", "nokey"),
729    ///         ("thread_type", "slice")
730    ///     ]);
731    /// ```
732    pub fn set_video_codec_opts(
733        mut self,
734        opts: Vec<(impl Into<String>, impl Into<String>)>,
735    ) -> Self {
736        let video_opts = self.video_codec_opts.get_or_insert_with(HashMap::new);
737        for (key, value) in opts {
738            video_opts.insert(key.into(), value.into());
739        }
740        self
741    }
742
743    /// Sets an **audio codec-specific option** for decoding.
744    ///
745    /// These options control **audio decoding parameters** such as threading and
746    /// codec-specific post-processing. They are applied to the audio decoder before it opens.
747    ///
748    /// Note: by default ez-ffmpeg opens decoders with `threads=auto`. Providing your
749    /// own `threads` value here overrides that default instead of being overwritten.
750    ///
751    /// **Supported Parameters:**
752    /// | Parameter | Description |
753    /// |-----------|-------------|
754    /// | `threads=1` | Number of decoder threads (overrides the `auto` default) |
755    /// | `drc_scale=0` | Disable dynamic range compression (AC-3 family) |
756    ///
757    /// **Example Usage:**
758    /// ```rust,ignore
759    /// let input = Input::from("some_url")
760    ///     .set_audio_codec_opt("drc_scale", "0");
761    /// ```
762    pub fn set_audio_codec_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
763        if let Some(ref mut opts) = self.audio_codec_opts {
764            opts.insert(key.into(), value.into());
765        } else {
766            let mut opts = HashMap::new();
767            opts.insert(key.into(), value.into());
768            self.audio_codec_opts = Some(opts);
769        }
770        self
771    }
772
773    /// **Sets multiple audio codec options at once** for decoding.
774    ///
775    /// **Example Usage:**
776    /// ```rust,ignore
777    /// let input = Input::from("some_url")
778    ///     .set_audio_codec_opts(vec![
779    ///         ("threads", "1"),
780    ///         ("drc_scale", "0")
781    ///     ]);
782    /// ```
783    pub fn set_audio_codec_opts(
784        mut self,
785        opts: Vec<(impl Into<String>, impl Into<String>)>,
786    ) -> Self {
787        let audio_opts = self.audio_codec_opts.get_or_insert_with(HashMap::new);
788        for (key, value) in opts {
789            audio_opts.insert(key.into(), value.into());
790        }
791        self
792    }
793
794    /// Sets a **subtitle codec-specific option** for decoding.
795    ///
796    /// These options control **subtitle decoding parameters** such as character
797    /// encoding. They are applied to the subtitle decoder before it opens.
798    ///
799    /// **Supported Parameters:**
800    /// | Parameter | Description |
801    /// |-----------|-------------|
802    /// | `sub_charenc=CP1252` | Character encoding of the source subtitles |
803    /// | `sub_charenc_mode=automatic` | Character-encoding detection mode |
804    ///
805    /// **Example Usage:**
806    /// ```rust,ignore
807    /// let input = Input::from("some_url")
808    ///     .set_subtitle_codec_opt("sub_charenc", "CP1252");
809    /// ```
810    pub fn set_subtitle_codec_opt(
811        mut self,
812        key: impl Into<String>,
813        value: impl Into<String>,
814    ) -> Self {
815        if let Some(ref mut opts) = self.subtitle_codec_opts {
816            opts.insert(key.into(), value.into());
817        } else {
818            let mut opts = HashMap::new();
819            opts.insert(key.into(), value.into());
820            self.subtitle_codec_opts = Some(opts);
821        }
822        self
823    }
824
825    /// **Sets multiple subtitle codec options at once** for decoding.
826    ///
827    /// **Example Usage:**
828    /// ```rust,ignore
829    /// let input = Input::from("some_url")
830    ///     .set_subtitle_codec_opts(vec![
831    ///         ("sub_charenc", "CP1252"),
832    ///         ("sub_charenc_mode", "automatic")
833    ///     ]);
834    /// ```
835    pub fn set_subtitle_codec_opts(
836        mut self,
837        opts: Vec<(impl Into<String>, impl Into<String>)>,
838    ) -> Self {
839        let subtitle_opts = self.subtitle_codec_opts.get_or_insert_with(HashMap::new);
840        for (key, value) in opts {
841            subtitle_opts.insert(key.into(), value.into());
842        }
843        self
844    }
845
846    /// Enables or disables **exit on error** behavior for the input.
847    ///
848    /// If set to `true`, FFmpeg will exit (stop processing) if it encounters any
849    /// decoding or demuxing error on this input. If set to `false` (the default),
850    /// FFmpeg may attempt to continue despite errors, skipping damaged portions.
851    ///
852    /// # Parameters
853    /// - `exit_on_error`: `true` to stop on errors, `false` to keep going.
854    ///
855    /// # Returns
856    /// * `Self` - allowing method chaining.
857    ///
858    /// # Example
859    /// ```rust,ignore
860    /// let input = Input::from("test.mp4")
861    ///     .set_exit_on_error(true);
862    /// ```
863    pub fn set_exit_on_error(mut self, exit_on_error: bool) -> Self {
864        self.exit_on_error = Some(exit_on_error);
865        self
866    }
867
868    /// Sets a **read rate** for this input, controlling how quickly frames are read.
869    ///
870    /// - If set to `1.0`, frames are read at their native frame rate.
871    /// - If set to another value (e.g., `0.5` or `2.0`), FFmpeg may attempt to read
872    ///   slower or faster, simulating changes in real-time playback speed.
873    ///
874    /// # Parameters
875    /// - `rate`: A floating-point value indicating the read rate multiplier.
876    ///
877    /// # Returns
878    /// * `Self` - allowing method chaining.
879    ///
880    /// # Example
881    /// ```rust,ignore
882    /// let input = Input::from("video.mp4")
883    ///     .set_readrate(0.5); // read at half speed
884    /// ```
885    pub fn set_readrate(mut self, rate: f32) -> Self {
886        self.readrate = Some(rate);
887        self
888    }
889
890    /// Sets a **log-level offset** for this input's decoders
891    /// (`AVCodecContext.log_level_offset`).
892    ///
893    /// FFmpeg shifts the effective level of every message a decoder emits by
894    /// this offset. Expected decoder noise — e.g. h264 `Missing reference
895    /// picture` / `decode_slice_header error` bursts right after seeking to a
896    /// non-keyframe (open GOP) — is logged at ERROR level; an offset of `8`
897    /// (one AV_LOG step) demotes those to WARNING for this input only,
898    /// without hiding errors from other inputs.
899    ///
900    /// # Arguments
901    /// * `offset` - Added to each message's log level; positive values make
902    ///   this input's decoders quieter, negative values make them louder.
903    ///
904    /// # Returns
905    /// * `Self` - allowing method chaining.
906    ///
907    /// # Example
908    /// ```rust,ignore
909    /// // Screenshot after seek: demote expected h264 reference errors.
910    /// let input = Input::from("video.mp4")
911    ///     .set_log_level_offset(8);
912    /// ```
913    pub fn set_log_level_offset(mut self, offset: i32) -> Self {
914        self.log_level_offset = Some(offset);
915        self
916    }
917
918    /// Sets the **start time** (in microseconds) from which to begin reading.
919    ///
920    /// FFmpeg will skip all data before this timestamp. This can be used to
921    /// implement “input seeking” or to only process a portion of the input.
922    ///
923    /// # Parameters
924    /// - `start_time_us`: The timestamp (in microseconds) at which to start reading.
925    ///
926    /// # Returns
927    /// * `Self` - allowing method chaining.
928    ///
929    /// # Example
930    /// ```rust,ignore
931    /// let input = Input::from("long_clip.mp4")
932    ///     .set_start_time_us(2_000_000); // Start at 2 seconds
933    /// ```
934    pub fn set_start_time_us(mut self, start_time_us: i64) -> Self {
935        self.start_time_us = Some(start_time_us);
936        self
937    }
938
939    /// Sets the **recording time** (in microseconds) for this input.
940    ///
941    /// FFmpeg will only read for the specified duration, ignoring data past this
942    /// limit. This can be used to trim or limit how much of the input is processed.
943    ///
944    /// # Parameters
945    /// - `recording_time_us`: The number of microseconds to read from the input.
946    ///
947    /// # Returns
948    /// * `Self` - allowing method chaining.
949    ///
950    /// # Example
951    /// ```rust,ignore
952    /// let input = Input::from("long_clip.mp4")
953    ///     .set_recording_time_us(5_000_000); // Only read 5 seconds
954    /// ```
955    pub fn set_recording_time_us(mut self, recording_time_us: i64) -> Self {
956        self.recording_time_us = Some(recording_time_us);
957        self
958    }
959
960    /// Sets a **stop time** (in microseconds) beyond which input data will be ignored.
961    ///
962    /// This is similar to [`set_recording_time_us`](Self::set_recording_time_us) but
963    /// specifically references an absolute timestamp in the stream. Once this timestamp
964    /// is reached, FFmpeg stops reading.
965    ///
966    /// # Parameters
967    /// - `stop_time_us`: The absolute timestamp (in microseconds) at which to stop reading.
968    ///
969    /// # Returns
970    /// * `Self` - allowing method chaining.
971    ///
972    /// # Example
973    /// ```rust,ignore
974    /// let input = Input::from("long_clip.mp4")
975    ///     .set_stop_time_us(10_000_000); // Stop reading at 10 seconds
976    /// ```
977    pub fn set_stop_time_us(mut self, stop_time_us: i64) -> Self {
978        self.stop_time_us = Some(stop_time_us);
979        self
980    }
981
982    /// Sets the number of **loops** to perform on this input stream.
983    ///
984    /// If FFmpeg reaches the end of the input, it can loop back and start from the
985    /// beginning, effectively repeating the content `stream_loop` times.
986    /// A negative value may indicate infinite looping (depending on FFmpeg’s actual behavior).
987    ///
988    /// # Parameters
989    /// - `count`: How many times to loop (e.g. `1` means one loop, `-1` might mean infinite).
990    ///
991    /// # Returns
992    /// * `Self` - allowing method chaining.
993    ///
994    /// # Example
995    /// ```rust,ignore
996    /// let input = Input::from("music.mp3")
997    ///     .set_stream_loop(2); // play the input 2 extra times
998    /// ```
999    pub fn set_stream_loop(mut self, count: i32) -> Self {
1000        self.stream_loop = Some(count);
1001        self
1002    }
1003
1004    /// Specifies a **hardware acceleration** name for decoding this input.
1005    ///
1006    /// Common values might include `"cuda"`, `"vaapi"`, `"dxva2"`, `"videotoolbox"`, etc.
1007    /// Whether it works depends on your FFmpeg build and the hardware you have available.
1008    ///
1009    /// # Parameters
1010    /// - `hwaccel_name`: A string naming the hardware accel to use.
1011    ///
1012    /// # Returns
1013    /// * `Self` - allowing method chaining.
1014    ///
1015    /// # Example
1016    /// ```rust,ignore
1017    /// let input = Input::from("video.mp4")
1018    ///     .set_hwaccel("cuda");
1019    /// ```
1020    pub fn set_hwaccel(mut self, hwaccel_name: impl Into<String>) -> Self {
1021        self.hwaccel = Some(hwaccel_name.into());
1022        self
1023    }
1024
1025    /// Selects a **hardware acceleration device** for decoding.
1026    ///
1027    /// For example, if you have multiple GPUs or want to specify a device node (like
1028    /// `"/dev/dri/renderD128"` on Linux for VAAPI), you can pass it here. This option
1029    /// must match the hardware accel you set via [`set_hwaccel`](Self::set_hwaccel) if
1030    /// you expect decoding to succeed.
1031    ///
1032    /// # Parameters
1033    /// - `device`: A string indicating the device path or identifier.
1034    ///
1035    /// # Returns
1036    /// * `Self` - allowing method chaining.
1037    ///
1038    /// # Example
1039    /// ```rust,ignore
1040    /// let input = Input::from("video.mp4")
1041    ///     .set_hwaccel("vaapi")
1042    ///     .set_hwaccel_device("/dev/dri/renderD128");
1043    /// ```
1044    pub fn set_hwaccel_device(mut self, device: impl Into<String>) -> Self {
1045        self.hwaccel_device = Some(device.into());
1046        self
1047    }
1048
1049    /// Sets the **output pixel format** to be used with hardware-accelerated decoding.
1050    ///
1051    /// Certain hardware decoders can produce various output pixel formats. This option
1052    /// lets you specify which format (e.g., `"nv12"`, `"vaapi"`, etc.) is used during
1053    /// the decode process.
1054    /// Must be compatible with the chosen hardware accel and device.
1055    ///
1056    /// # Performance: avoid a double copy on hardware transcode
1057    ///
1058    /// Modern `set_hwaccel("cuda"/"vaapi")` without this option keeps the decoder
1059    /// output at `AV_PIX_FMT_NONE`, matching the FFmpeg CLI. That is **not**
1060    /// zero-copy: every decoded frame is downloaded from the GPU to system memory,
1061    /// and a hardware encoder then uploads it right back. For a pure hardware
1062    /// pipeline (hardware decode straight into a hardware encoder such as
1063    /// `h264_nvenc`/`hevc_vaapi`, with no software filter), pair the accel with the
1064    /// device output format — `.set_hwaccel_output_format("cuda")` /
1065    /// `("vaapi")` — so frames stay device-resident and skip the download/upload
1066    /// round trip. Leave it unset when a **software** filter or encoder consumes
1067    /// the frames, or they would receive undownloaded GPU frames.
1068    ///
1069    /// # Parameters
1070    /// - `format`: A string naming the desired output pixel format (e.g. `"nv12"`).
1071    ///
1072    /// # Returns
1073    /// * `Self` - allowing method chaining.
1074    ///
1075    /// # Example
1076    /// ```rust,ignore
1077    /// // Pure GPU transcode: keep frames on the device (no double copy).
1078    /// let input = Input::from("video.mp4")
1079    ///     .set_hwaccel("cuda")
1080    ///     .set_hwaccel_output_format("cuda");
1081    /// ```
1082    pub fn set_hwaccel_output_format(mut self, format: impl Into<String>) -> Self {
1083        self.hwaccel_output_format = Some(format.into());
1084        self
1085    }
1086
1087    /// Sets a single format option for avformat_open_input — the input-side
1088    /// mirror of [`Output::set_format_opt`](crate::core::context::output::Output::set_format_opt).
1089    ///
1090    /// This method configures options that will be passed to FFmpeg's `avformat_open_input()`
1091    /// function. The options can control behavior at different levels including format detection,
1092    /// protocol handling, device configuration, and general input processing.
1093    ///
1094    /// **Example Usage:**
1095    /// ```rust,ignore
1096    /// let input = Input::new("avfoundation:0")
1097    ///     .set_format_opt("framerate", "30")
1098    ///     .set_format_opt("probesize", "5000000");
1099    /// ```
1100    ///
1101    /// ### Parameters:
1102    /// - `key`: The option name (e.g., `"framerate"`, `"probesize"`, `"timeout"`).
1103    /// - `value`: The option value (e.g., `"30"`, `"5000000"`, `"10000000"`).
1104    ///
1105    /// ### Return Value:
1106    /// - Returns the modified `Input` instance for method chaining.
1107    pub fn set_format_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1108        if let Some(ref mut opts) = self.input_opts {
1109            opts.insert(key.into(), value.into());
1110        } else {
1111            let mut opts = HashMap::new();
1112            opts.insert(key.into(), value.into());
1113            self.input_opts = Some(opts);
1114        }
1115        self
1116    }
1117
1118    /// Deprecated spelling of [`set_format_opt`](Self::set_format_opt): the
1119    /// input and output sides used different names for the same
1120    /// AVFormatContext option map.
1121    #[deprecated(since = "0.13.0", note = "renamed to `set_format_opt`")]
1122    pub fn set_input_opt(self, key: impl Into<String>, value: impl Into<String>) -> Self {
1123        self.set_format_opt(key, value)
1124    }
1125
1126    /// Sets multiple format options at once for avformat_open_input — the
1127    /// input-side mirror of
1128    /// [`Output::set_format_opts`](crate::core::context::output::Output::set_format_opts).
1129    ///
1130    /// This method allows setting multiple options in a single call, which will all be
1131    /// passed to FFmpeg's `avformat_open_input()` function. Each key-value pair will be
1132    /// inserted into the options map, overwriting any existing keys with the same name.
1133    ///
1134    /// **Example Usage:**
1135    /// ```rust,ignore
1136    /// let input = Input::new("http://example.com/stream.m3u8")
1137    ///     .set_format_opts(vec![
1138    ///         ("user_agent", "MyApp/1.0"),
1139    ///         ("timeout", "10000000"),
1140    ///         ("probesize", "5000000"),
1141    ///     ]);
1142    /// ```
1143    ///
1144    /// ### Parameters:
1145    /// - `opts`: A vector of key-value pairs representing input options.
1146    ///
1147    /// ### Return Value:
1148    /// - Returns the modified `Input` instance for method chaining.
1149    pub fn set_format_opts(mut self, opts: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
1150        if let Some(ref mut input_opts) = self.input_opts {
1151            for (key, value) in opts {
1152                input_opts.insert(key.into(), value.into());
1153            }
1154        } else {
1155            let mut input_opts = HashMap::new();
1156            for (key, value) in opts {
1157                input_opts.insert(key.into(), value.into());
1158            }
1159            self.input_opts = Some(input_opts);
1160        }
1161        self
1162    }
1163
1164    /// Deprecated spelling of [`set_format_opts`](Self::set_format_opts).
1165    #[deprecated(since = "0.13.0", note = "renamed to `set_format_opts`")]
1166    pub fn set_input_opts(self, opts: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
1167        self.set_format_opts(opts)
1168    }
1169
1170    /// Enables or disables stream-information probing (`avformat_find_stream_info`)
1171    /// after the input is opened. Enabled by default.
1172    ///
1173    /// Probing reads ahead in the input to fill stream parameters the
1174    /// container header does not carry. Disabling it cuts startup latency and
1175    /// read-ahead, which suits **low-latency or known-format inputs** (e.g. a
1176    /// live stream whose container already exposes complete stream headers).
1177    ///
1178    /// **Warning:** with probing disabled, FFmpeg only knows what the
1179    /// container header declares. Formats that reveal streams or codec
1180    /// parameters progressively (raw streams, some MPEG-TS variants) may
1181    /// yield **incomplete `codecpar`** — decoders, filters, or stream copy
1182    /// further down the pipeline can then fail or misbehave. If no stream at
1183    /// all is visible at open time, the input is rejected with
1184    /// `FindStreamError::NoStreamFound`.
1185    ///
1186    /// To shrink probing instead of skipping it, prefer
1187    /// `set_format_opt("probesize", ...)` / `set_format_opt("analyzeduration", ...)`.
1188    ///
1189    /// # Parameters
1190    /// - `enabled`: `true` to probe (default), `false` to trust the container header.
1191    ///
1192    /// # Returns
1193    /// * `Self` - allowing method chaining.
1194    ///
1195    /// # Example
1196    /// ```rust,ignore
1197    /// // Known-format low-latency ingest: skip the probing read-ahead.
1198    /// let input = Input::from("rtmp://example.com/live/stream")
1199    ///     .set_find_stream_info(false);
1200    /// ```
1201    pub fn set_find_stream_info(mut self, enabled: bool) -> Self {
1202        self.find_stream_info = enabled;
1203        self
1204    }
1205
1206    /// Sets a codec option applied to one stream's **probing** codec context
1207    /// inside `avformat_find_stream_info`.
1208    ///
1209    /// The options only affect the temporary decoders FFmpeg opens while
1210    /// probing (they can speed probing up or work around quirky streams);
1211    /// they are **not** the decode-time options — use
1212    /// [`set_video_codec_opt`](Self::set_video_codec_opt) and friends for
1213    /// those. They are ignored when probing is disabled via
1214    /// [`set_find_stream_info(false)`](Self::set_find_stream_info).
1215    ///
1216    /// # Parameters
1217    /// - `stream_index`: Index of the stream the option applies to. Must be a
1218    ///   valid index of the opened input (`< nb_streams`), otherwise opening
1219    ///   the input fails with `FindStreamError::InvalidArgument`.
1220    /// - `key`: The codec option name (e.g., `"skip_frame"`).
1221    /// - `value`: The option value (e.g., `"nokey"`).
1222    ///
1223    /// # Returns
1224    /// * `Self` - allowing method chaining.
1225    ///
1226    /// # Example
1227    /// ```rust,ignore
1228    /// let input = Input::from("video.mp4")
1229    ///     .set_find_stream_info_codec_opt(0, "skip_frame", "nokey");
1230    /// ```
1231    pub fn set_find_stream_info_codec_opt(
1232        mut self,
1233        stream_index: usize,
1234        key: impl Into<String>,
1235        value: impl Into<String>,
1236    ) -> Self {
1237        self.find_stream_info_codec_opts
1238            .get_or_insert_with(HashMap::new)
1239            .entry(stream_index)
1240            .or_default()
1241            .insert(key.into(), value.into());
1242        self
1243    }
1244
1245    /// **Sets multiple probing codec options at once** for one stream of
1246    /// `avformat_find_stream_info` (see
1247    /// [`set_find_stream_info_codec_opt`](Self::set_find_stream_info_codec_opt)).
1248    ///
1249    /// # Example
1250    /// ```rust,ignore
1251    /// let input = Input::from("video.mp4")
1252    ///     .set_find_stream_info_codec_opts(0, vec![
1253    ///         ("skip_frame", "nokey"),
1254    ///         ("lowres", "1")
1255    ///     ]);
1256    /// ```
1257    pub fn set_find_stream_info_codec_opts(
1258        mut self,
1259        stream_index: usize,
1260        opts: Vec<(impl Into<String>, impl Into<String>)>,
1261    ) -> Self {
1262        let stream_opts = self
1263            .find_stream_info_codec_opts
1264            .get_or_insert_with(HashMap::new)
1265            .entry(stream_index)
1266            .or_default();
1267        for (key, value) in opts {
1268            stream_opts.insert(key.into(), value.into());
1269        }
1270        self
1271    }
1272
1273    /// Sets whether to automatically rotate video based on display matrix metadata.
1274    ///
1275    /// When enabled (default is `true`), videos with rotation metadata (common in
1276    /// smartphone recordings) will be automatically rotated to the correct orientation
1277    /// using transpose/hflip/vflip filters.
1278    ///
1279    /// # Parameters
1280    /// - `autorotate`: `true` to enable automatic rotation (default), `false` to disable.
1281    ///
1282    /// # Returns
1283    /// * `Self` - allowing method chaining.
1284    ///
1285    /// # FFmpeg CLI equivalent
1286    /// ```bash
1287    /// ffmpeg -autorotate 0 -i input.mp4 output.mp4
1288    /// ```
1289    ///
1290    /// # Example
1291    /// ```rust,ignore
1292    /// // Disable automatic rotation to preserve original video orientation
1293    /// let input = Input::from("smartphone_video.mp4")
1294    ///     .set_autorotate(false);
1295    /// ```
1296    pub fn set_autorotate(mut self, autorotate: bool) -> Self {
1297        self.autorotate = Some(autorotate);
1298        self
1299    }
1300
1301    /// Sets a timestamp scale factor for pts/dts values.
1302    ///
1303    /// This multiplier is applied to packet timestamps after ts_offset addition.
1304    /// Default is `1.0` (no scaling). Values must be positive.
1305    ///
1306    /// This is useful for fixing videos with incorrect timestamps or for
1307    /// special timestamp manipulation scenarios.
1308    ///
1309    /// # Parameters
1310    /// - `scale`: A positive floating-point value for timestamp scaling.
1311    ///
1312    /// # Returns
1313    /// * `Self` - allowing method chaining.
1314    ///
1315    /// # FFmpeg CLI equivalent
1316    /// ```bash
1317    /// ffmpeg -itsscale 2.0 -i input.mp4 output.mp4
1318    /// ```
1319    ///
1320    /// # Example
1321    /// ```rust,ignore
1322    /// // Scale timestamps by 2x (double the playback speed effect on timestamps)
1323    /// let input = Input::from("video.mp4")
1324    ///     .set_ts_scale(2.0);
1325    /// ```
1326    ///
1327    /// # Errors
1328    /// The value is stored as given and validated when the context is built:
1329    /// `FfmpegContext::builder().build()` fails with
1330    /// [`OpenInputError::InvalidOption`](crate::error::OpenInputError::InvalidOption)
1331    /// if `scale` is not a positive finite number.
1332    pub fn set_ts_scale(mut self, scale: f64) -> Self {
1333        self.ts_scale = Some(scale);
1334        self
1335    }
1336
1337    /// Sets a forced framerate for the input video stream.
1338    ///
1339    /// When set, this overrides the default DTS estimation behavior. By default,
1340    /// ez-ffmpeg uses the actual packet duration for DTS estimation (matching FFmpeg
1341    /// CLI behavior without `-r`). Setting a framerate forces DTS estimation to use
1342    /// the specified rate instead, which snaps timestamps to a fixed frame grid.
1343    ///
1344    /// # Parameters
1345    /// - `num`: Framerate numerator (e.g., 30 for 30fps, 24000 for 23.976fps)
1346    /// - `den`: Framerate denominator (e.g., 1 for 30fps, 1001 for 23.976fps)
1347    ///
1348    /// # Returns
1349    /// * `Self` - allowing method chaining.
1350    ///
1351    /// # FFmpeg CLI equivalent
1352    /// ```bash
1353    /// ffmpeg -r 30 -i input.mp4 output.mp4
1354    /// ffmpeg -r 24000/1001 -i input.mp4 output.mp4
1355    /// ```
1356    ///
1357    /// # Example
1358    /// ```rust,ignore
1359    /// // Force 30fps framerate for DTS estimation
1360    /// let input = Input::from("video.mp4")
1361    ///     .set_framerate(30, 1);
1362    ///
1363    /// // Force 23.976fps framerate
1364    /// let input = Input::from("video.mp4")
1365    ///     .set_framerate(24000, 1001);
1366    /// ```
1367    ///
1368    /// # Errors
1369    /// The value is stored as given and validated when the context is built
1370    /// (like every other deferred option): `FfmpegContext::builder().build()`
1371    /// fails with [`OpenInputError::InvalidOption`](crate::error::OpenInputError::InvalidOption)
1372    /// if `num` or `den` is not positive.
1373    pub fn set_framerate(mut self, num: i32, den: i32) -> Self {
1374        self.framerate = Some((num, den));
1375        self
1376    }
1377}
1378
1379impl From<Box<dyn FnMut(&mut [u8]) -> i32 + Send>> for Input {
1380    fn from(read_callback: Box<dyn FnMut(&mut [u8]) -> i32 + Send>) -> Self {
1381        Self {
1382            url: None,
1383            read_callback: Some(read_callback),
1384            io_buffer_size: crate::core::context::DEFAULT_CUSTOM_IO_BUFFER_SIZE,
1385            seek_callback: None,
1386            frame_pipelines: None,
1387            format: None,
1388            video_codec: None,
1389            audio_codec: None,
1390            subtitle_codec: None,
1391            video_codec_opts: None,
1392            audio_codec_opts: None,
1393            subtitle_codec_opts: None,
1394            exit_on_error: None,
1395            readrate: None,
1396            start_time_us: None,
1397            recording_time_us: None,
1398            stop_time_us: None,
1399            stream_loop: None,
1400            hwaccel: None,
1401            hwaccel_device: None,
1402            hwaccel_output_format: None,
1403            log_level_offset: None,
1404            input_opts: None,
1405            find_stream_info: true,
1406            find_stream_info_codec_opts: None,
1407            autorotate: None,
1408            ts_scale: None,
1409            framerate: None,
1410        }
1411    }
1412}
1413
1414impl From<String> for Input {
1415    fn from(url: String) -> Self {
1416        Self {
1417            url: Some(url),
1418            read_callback: None,
1419            io_buffer_size: crate::core::context::DEFAULT_CUSTOM_IO_BUFFER_SIZE,
1420            seek_callback: None,
1421            frame_pipelines: None,
1422            format: None,
1423            video_codec: None,
1424            audio_codec: None,
1425            subtitle_codec: None,
1426            video_codec_opts: None,
1427            audio_codec_opts: None,
1428            subtitle_codec_opts: None,
1429            exit_on_error: None,
1430            readrate: None,
1431            start_time_us: None,
1432            recording_time_us: None,
1433            stop_time_us: None,
1434            stream_loop: None,
1435            hwaccel: None,
1436            hwaccel_device: None,
1437            hwaccel_output_format: None,
1438            log_level_offset: None,
1439            input_opts: None,
1440            find_stream_info: true,
1441            find_stream_info_codec_opts: None,
1442            autorotate: None,
1443            ts_scale: None,
1444            framerate: None,
1445        }
1446    }
1447}
1448
1449impl From<&str> for Input {
1450    fn from(url: &str) -> Self {
1451        Self::from(String::from(url))
1452    }
1453}
1454
1455#[cfg(test)]
1456mod tests {
1457    use crate::core::context::input::Input;
1458
1459    #[test]
1460    fn set_framerate_valid() {
1461        let input = Input::from("test.mp4").set_framerate(24000, 1001);
1462        assert_eq!(input.framerate, Some((24000, 1001)));
1463    }
1464
1465    #[test]
1466    fn set_framerate_simple() {
1467        let input = Input::from("test.mp4").set_framerate(30, 1);
1468        assert_eq!(input.framerate, Some((30, 1)));
1469    }
1470
1471    // Setters are infallible and store values as given; validation is
1472    // deferred to open time (OpenInputError::InvalidOption), where the
1473    // whole option set is checked uniformly.
1474    #[test]
1475    fn set_framerate_stores_invalid_values_for_deferred_validation() {
1476        for (num, den) in [(0, 1), (24, 0), (-1, 1), (24, -1)] {
1477            let input = Input::from("test.mp4").set_framerate(num, den);
1478            assert_eq!(input.framerate, Some((num, den)));
1479        }
1480    }
1481
1482    #[test]
1483    fn set_ts_scale_valid() {
1484        let input = Input::from("test.mp4").set_ts_scale(2.0);
1485        assert_eq!(input.ts_scale, Some(2.0));
1486    }
1487
1488    #[test]
1489    fn set_ts_scale_fractional() {
1490        let input = Input::from("test.mp4").set_ts_scale(0.5);
1491        assert_eq!(input.ts_scale, Some(0.5));
1492    }
1493
1494    #[test]
1495    fn set_ts_scale_stores_invalid_values_for_deferred_validation() {
1496        for scale in [f64::INFINITY, f64::NEG_INFINITY, 0.0, -1.0] {
1497            let input = Input::from("test.mp4").set_ts_scale(scale);
1498            assert_eq!(input.ts_scale, Some(scale));
1499        }
1500    }
1501
1502    #[test]
1503    fn set_video_codec_opt_inserts_and_overwrites() {
1504        let input = Input::from("test.mp4")
1505            .set_video_codec_opt("skip_frame", "default")
1506            .set_video_codec_opt("skip_frame", "nokey")
1507            .set_video_codec_opt("threads", "1");
1508        let opts = input.video_codec_opts.as_ref().unwrap();
1509        assert_eq!(opts.get("skip_frame").map(String::as_str), Some("nokey"));
1510        assert_eq!(opts.get("threads").map(String::as_str), Some("1"));
1511        assert!(input.audio_codec_opts.is_none());
1512        assert!(input.subtitle_codec_opts.is_none());
1513    }
1514
1515    #[test]
1516    fn set_codec_opts_bulk_merges_per_media() {
1517        let input = Input::from("test.mp4")
1518            .set_audio_codec_opt("threads", "2")
1519            .set_audio_codec_opts(vec![("drc_scale", "0"), ("threads", "1")])
1520            .set_subtitle_codec_opts(vec![("sub_charenc", "CP1252")]);
1521        let audio = input.audio_codec_opts.as_ref().unwrap();
1522        assert_eq!(audio.get("threads").map(String::as_str), Some("1"));
1523        assert_eq!(audio.get("drc_scale").map(String::as_str), Some("0"));
1524        let subtitle = input.subtitle_codec_opts.as_ref().unwrap();
1525        assert_eq!(
1526            subtitle.get("sub_charenc").map(String::as_str),
1527            Some("CP1252")
1528        );
1529        assert!(input.video_codec_opts.is_none());
1530    }
1531
1532    #[test]
1533    fn find_stream_info_defaults_to_enabled() {
1534        let input = Input::from("test.mp4");
1535        assert!(input.find_stream_info);
1536        assert!(input.find_stream_info_codec_opts.is_none());
1537
1538        let input = Input::new_by_read_callback(|_buf| 0);
1539        assert!(input.find_stream_info);
1540        assert!(input.find_stream_info_codec_opts.is_none());
1541    }
1542
1543    #[test]
1544    fn set_find_stream_info_toggles() {
1545        let input = Input::from("test.mp4").set_find_stream_info(false);
1546        assert!(!input.find_stream_info);
1547        let input = input.set_find_stream_info(true);
1548        assert!(input.find_stream_info);
1549    }
1550
1551    #[test]
1552    fn set_find_stream_info_codec_opts_merges_per_stream() {
1553        let input = Input::from("test.mp4")
1554            .set_find_stream_info_codec_opt(0, "skip_frame", "default")
1555            .set_find_stream_info_codec_opts(0, vec![("skip_frame", "nokey"), ("lowres", "1")])
1556            .set_find_stream_info_codec_opt(2, "skip_frame", "all");
1557        let opts = input.find_stream_info_codec_opts.as_ref().unwrap();
1558        assert_eq!(opts.len(), 2, "sparse stream indices stay separate entries");
1559        let stream0 = opts.get(&0).unwrap();
1560        assert_eq!(stream0.get("skip_frame").map(String::as_str), Some("nokey"));
1561        assert_eq!(stream0.get("lowres").map(String::as_str), Some("1"));
1562        assert_eq!(
1563            opts.get(&2).unwrap().get("skip_frame").map(String::as_str),
1564            Some("all")
1565        );
1566        assert!(opts.get(&1).is_none());
1567    }
1568
1569    #[test]
1570    fn test_new_by_read_callback() {
1571        let data_source = b"example custom data source".to_vec();
1572        let _input = Input::new_by_read_callback(move |buf| {
1573            let len = data_source.len().min(buf.len());
1574            buf[..len].copy_from_slice(&data_source[..len]);
1575            len as i32 // Return the number of bytes written
1576        });
1577
1578        let data_source2 = b"example custom data source2".to_vec();
1579        let _input = Input::new_by_read_callback(move |buf2| {
1580            let len = data_source2.len().min(buf2.len());
1581            buf2[..len].copy_from_slice(&data_source2[..len]);
1582            len as i32 // Return the number of bytes written
1583        });
1584    }
1585
1586    #[test]
1587    fn io_buffer_size_defaults_to_64k() {
1588        use crate::core::context::DEFAULT_CUSTOM_IO_BUFFER_SIZE;
1589        assert_eq!(DEFAULT_CUSTOM_IO_BUFFER_SIZE, 64 * 1024);
1590        assert_eq!(
1591            Input::from("test.mp4").io_buffer_size,
1592            DEFAULT_CUSTOM_IO_BUFFER_SIZE
1593        );
1594    }
1595
1596    #[test]
1597    fn set_io_buffer_size_valid() {
1598        assert_eq!(
1599            Input::from("test.mp4")
1600                .set_io_buffer_size(1 << 20)
1601                .io_buffer_size,
1602            1 << 20
1603        );
1604    }
1605
1606    #[test]
1607    fn set_io_buffer_size_stores_invalid_values_for_deferred_validation() {
1608        let input = Input::new_by_read_callback(|_| 0).set_io_buffer_size(0);
1609        assert_eq!(input.io_buffer_size, 0);
1610    }
1611}