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