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    /// # Parameters
1016    /// - `hwaccel_name`: A string naming the hardware accel to use.
1017    ///
1018    /// # Returns
1019    /// * `Self` - allowing method chaining.
1020    ///
1021    /// # Example
1022    /// ```rust,ignore
1023    /// let input = Input::from("video.mp4")
1024    ///     .set_hwaccel("cuda");
1025    /// ```
1026    pub fn set_hwaccel(mut self, hwaccel_name: impl Into<String>) -> Self {
1027        self.hwaccel = Some(hwaccel_name.into());
1028        self
1029    }
1030
1031    /// Selects a **hardware acceleration device** for decoding.
1032    ///
1033    /// For example, if you have multiple GPUs or want to specify a device node (like
1034    /// `"/dev/dri/renderD128"` on Linux for VAAPI), you can pass it here. This option
1035    /// must match the hardware accel you set via [`set_hwaccel`](Self::set_hwaccel) if
1036    /// you expect decoding to succeed.
1037    ///
1038    /// # Parameters
1039    /// - `device`: A string indicating the device path or identifier.
1040    ///
1041    /// # Returns
1042    /// * `Self` - allowing method chaining.
1043    ///
1044    /// # Example
1045    /// ```rust,ignore
1046    /// let input = Input::from("video.mp4")
1047    ///     .set_hwaccel("vaapi")
1048    ///     .set_hwaccel_device("/dev/dri/renderD128");
1049    /// ```
1050    pub fn set_hwaccel_device(mut self, device: impl Into<String>) -> Self {
1051        self.hwaccel_device = Some(device.into());
1052        self
1053    }
1054
1055    /// Sets the **output pixel format** to be used with hardware-accelerated decoding.
1056    ///
1057    /// Certain hardware decoders can produce various output pixel formats. This option
1058    /// lets you specify which format (e.g., `"nv12"`, `"vaapi"`, etc.) is used during
1059    /// the decode process.
1060    /// Must be compatible with the chosen hardware accel and device.
1061    ///
1062    /// # Performance: avoid a double copy on hardware transcode
1063    ///
1064    /// Modern `set_hwaccel("cuda"/"vaapi")` without this option keeps the decoder
1065    /// output at `AV_PIX_FMT_NONE`, matching the FFmpeg CLI. That is **not**
1066    /// zero-copy: every decoded frame is downloaded from the GPU to system memory,
1067    /// and a hardware encoder then uploads it right back. For a pure hardware
1068    /// pipeline (hardware decode straight into a hardware encoder such as
1069    /// `h264_nvenc`/`hevc_vaapi`, with no software filter), pair the accel with the
1070    /// device output format — `.set_hwaccel_output_format("cuda")` /
1071    /// `("vaapi")` — so frames stay device-resident and skip the download/upload
1072    /// round trip. Leave it unset when a **software** filter or encoder consumes
1073    /// the frames, or they would receive undownloaded GPU frames.
1074    ///
1075    /// # Parameters
1076    /// - `format`: A string naming the desired output pixel format (e.g. `"nv12"`).
1077    ///
1078    /// # Returns
1079    /// * `Self` - allowing method chaining.
1080    ///
1081    /// # Example
1082    /// ```rust,ignore
1083    /// // Pure GPU transcode: keep frames on the device (no double copy).
1084    /// let input = Input::from("video.mp4")
1085    ///     .set_hwaccel("cuda")
1086    ///     .set_hwaccel_output_format("cuda");
1087    /// ```
1088    pub fn set_hwaccel_output_format(mut self, format: impl Into<String>) -> Self {
1089        self.hwaccel_output_format = Some(format.into());
1090        self
1091    }
1092
1093    /// Sets a single format option for avformat_open_input — the input-side
1094    /// mirror of [`Output::set_format_opt`](crate::core::context::output::Output::set_format_opt).
1095    ///
1096    /// This method configures options that will be passed to FFmpeg's `avformat_open_input()`
1097    /// function. The options can control behavior at different levels including format detection,
1098    /// protocol handling, device configuration, and general input processing.
1099    ///
1100    /// **Example Usage:**
1101    /// ```rust,ignore
1102    /// let input = Input::new("avfoundation:0")
1103    ///     .set_format_opt("framerate", "30")
1104    ///     .set_format_opt("probesize", "5000000");
1105    /// ```
1106    ///
1107    /// ### Parameters:
1108    /// - `key`: The option name (e.g., `"framerate"`, `"probesize"`, `"timeout"`).
1109    /// - `value`: The option value (e.g., `"30"`, `"5000000"`, `"10000000"`).
1110    ///
1111    /// ### Return Value:
1112    /// - Returns the modified `Input` instance for method chaining.
1113    pub fn set_format_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
1114        if let Some(ref mut opts) = self.input_opts {
1115            opts.insert(key.into(), value.into());
1116        } else {
1117            let mut opts = HashMap::new();
1118            opts.insert(key.into(), value.into());
1119            self.input_opts = Some(opts);
1120        }
1121        self
1122    }
1123
1124    /// Deprecated spelling of [`set_format_opt`](Self::set_format_opt): the
1125    /// input and output sides used different names for the same
1126    /// AVFormatContext option map.
1127    #[deprecated(since = "0.13.0", note = "renamed to `set_format_opt`")]
1128    pub fn set_input_opt(self, key: impl Into<String>, value: impl Into<String>) -> Self {
1129        self.set_format_opt(key, value)
1130    }
1131
1132    /// Sets multiple format options at once for avformat_open_input — the
1133    /// input-side mirror of
1134    /// [`Output::set_format_opts`](crate::core::context::output::Output::set_format_opts).
1135    ///
1136    /// This method allows setting multiple options in a single call, which will all be
1137    /// passed to FFmpeg's `avformat_open_input()` function. Each key-value pair will be
1138    /// inserted into the options map, overwriting any existing keys with the same name.
1139    ///
1140    /// **Example Usage:**
1141    /// ```rust,ignore
1142    /// let input = Input::new("http://example.com/stream.m3u8")
1143    ///     .set_format_opts(vec![
1144    ///         ("user_agent", "MyApp/1.0"),
1145    ///         ("timeout", "10000000"),
1146    ///         ("probesize", "5000000"),
1147    ///     ]);
1148    /// ```
1149    ///
1150    /// ### Parameters:
1151    /// - `opts`: A vector of key-value pairs representing input options.
1152    ///
1153    /// ### Return Value:
1154    /// - Returns the modified `Input` instance for method chaining.
1155    pub fn set_format_opts(mut self, opts: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
1156        if let Some(ref mut input_opts) = self.input_opts {
1157            for (key, value) in opts {
1158                input_opts.insert(key.into(), value.into());
1159            }
1160        } else {
1161            let mut input_opts = HashMap::new();
1162            for (key, value) in opts {
1163                input_opts.insert(key.into(), value.into());
1164            }
1165            self.input_opts = Some(input_opts);
1166        }
1167        self
1168    }
1169
1170    /// Deprecated spelling of [`set_format_opts`](Self::set_format_opts).
1171    #[deprecated(since = "0.13.0", note = "renamed to `set_format_opts`")]
1172    pub fn set_input_opts(self, opts: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
1173        self.set_format_opts(opts)
1174    }
1175
1176    /// Enables or disables stream-information probing (`avformat_find_stream_info`)
1177    /// after the input is opened. Enabled by default.
1178    ///
1179    /// Probing reads ahead in the input to fill stream parameters the
1180    /// container header does not carry. Disabling it cuts startup latency and
1181    /// read-ahead, which suits **low-latency or known-format inputs** (e.g. a
1182    /// live stream whose container already exposes complete stream headers).
1183    ///
1184    /// **Warning:** with probing disabled, FFmpeg only knows what the
1185    /// container header declares. Formats that reveal streams or codec
1186    /// parameters progressively (raw streams, some MPEG-TS variants) may
1187    /// yield **incomplete `codecpar`** — decoders, filters, or stream copy
1188    /// further down the pipeline can then fail or misbehave. If no stream at
1189    /// all is visible at open time, the input is rejected with
1190    /// `FindStreamError::NoStreamFound`.
1191    ///
1192    /// To shrink probing instead of skipping it, prefer
1193    /// `set_format_opt("probesize", ...)` / `set_format_opt("analyzeduration", ...)`.
1194    ///
1195    /// # Parameters
1196    /// - `enabled`: `true` to probe (default), `false` to trust the container header.
1197    ///
1198    /// # Returns
1199    /// * `Self` - allowing method chaining.
1200    ///
1201    /// # Example
1202    /// ```rust,ignore
1203    /// // Known-format low-latency ingest: skip the probing read-ahead.
1204    /// let input = Input::from("rtmp://example.com/live/stream")
1205    ///     .set_find_stream_info(false);
1206    /// ```
1207    pub fn set_find_stream_info(mut self, enabled: bool) -> Self {
1208        self.find_stream_info = enabled;
1209        self
1210    }
1211
1212    /// Sets a codec option applied to one stream's **probing** codec context
1213    /// inside `avformat_find_stream_info`.
1214    ///
1215    /// The options only affect the temporary decoders FFmpeg opens while
1216    /// probing (they can speed probing up or work around quirky streams);
1217    /// they are **not** the decode-time options — use
1218    /// [`set_video_codec_opt`](Self::set_video_codec_opt) and friends for
1219    /// those. They are ignored when probing is disabled via
1220    /// [`set_find_stream_info(false)`](Self::set_find_stream_info).
1221    ///
1222    /// # Parameters
1223    /// - `stream_index`: Index of the stream the option applies to. Must be a
1224    ///   valid index of the opened input (`< nb_streams`), otherwise opening
1225    ///   the input fails with `FindStreamError::InvalidArgument`.
1226    /// - `key`: The codec option name (e.g., `"skip_frame"`).
1227    /// - `value`: The option value (e.g., `"nokey"`).
1228    ///
1229    /// # Returns
1230    /// * `Self` - allowing method chaining.
1231    ///
1232    /// # Example
1233    /// ```rust,ignore
1234    /// let input = Input::from("video.mp4")
1235    ///     .set_find_stream_info_codec_opt(0, "skip_frame", "nokey");
1236    /// ```
1237    pub fn set_find_stream_info_codec_opt(
1238        mut self,
1239        stream_index: usize,
1240        key: impl Into<String>,
1241        value: impl Into<String>,
1242    ) -> Self {
1243        self.find_stream_info_codec_opts
1244            .get_or_insert_with(HashMap::new)
1245            .entry(stream_index)
1246            .or_default()
1247            .insert(key.into(), value.into());
1248        self
1249    }
1250
1251    /// **Sets multiple probing codec options at once** for one stream of
1252    /// `avformat_find_stream_info` (see
1253    /// [`set_find_stream_info_codec_opt`](Self::set_find_stream_info_codec_opt)).
1254    ///
1255    /// # Example
1256    /// ```rust,ignore
1257    /// let input = Input::from("video.mp4")
1258    ///     .set_find_stream_info_codec_opts(0, vec![
1259    ///         ("skip_frame", "nokey"),
1260    ///         ("lowres", "1")
1261    ///     ]);
1262    /// ```
1263    pub fn set_find_stream_info_codec_opts(
1264        mut self,
1265        stream_index: usize,
1266        opts: Vec<(impl Into<String>, impl Into<String>)>,
1267    ) -> Self {
1268        let stream_opts = self
1269            .find_stream_info_codec_opts
1270            .get_or_insert_with(HashMap::new)
1271            .entry(stream_index)
1272            .or_default();
1273        for (key, value) in opts {
1274            stream_opts.insert(key.into(), value.into());
1275        }
1276        self
1277    }
1278
1279    /// Sets whether to automatically rotate video based on display matrix metadata.
1280    ///
1281    /// When enabled (default is `true`), videos with rotation metadata (common in
1282    /// smartphone recordings) will be automatically rotated to the correct orientation
1283    /// using transpose/hflip/vflip filters.
1284    ///
1285    /// # Parameters
1286    /// - `autorotate`: `true` to enable automatic rotation (default), `false` to disable.
1287    ///
1288    /// # Returns
1289    /// * `Self` - allowing method chaining.
1290    ///
1291    /// # FFmpeg CLI equivalent
1292    /// ```bash
1293    /// ffmpeg -autorotate 0 -i input.mp4 output.mp4
1294    /// ```
1295    ///
1296    /// # Example
1297    /// ```rust,ignore
1298    /// // Disable automatic rotation to preserve original video orientation
1299    /// let input = Input::from("smartphone_video.mp4")
1300    ///     .set_autorotate(false);
1301    /// ```
1302    pub fn set_autorotate(mut self, autorotate: bool) -> Self {
1303        self.autorotate = Some(autorotate);
1304        self
1305    }
1306
1307    /// Sets a timestamp scale factor for pts/dts values.
1308    ///
1309    /// This multiplier is applied to packet timestamps after ts_offset addition.
1310    /// Default is `1.0` (no scaling). Values must be positive.
1311    ///
1312    /// This is useful for fixing videos with incorrect timestamps or for
1313    /// special timestamp manipulation scenarios.
1314    ///
1315    /// # Parameters
1316    /// - `scale`: A positive floating-point value for timestamp scaling.
1317    ///
1318    /// # Returns
1319    /// * `Self` - allowing method chaining.
1320    ///
1321    /// # FFmpeg CLI equivalent
1322    /// ```bash
1323    /// ffmpeg -itsscale 2.0 -i input.mp4 output.mp4
1324    /// ```
1325    ///
1326    /// # Example
1327    /// ```rust,ignore
1328    /// // Scale timestamps by 2x (double the playback speed effect on timestamps)
1329    /// let input = Input::from("video.mp4")
1330    ///     .set_ts_scale(2.0);
1331    /// ```
1332    ///
1333    /// # Errors
1334    /// The value is stored as given and validated when the context is built:
1335    /// `FfmpegContext::builder().build()` fails with
1336    /// [`OpenInputError::InvalidOption`](crate::error::OpenInputError::InvalidOption)
1337    /// if `scale` is not a positive finite number.
1338    pub fn set_ts_scale(mut self, scale: f64) -> Self {
1339        self.ts_scale = Some(scale);
1340        self
1341    }
1342
1343    /// Sets a forced framerate for the input video stream.
1344    ///
1345    /// When set, this overrides the default DTS estimation behavior. By default,
1346    /// ez-ffmpeg uses the actual packet duration for DTS estimation (matching FFmpeg
1347    /// CLI behavior without `-r`). Setting a framerate forces DTS estimation to use
1348    /// the specified rate instead, which snaps timestamps to a fixed frame grid.
1349    ///
1350    /// # Parameters
1351    /// - `num`: Framerate numerator (e.g., 30 for 30fps, 24000 for 23.976fps)
1352    /// - `den`: Framerate denominator (e.g., 1 for 30fps, 1001 for 23.976fps)
1353    ///
1354    /// # Returns
1355    /// * `Self` - allowing method chaining.
1356    ///
1357    /// # FFmpeg CLI equivalent
1358    /// ```bash
1359    /// ffmpeg -r 30 -i input.mp4 output.mp4
1360    /// ffmpeg -r 24000/1001 -i input.mp4 output.mp4
1361    /// ```
1362    ///
1363    /// # Example
1364    /// ```rust,ignore
1365    /// // Force 30fps framerate for DTS estimation
1366    /// let input = Input::from("video.mp4")
1367    ///     .set_framerate(30, 1);
1368    ///
1369    /// // Force 23.976fps framerate
1370    /// let input = Input::from("video.mp4")
1371    ///     .set_framerate(24000, 1001);
1372    /// ```
1373    ///
1374    /// # Errors
1375    /// The value is stored as given and validated when the context is built
1376    /// (like every other deferred option): `FfmpegContext::builder().build()`
1377    /// fails with [`OpenInputError::InvalidOption`](crate::error::OpenInputError::InvalidOption)
1378    /// if `num` or `den` is not positive.
1379    pub fn set_framerate(mut self, num: i32, den: i32) -> Self {
1380        self.framerate = Some((num, den));
1381        self
1382    }
1383}
1384
1385impl From<Box<dyn FnMut(&mut [u8]) -> i32 + Send>> for Input {
1386    fn from(read_callback: Box<dyn FnMut(&mut [u8]) -> i32 + Send>) -> Self {
1387        Self {
1388            url: None,
1389            read_callback: Some(read_callback),
1390            io_buffer_size: crate::core::context::DEFAULT_CUSTOM_IO_BUFFER_SIZE,
1391            seek_callback: None,
1392            frame_pipelines: None,
1393            format: None,
1394            video_codec: None,
1395            audio_codec: None,
1396            subtitle_codec: None,
1397            video_codec_opts: None,
1398            audio_codec_opts: None,
1399            subtitle_codec_opts: None,
1400            exit_on_error: None,
1401            readrate: None,
1402            strict_avoptions: false,
1403            start_time_us: None,
1404            recording_time_us: None,
1405            stop_time_us: None,
1406            stream_loop: None,
1407            hwaccel: None,
1408            hwaccel_device: None,
1409            hwaccel_output_format: None,
1410            log_level_offset: None,
1411            input_opts: None,
1412            find_stream_info: true,
1413            find_stream_info_codec_opts: None,
1414            autorotate: None,
1415            ts_scale: None,
1416            framerate: None,
1417        }
1418    }
1419}
1420
1421impl From<String> for Input {
1422    fn from(url: String) -> Self {
1423        Self {
1424            url: Some(url),
1425            read_callback: None,
1426            io_buffer_size: crate::core::context::DEFAULT_CUSTOM_IO_BUFFER_SIZE,
1427            seek_callback: None,
1428            frame_pipelines: None,
1429            format: None,
1430            video_codec: None,
1431            audio_codec: None,
1432            subtitle_codec: None,
1433            video_codec_opts: None,
1434            audio_codec_opts: None,
1435            subtitle_codec_opts: None,
1436            exit_on_error: None,
1437            readrate: None,
1438            strict_avoptions: false,
1439            start_time_us: None,
1440            recording_time_us: None,
1441            stop_time_us: None,
1442            stream_loop: None,
1443            hwaccel: None,
1444            hwaccel_device: None,
1445            hwaccel_output_format: None,
1446            log_level_offset: None,
1447            input_opts: None,
1448            find_stream_info: true,
1449            find_stream_info_codec_opts: None,
1450            autorotate: None,
1451            ts_scale: None,
1452            framerate: None,
1453        }
1454    }
1455}
1456
1457impl From<&str> for Input {
1458    fn from(url: &str) -> Self {
1459        Self::from(String::from(url))
1460    }
1461}
1462
1463#[cfg(test)]
1464mod tests {
1465    use crate::core::context::input::Input;
1466
1467    #[test]
1468    fn set_framerate_valid() {
1469        let input = Input::from("test.mp4").set_framerate(24000, 1001);
1470        assert_eq!(input.framerate, Some((24000, 1001)));
1471    }
1472
1473    #[test]
1474    fn set_framerate_simple() {
1475        let input = Input::from("test.mp4").set_framerate(30, 1);
1476        assert_eq!(input.framerate, Some((30, 1)));
1477    }
1478
1479    // Setters are infallible and store values as given; validation is
1480    // deferred to open time (OpenInputError::InvalidOption), where the
1481    // whole option set is checked uniformly.
1482    #[test]
1483    fn set_framerate_stores_invalid_values_for_deferred_validation() {
1484        for (num, den) in [(0, 1), (24, 0), (-1, 1), (24, -1)] {
1485            let input = Input::from("test.mp4").set_framerate(num, den);
1486            assert_eq!(input.framerate, Some((num, den)));
1487        }
1488    }
1489
1490    #[test]
1491    fn set_ts_scale_valid() {
1492        let input = Input::from("test.mp4").set_ts_scale(2.0);
1493        assert_eq!(input.ts_scale, Some(2.0));
1494    }
1495
1496    #[test]
1497    fn set_ts_scale_fractional() {
1498        let input = Input::from("test.mp4").set_ts_scale(0.5);
1499        assert_eq!(input.ts_scale, Some(0.5));
1500    }
1501
1502    #[test]
1503    fn set_ts_scale_stores_invalid_values_for_deferred_validation() {
1504        for scale in [f64::INFINITY, f64::NEG_INFINITY, 0.0, -1.0] {
1505            let input = Input::from("test.mp4").set_ts_scale(scale);
1506            assert_eq!(input.ts_scale, Some(scale));
1507        }
1508    }
1509
1510    #[test]
1511    fn set_video_codec_opt_inserts_and_overwrites() {
1512        let input = Input::from("test.mp4")
1513            .set_video_codec_opt("skip_frame", "default")
1514            .set_video_codec_opt("skip_frame", "nokey")
1515            .set_video_codec_opt("threads", "1");
1516        let opts = input.video_codec_opts.as_ref().unwrap();
1517        assert_eq!(opts.get("skip_frame").map(String::as_str), Some("nokey"));
1518        assert_eq!(opts.get("threads").map(String::as_str), Some("1"));
1519        assert!(input.audio_codec_opts.is_none());
1520        assert!(input.subtitle_codec_opts.is_none());
1521    }
1522
1523    #[test]
1524    fn set_codec_opts_bulk_merges_per_media() {
1525        let input = Input::from("test.mp4")
1526            .set_audio_codec_opt("threads", "2")
1527            .set_audio_codec_opts(vec![("drc_scale", "0"), ("threads", "1")])
1528            .set_subtitle_codec_opts(vec![("sub_charenc", "CP1252")]);
1529        let audio = input.audio_codec_opts.as_ref().unwrap();
1530        assert_eq!(audio.get("threads").map(String::as_str), Some("1"));
1531        assert_eq!(audio.get("drc_scale").map(String::as_str), Some("0"));
1532        let subtitle = input.subtitle_codec_opts.as_ref().unwrap();
1533        assert_eq!(
1534            subtitle.get("sub_charenc").map(String::as_str),
1535            Some("CP1252")
1536        );
1537        assert!(input.video_codec_opts.is_none());
1538    }
1539
1540    #[test]
1541    fn find_stream_info_defaults_to_enabled() {
1542        let input = Input::from("test.mp4");
1543        assert!(input.find_stream_info);
1544        assert!(input.find_stream_info_codec_opts.is_none());
1545
1546        let input = Input::new_by_read_callback(|_buf| 0);
1547        assert!(input.find_stream_info);
1548        assert!(input.find_stream_info_codec_opts.is_none());
1549    }
1550
1551    #[test]
1552    fn set_find_stream_info_toggles() {
1553        let input = Input::from("test.mp4").set_find_stream_info(false);
1554        assert!(!input.find_stream_info);
1555        let input = input.set_find_stream_info(true);
1556        assert!(input.find_stream_info);
1557    }
1558
1559    #[test]
1560    fn set_find_stream_info_codec_opts_merges_per_stream() {
1561        let input = Input::from("test.mp4")
1562            .set_find_stream_info_codec_opt(0, "skip_frame", "default")
1563            .set_find_stream_info_codec_opts(0, vec![("skip_frame", "nokey"), ("lowres", "1")])
1564            .set_find_stream_info_codec_opt(2, "skip_frame", "all");
1565        let opts = input.find_stream_info_codec_opts.as_ref().unwrap();
1566        assert_eq!(opts.len(), 2, "sparse stream indices stay separate entries");
1567        let stream0 = opts.get(&0).unwrap();
1568        assert_eq!(stream0.get("skip_frame").map(String::as_str), Some("nokey"));
1569        assert_eq!(stream0.get("lowres").map(String::as_str), Some("1"));
1570        assert_eq!(
1571            opts.get(&2).unwrap().get("skip_frame").map(String::as_str),
1572            Some("all")
1573        );
1574        assert!(opts.get(&1).is_none());
1575    }
1576
1577    #[test]
1578    fn test_new_by_read_callback() {
1579        let data_source = b"example custom data source".to_vec();
1580        let _input = Input::new_by_read_callback(move |buf| {
1581            let len = data_source.len().min(buf.len());
1582            buf[..len].copy_from_slice(&data_source[..len]);
1583            len as i32 // Return the number of bytes written
1584        });
1585
1586        let data_source2 = b"example custom data source2".to_vec();
1587        let _input = Input::new_by_read_callback(move |buf2| {
1588            let len = data_source2.len().min(buf2.len());
1589            buf2[..len].copy_from_slice(&data_source2[..len]);
1590            len as i32 // Return the number of bytes written
1591        });
1592    }
1593
1594    #[test]
1595    fn io_buffer_size_defaults_to_64k() {
1596        use crate::core::context::DEFAULT_CUSTOM_IO_BUFFER_SIZE;
1597        assert_eq!(DEFAULT_CUSTOM_IO_BUFFER_SIZE, 64 * 1024);
1598        assert_eq!(
1599            Input::from("test.mp4").io_buffer_size,
1600            DEFAULT_CUSTOM_IO_BUFFER_SIZE
1601        );
1602    }
1603
1604    #[test]
1605    fn set_io_buffer_size_valid() {
1606        assert_eq!(
1607            Input::from("test.mp4")
1608                .set_io_buffer_size(1 << 20)
1609                .io_buffer_size,
1610            1 << 20
1611        );
1612    }
1613
1614    #[test]
1615    fn set_io_buffer_size_stores_invalid_values_for_deferred_validation() {
1616        let input = Input::new_by_read_callback(|_| 0).set_io_buffer_size(0);
1617        assert_eq!(input.io_buffer_size, 0);
1618    }
1619}