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