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