Skip to main content

ez_ffmpeg/core/context/
input.rs

1use std::collections::HashMap;
2use crate::filter::frame_pipeline::FramePipeline;
3
4// Note: Input is Send if all callback fields are Send.
5// We require `+ Send` on callback types to ensure this.
6// Input is !Sync because FnMut callbacks require exclusive access.
7
8pub struct Input {
9    /// The URL of the input source.
10    ///
11    /// This specifies the source from which the input stream is obtained. It can be:
12    /// - A local file path (e.g., `file:///path/to/video.mp4`).
13    /// - A network stream (e.g., `rtmp://example.com/live/stream`).
14    /// - Any other URL supported by FFmpeg (e.g., `http://example.com/video.mp4`, `udp://...`).
15    ///
16    /// The URL must be valid. If the URL is invalid or unsupported,
17    /// the library will return an error when attempting to open the input stream.
18    pub(crate) url: Option<String>,
19
20    /// A callback function for custom data reading.
21    ///
22    /// The `read_callback` function allows you to provide custom logic for feeding data into
23    /// the input stream. This is useful for scenarios where the input does not come directly
24    /// from a standard source (like a file or URL), but instead from a custom data source,
25    /// such as an in-memory buffer or a custom network stream.
26    ///
27    /// ### Parameters:
28    /// - `buf: &mut [u8]`: A mutable buffer into which the data should be written.
29    ///   The callback should fill this buffer with as much data as possible, up to its length.
30    ///
31    /// ### Return Value:
32    /// - **Positive Value**: The number of bytes successfully read into `buf`.
33    /// - **`ffmpeg_sys_next::AVERROR_EOF`**: Indicates the end of the input stream. No more data will be read.
34    /// - **Negative Value**: Indicates an error occurred, such as:
35    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
36    ///   - Custom-defined error codes depending on your implementation.
37    ///
38    /// ### Example:
39    /// ```rust,ignore
40    /// fn custom_read_callback(buf: &mut [u8]) -> i32 {
41    ///     let data = b"example data stream";
42    ///     let len = data.len().min(buf.len());
43    ///     buf[..len].copy_from_slice(&data[..len]);
44    ///     len as i32 // Return the number of bytes written into the buffer
45    /// }
46    /// ```
47    pub(crate) read_callback: Option<Box<dyn FnMut(&mut [u8]) -> i32 + Send>>,
48
49    /// A callback function for custom seeking within the input stream.
50    ///
51    /// The `seek_callback` function allows defining custom seeking behavior.
52    /// This is useful for data sources that support seeking, such as files or memory-mapped data.
53    /// For non-seekable streams (e.g., live network streams), this function may return an error.
54    ///
55    /// **FFmpeg may invoke `seek_callback` from multiple threads, so thread safety is required.**
56    /// When using a `File` as an input source, **use `Arc<Mutex<File>>` to ensure safe access.**
57    ///
58    /// ### Parameters:
59    /// - `offset: i64`: The target position in the stream for seeking.
60    /// - `whence: i32`: The seek mode defining how the `offset` should be interpreted:
61    ///   - `ffmpeg_sys_next::SEEK_SET` (0): Seek to an absolute position.
62    ///   - `ffmpeg_sys_next::SEEK_CUR` (1): Seek relative to the current position.
63    ///   - `ffmpeg_sys_next::SEEK_END` (2): Seek relative to the end of the stream.
64    ///   - `ffmpeg_sys_next::SEEK_HOLE` (3): Find the next file hole (sparse file support).
65    ///   - `ffmpeg_sys_next::SEEK_DATA` (4): Find the next data block (sparse file support).
66    ///   - `ffmpeg_sys_next::AVSEEK_FLAG_BYTE` (2): Seek using **byte offsets** instead of timestamps.
67    ///   - `ffmpeg_sys_next::AVSEEK_SIZE` (65536): Query the **total size** of the stream.
68    ///   - `ffmpeg_sys_next::AVSEEK_FORCE` (131072): **Force seeking even if normally restricted.**
69    ///
70    /// ### Return Value:
71    /// - **Positive Value**: The new offset position after seeking.
72    /// - **Negative Value**: An error occurred. Common errors include:
73    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE)`: Seek is not supported.
74    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
75    ///
76    /// ### Example (Handling multi-threaded access safely with `Arc<Mutex<File>>`):
77    /// Since FFmpeg may call `read_callback` and `seek_callback` from different threads,
78    /// **`Arc<Mutex<File>>` is used to ensure safe access across threads.**
79    ///
80    /// ```rust,ignore
81    /// use std::fs::File;
82    /// use std::io::{Seek, SeekFrom};
83    /// use std::sync::{Arc, Mutex};
84    ///
85    /// let file = Arc::new(Mutex::new(File::open("test.mp4").expect("Failed to open file")));
86    ///
87    /// let seek_callback = {
88    ///     let file = Arc::clone(&file);
89    ///     Box::new(move |offset: i64, whence: i32| -> i64 {
90    ///         let mut file = file.lock().unwrap(); // Acquire lock
91    ///
92    ///         // ✅ Handle AVSEEK_SIZE: Return total file size
93    ///         if whence == ffmpeg_sys_next::AVSEEK_SIZE {
94    ///             if let Ok(size) = file.metadata().map(|m| m.len() as i64) {
95    ///                 println!("FFmpeg requested stream size: {}", size);
96    ///                 return size;
97    ///             }
98    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
99    ///         }
100    ///
101    ///         // ✅ Handle AVSEEK_FORCE: Ignore this flag when processing seek
102    ///         let actual_whence = whence & !ffmpeg_sys_next::AVSEEK_FORCE;
103    ///
104    ///         // ✅ Handle AVSEEK_FLAG_BYTE: Perform byte-based seek
105    ///         if actual_whence & ffmpeg_sys_next::AVSEEK_FLAG_BYTE != 0 {
106    ///             println!("FFmpeg requested byte-based seeking. Seeking to byte offset: {}", offset);
107    ///             if let Ok(new_pos) = file.seek(SeekFrom::Start(offset as u64)) {
108    ///                 return new_pos as i64;
109    ///             }
110    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
111    ///         }
112    ///
113    ///         // ✅ Handle SEEK_HOLE and SEEK_DATA (Linux only)
114    ///         #[cfg(target_os = "linux")]
115    ///         if actual_whence == ffmpeg_sys_next::SEEK_HOLE {
116    ///             println!("FFmpeg requested SEEK_HOLE, but Rust std::fs does not support it.");
117    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
118    ///         }
119    ///         #[cfg(target_os = "linux")]
120    ///         if actual_whence == ffmpeg_sys_next::SEEK_DATA {
121    ///             println!("FFmpeg requested SEEK_DATA, but Rust std::fs does not support it.");
122    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
123    ///         }
124    ///
125    ///         // ✅ Standard seek modes
126    ///         let seek_result = match actual_whence {
127    ///             ffmpeg_sys_next::SEEK_SET => file.seek(SeekFrom::Start(offset as u64)),
128    ///             ffmpeg_sys_next::SEEK_CUR => file.seek(SeekFrom::Current(offset)),
129    ///             ffmpeg_sys_next::SEEK_END => file.seek(SeekFrom::End(offset)),
130    ///             _ => {
131    ///                 println!("Unsupported seek mode: {}", whence);
132    ///                 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
133    ///             }
134    ///         };
135    ///
136    ///         match seek_result {
137    ///             Ok(new_pos) => {
138    ///                 println!("Seek successful, new position: {}", new_pos);
139    ///                 new_pos as i64
140    ///             }
141    ///             Err(e) => {
142    ///                 println!("Seek failed: {}", e);
143    ///                 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64
144    ///             }
145    ///         }
146    ///     })
147    /// };
148    /// ```
149    pub(crate) seek_callback: Option<Box<dyn FnMut(i64, i32) -> i64 + Send>>,
150
151    /// The pipeline that provides custom processing for decoded frames.
152    ///
153    /// After the input data is decoded into `Frame` objects, these frames
154    /// are passed through the `frame_pipeline`. Each frame goes through
155    /// a series of `FrameFilter` objects in the pipeline, allowing for
156    /// customized processing (e.g., filtering, transformation, etc.).
157    ///
158    /// If `None`, no processing pipeline is applied to the decoded frames.
159    pub(crate) frame_pipelines: Option<Vec<FramePipeline>>,
160
161    /// The input format for the source.
162    ///
163    /// This field specifies which container or device format FFmpeg should use to read the input.
164    /// If `None`, FFmpeg will attempt to automatically detect the format based on the source URL,
165    /// file extension, or stream data.
166    ///
167    /// You might need to specify a format explicitly in cases where automatic detection fails or
168    /// when you must force a particular format. For example:
169    /// - When capturing from a specific device on macOS (using `avfoundation`).
170    /// - When capturing on Windows devices (using `dshow`).
171    /// - When dealing with raw streams or unusual data sources.
172    pub(crate) format: Option<String>,
173
174    /// The codec to be used for **video** decoding.
175    ///
176    /// If set, this forces FFmpeg to use the specified video codec for decoding.
177    /// Otherwise, FFmpeg will attempt to auto-detect the best available codec.
178    pub(crate) video_codec: Option<String>,
179
180    /// The codec to be used for **audio** decoding.
181    ///
182    /// If set, this forces FFmpeg to use the specified audio codec for decoding.
183    /// Otherwise, FFmpeg will attempt to auto-detect the best available codec.
184    pub(crate) audio_codec: Option<String>,
185
186    /// The codec to be used for **subtitle** decoding.
187    ///
188    /// If set, this forces FFmpeg to use the specified subtitle codec for decoding.
189    /// Otherwise, FFmpeg will attempt to auto-detect the best available codec.
190    pub(crate) subtitle_codec: Option<String>,
191
192    pub(crate) exit_on_error: Option<bool>,
193
194    /// read input at specified rate.
195    /// when set 1. read input at native frame rate.
196    pub(crate) readrate: Option<f32>,
197    pub(crate) start_time_us: Option<i64>,
198    pub(crate) recording_time_us: Option<i64>,
199    pub(crate) stop_time_us: Option<i64>,
200
201    /// set number of times input stream shall be looped
202    pub(crate) stream_loop: Option<i32>,
203
204    /// Hardware Acceleration name
205    /// use Hardware accelerated decoding
206    pub(crate) hwaccel: Option<String>,
207    /// select a device for HW acceleration
208    pub(crate) hwaccel_device: Option<String>,
209    /// select output format used with HW accelerated decoding
210    pub(crate) hwaccel_output_format: Option<String>,
211
212    /// Log-level offset applied to this input's decoders
213    /// (`AVCodecContext.log_level_offset`).
214    pub(crate) log_level_offset: Option<i32>,
215
216    /// Input options for avformat_open_input.
217    ///
218    /// This field stores options that are passed to FFmpeg's `avformat_open_input()` function.
219    /// These options can affect different layers of the input processing pipeline:
220    ///
221    /// **Format/Demuxer options:**
222    /// - `probesize` - Maximum data to probe for format detection
223    /// - `analyzeduration` - Duration to analyze for stream info
224    /// - `fflags` - Format flags (e.g., "+genpts")
225    ///
226    /// **Protocol options:**
227    /// - `user_agent` - HTTP User-Agent header
228    /// - `timeout` - Network timeout in microseconds
229    /// - `headers` - Custom HTTP headers
230    ///
231    /// **Device options:**
232    /// - `framerate` - Input framerate (for avfoundation, dshow, etc.)
233    /// - `video_size` - Input video resolution
234    /// - `pixel_format` - Input pixel format
235    ///
236    /// **General input options:**
237    /// - `thread_queue_size` - Input thread queue size
238    /// - `re` - Read input at native frame rate
239    ///
240    /// These options allow fine-tuning of input behavior across different components
241    /// of the FFmpeg input pipeline.
242    pub(crate) input_opts: Option<HashMap<String, String>>,
243
244    /// Automatically rotate video based on display matrix metadata.
245    ///
246    /// When enabled (default), videos with rotation metadata (common in smartphone
247    /// recordings) will be automatically rotated to the correct orientation using
248    /// transpose/hflip/vflip filters.
249    ///
250    /// Set to `false` to disable automatic rotation and preserve the original
251    /// video orientation.
252    ///
253    /// ## FFmpeg CLI equivalent
254    /// ```bash
255    /// # Disable autorotate
256    /// ffmpeg -autorotate 0 -i input.mp4 output.mp4
257    ///
258    /// # Enable autorotate (default)
259    /// ffmpeg -autorotate 1 -i input.mp4 output.mp4
260    /// ```
261    ///
262    /// ## FFmpeg source reference (FFmpeg 7.x)
263    /// - Default value: `ffmpeg_demux.c:1270` (`ds->autorotate = 1`)
264    /// - Flag setting: `ffmpeg_demux.c:1088` (`IFILTER_FLAG_AUTOROTATE`)
265    /// - Filter insertion: `ffmpeg_filter.c:1744-1778`
266    pub(crate) autorotate: Option<bool>,
267
268    /// Timestamp scale factor for pts/dts values.
269    ///
270    /// This multiplier is applied to packet timestamps after ts_offset addition.
271    /// Default is 1.0 (no scaling). Values must be positive.
272    ///
273    /// This is useful for fixing videos with incorrect timestamps or for
274    /// special timestamp manipulation scenarios.
275    ///
276    /// ## FFmpeg CLI equivalent
277    /// ```bash
278    /// # Scale timestamps by 2x
279    /// ffmpeg -itsscale 2.0 -i input.mp4 output.mp4
280    ///
281    /// # Scale timestamps by 0.5x (half speed effect on timestamps)
282    /// ffmpeg -itsscale 0.5 -i input.mp4 output.mp4
283    /// ```
284    ///
285    /// ## FFmpeg source reference (FFmpeg 7.x)
286    /// - Default value: `ffmpeg_demux.c:1267` (`ds->ts_scale = 1.0`)
287    /// - Application: `ffmpeg_demux.c:404-406` (applied after ts_offset)
288    pub(crate) ts_scale: Option<f64>,
289
290    /// Forced framerate for the input video stream.
291    ///
292    /// When set, this overrides the DTS estimation logic to use the specified
293    /// framerate for computing `next_dts` in the video stream. By default (None),
294    /// the actual packet duration is used for DTS estimation, matching FFmpeg CLI
295    /// behavior when `-r` is not specified.
296    ///
297    /// This affects all video DTS estimation, including recording_time cutoff
298    /// decisions during stream copy and the output stream time_base when set via
299    /// `streamcopy_init`.
300    ///
301    /// ## FFmpeg CLI equivalent
302    /// ```bash
303    /// # Force input framerate to 30fps
304    /// ffmpeg -r 30 -i input.mp4 output.mp4
305    /// ```
306    ///
307    /// ## FFmpeg source reference (FFmpeg 7.x)
308    /// - Field: `ffmpeg.h:452` (`ist->framerate`, only set with `-r`)
309    /// - Application: `ffmpeg_demux.c:329-333` (used in `ist_dts_update`)
310    pub(crate) framerate: Option<(i32, i32)>,
311}
312
313impl Input {
314    pub fn new(url: impl Into<String>) -> Self {
315        url.into().into()
316    }
317
318    /// Creates a new `Input` instance with a custom read callback.
319    ///
320    /// This method initializes an `Input` object that uses a provided `read_callback` function
321    /// to supply data to the input stream. This is particularly useful for custom data sources
322    /// such as in-memory buffers, network streams, or other non-standard input mechanisms.
323    ///
324    /// ### Parameters:
325    /// - `read_callback: fn(buf: &mut [u8]) -> i32`: A function pointer that fills the provided
326    ///   mutable buffer with data and returns the number of bytes read.
327    ///
328    /// ### Return Value:
329    /// - Returns a new `Input` instance configured with the specified `read_callback`.
330    ///
331    /// ### Behavior of `read_callback`:
332    /// - **Positive Value**: Indicates the number of bytes successfully read.
333    /// - **`ffmpeg_sys_next::AVERROR_EOF`**: Indicates the end of the stream. The library will stop requesting data.
334    /// - **Negative Value**: Indicates an error occurred. For example:
335    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: Represents an input/output error.
336    ///   - Other custom-defined error codes can also be returned to signal specific issues.
337    ///
338    /// ### Example:
339    /// ```rust,ignore
340    /// let input = Input::new_by_read_callback(move |buf| {
341    ///     let data = b"example custom data source";
342    ///     let len = data.len().min(buf.len());
343    ///     buf[..len].copy_from_slice(&data[..len]);
344    ///     len as i32 // Return the number of bytes written
345    /// });
346    /// ```
347    pub fn new_by_read_callback<F>(read_callback: F) -> Self
348    where
349        F: FnMut(&mut [u8]) -> i32 + Send + 'static,
350    {
351        (Box::new(read_callback) as Box<dyn FnMut(&mut [u8]) -> i32 + Send>).into()
352    }
353
354    /// Sets a custom seek callback for the input stream.
355    ///
356    /// This function assigns a user-defined function that handles seeking within the input stream.
357    /// It is required when using custom data sources that support random access, such as files,
358    /// memory-mapped buffers, or seekable network streams.
359    ///
360    /// **FFmpeg may invoke `seek_callback` from different threads.**
361    /// If using a `File` as the data source, **wrap it in `Arc<Mutex<File>>`** to ensure
362    /// thread-safe access across multiple threads.
363    ///
364    /// ### Parameters:
365    /// - `seek_callback: FnMut(i64, i32) -> i64`: A function that handles seek operations.
366    ///   - `offset: i64`: The target seek position in the stream.
367    ///   - `whence: i32`: The seek mode, which determines how `offset` should be interpreted:
368    ///     - `ffmpeg_sys_next::SEEK_SET` (0) - Seek to an absolute position.
369    ///     - `ffmpeg_sys_next::SEEK_CUR` (1) - Seek relative to the current position.
370    ///     - `ffmpeg_sys_next::SEEK_END` (2) - Seek relative to the end of the stream.
371    ///     - `ffmpeg_sys_next::SEEK_HOLE` (3) - Find the next hole in a sparse file (Linux only).
372    ///     - `ffmpeg_sys_next::SEEK_DATA` (4) - Find the next data block in a sparse file (Linux only).
373    ///     - `ffmpeg_sys_next::AVSEEK_FLAG_BYTE` (2) - Seek using byte offset instead of timestamps.
374    ///     - `ffmpeg_sys_next::AVSEEK_SIZE` (65536) - Query the total size of the stream.
375    ///     - `ffmpeg_sys_next::AVSEEK_FORCE` (131072) - Force seeking, even if normally restricted.
376    ///
377    /// ### Return Value:
378    /// - Returns `Self`, allowing for method chaining.
379    ///
380    /// ### Behavior of `seek_callback`:
381    /// - **Positive Value**: The new offset position after seeking.
382    /// - **Negative Value**: An error occurred, such as:
383    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE)`: Seek is not supported.
384    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
385    ///
386    /// ### Example (Thread-safe seek callback using `Arc<Mutex<File>>`):
387    /// Since `FFmpeg` may call `read_callback` and `seek_callback` from different threads,
388    /// **use `Arc<Mutex<File>>` to ensure safe concurrent access.**
389    ///
390    /// ```rust,ignore
391    /// use std::fs::File;
392    /// use std::io::{Read, Seek, SeekFrom};
393    /// use std::sync::{Arc, Mutex};
394    ///
395    /// // ✅ Wrap the file in Arc<Mutex<>> for safe shared access
396    /// let file = Arc::new(Mutex::new(File::open("test.mp4").expect("Failed to open file")));
397    ///
398    /// // ✅ Thread-safe read callback
399    /// let read_callback = {
400    ///     let file = Arc::clone(&file);
401    ///     move |buf: &mut [u8]| -> i32 {
402    ///         let mut file = file.lock().unwrap();
403    ///         match file.read(buf) {
404    ///             Ok(0) => {
405    ///                 println!("Read EOF");
406    ///                 ffmpeg_sys_next::AVERROR_EOF
407    ///             }
408    ///             Ok(bytes_read) => bytes_read as i32,
409    ///             Err(e) => {
410    ///                 println!("Read error: {}", e);
411    ///                 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)
412    ///             }
413    ///         }
414    ///     }
415    /// };
416    ///
417    /// // ✅ Thread-safe seek callback
418    /// let seek_callback = {
419    ///     let file = Arc::clone(&file);
420    ///     Box::new(move |offset: i64, whence: i32| -> i64 {
421    ///         let mut file = file.lock().unwrap();
422    ///
423    ///         // ✅ Handle AVSEEK_SIZE: Return total file size
424    ///         if whence == ffmpeg_sys_next::AVSEEK_SIZE {
425    ///             if let Ok(size) = file.metadata().map(|m| m.len() as i64) {
426    ///                 println!("FFmpeg requested stream size: {}", size);
427    ///                 return size;
428    ///             }
429    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
430    ///         }
431    ///
432    ///         // ✅ Ignore AVSEEK_FORCE flag
433    ///         let actual_whence = whence & !ffmpeg_sys_next::AVSEEK_FORCE;
434    ///
435    ///         // ✅ Handle AVSEEK_FLAG_BYTE: Perform byte-based seek
436    ///         if actual_whence & ffmpeg_sys_next::AVSEEK_FLAG_BYTE != 0 {
437    ///             println!("FFmpeg requested byte-based seeking. Seeking to byte offset: {}", offset);
438    ///             if let Ok(new_pos) = file.seek(SeekFrom::Start(offset as u64)) {
439    ///                 return new_pos as i64;
440    ///             }
441    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
442    ///         }
443    ///
444    ///         // ✅ Handle SEEK_HOLE and SEEK_DATA (Linux only)
445    ///         #[cfg(target_os = "linux")]
446    ///         if actual_whence == ffmpeg_sys_next::SEEK_HOLE {
447    ///             println!("FFmpeg requested SEEK_HOLE, but Rust std::fs does not support it.");
448    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
449    ///         }
450    ///         #[cfg(target_os = "linux")]
451    ///         if actual_whence == ffmpeg_sys_next::SEEK_DATA {
452    ///             println!("FFmpeg requested SEEK_DATA, but Rust std::fs does not support it.");
453    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
454    ///         }
455    ///
456    ///         // ✅ Standard seek modes
457    ///         let seek_result = match actual_whence {
458    ///             ffmpeg_sys_next::SEEK_SET => file.seek(SeekFrom::Start(offset as u64)),
459    ///             ffmpeg_sys_next::SEEK_CUR => file.seek(SeekFrom::Current(offset)),
460    ///             ffmpeg_sys_next::SEEK_END => file.seek(SeekFrom::End(offset)),
461    ///             _ => {
462    ///                 println!("Unsupported seek mode: {}", whence);
463    ///                 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
464    ///             }
465    ///         };
466    ///
467    ///         match seek_result {
468    ///             Ok(new_pos) => {
469    ///                 println!("Seek successful, new position: {}", new_pos);
470    ///                 new_pos as i64
471    ///             }
472    ///             Err(e) => {
473    ///                 println!("Seek failed: {}", e);
474    ///                 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64
475    ///             }
476    ///         }
477    ///     })
478    /// };
479    ///
480    /// let input = Input::new_by_read_callback(read_callback).set_seek_callback(seek_callback);
481    /// ```
482    pub fn set_seek_callback<F>(mut self, seek_callback: F) -> Self
483    where
484        F: FnMut(i64, i32) -> i64 + Send + 'static,
485    {
486        self.seek_callback = Some(Box::new(seek_callback) as Box<dyn FnMut(i64, i32) -> i64 + Send>);
487        self
488    }
489
490    /// Replaces the entire frame-processing pipeline with a new sequence
491    /// of transformations for **post-decoding** frames on this `Input`.
492    ///
493    /// This method clears any previously set pipelines and replaces them with the provided list.
494    ///
495    /// # Parameters
496    /// * `frame_pipelines` - A list of [`FramePipeline`] instances defining the
497    ///   transformations to apply to decoded frames.
498    ///
499    /// # Returns
500    /// * `Self` - Returns the modified `Input`, enabling method chaining.
501    ///
502    /// # Example
503    /// ```rust,ignore
504    /// let input = Input::from("my_video.mp4")
505    ///     .set_frame_pipelines(vec![
506    ///         FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_VIDEO).filter("opengl", Box::new(my_filter)),
507    ///         // Additional pipelines...
508    ///     ]);
509    /// ```
510    pub fn set_frame_pipelines(mut self, frame_pipelines: Vec<impl Into<FramePipeline>>) -> Self {
511        self.frame_pipelines = Some(frame_pipelines.into_iter().map(|frame_pipeline| frame_pipeline.into()).collect());
512        self
513    }
514
515    /// Adds a single [`FramePipeline`] to the existing pipeline list.
516    ///
517    /// If no pipelines are currently defined, this method creates a new pipeline list.
518    /// Otherwise, it appends the provided pipeline to the existing transformations.
519    ///
520    /// # Parameters
521    /// * `frame_pipeline` - A [`FramePipeline`] defining a transformation.
522    ///
523    /// # Returns
524    /// * `Self` - Returns the modified `Input`, enabling method chaining.
525    ///
526    /// # Example
527    /// ```rust,ignore
528    /// let input = Input::from("my_video.mp4")
529    ///     .add_frame_pipeline(FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_VIDEO).filter("opengl", Box::new(my_filter)).build())
530    ///     .add_frame_pipeline(FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_AUDIO).filter("my_custom_filter1", Box::new(...)).filter("my_custom_filter2", Box::new(...)).build());
531    /// ```
532    pub fn add_frame_pipeline(mut self, frame_pipeline: impl Into<FramePipeline>) -> Self {
533        if self.frame_pipelines.is_none() {
534            self.frame_pipelines = Some(vec![frame_pipeline.into()]);
535        } else {
536            self.frame_pipelines
537                .as_mut()
538                .unwrap()
539                .push(frame_pipeline.into());
540        }
541        self
542    }
543
544    /// Sets the input format for the container or device.
545    ///
546    /// By default, if no format is specified,
547    /// FFmpeg will attempt to detect the format automatically. However, certain
548    /// use cases require specifying the format explicitly:
549    /// - Using device-specific inputs (e.g., `avfoundation` on macOS, `dshow` on Windows).
550    /// - Handling raw streams or formats that FFmpeg may not detect automatically.
551    ///
552    /// ### Parameters:
553    /// - `format`: A string specifying the desired input format (e.g., `mp4`, `flv`, `avfoundation`).
554    ///
555    /// ### Return Value:
556    /// - Returns the `Input` instance with the newly set format.
557    pub fn set_format(mut self, format: impl Into<String>) -> Self {
558        self.format = Some(format.into());
559        self
560    }
561
562    /// Sets the **video codec** to be used for decoding.
563    ///
564    /// By default, FFmpeg will automatically select an appropriate video codec
565    /// based on the input format and available decoders. However, this method
566    /// allows you to override that selection and force a specific codec.
567    ///
568    /// # Common Video Codecs:
569    /// | Codec | Description |
570    /// |-------|-------------|
571    /// | `h264` | H.264 (AVC), widely supported and efficient |
572    /// | `hevc` | H.265 (HEVC), better compression at higher complexity |
573    /// | `vp9` | VP9, open-source alternative to H.265 |
574    /// | `av1` | AV1, newer open-source codec with improved compression |
575    /// | `mpeg4` | MPEG-4 Part 2, older but still used in some cases |
576    ///
577    /// # Arguments
578    /// * `video_codec` - A string representing the desired video codec (e.g., `"h264"`, `"hevc"`).
579    ///
580    /// # Returns
581    /// * `Self` - Returns the modified `Input` struct, allowing for method chaining.
582    ///
583    /// # Example:
584    /// ```rust,ignore
585    /// let input = Input::from("video.mp4").set_video_codec("h264");
586    /// ```
587    pub fn set_video_codec(mut self, video_codec: impl Into<String>) -> Self {
588        self.video_codec = Some(video_codec.into());
589        self
590    }
591
592    /// Sets the **audio codec** to be used for decoding.
593    ///
594    /// By default, FFmpeg will automatically select an appropriate audio codec
595    /// based on the input format and available decoders. However, this method
596    /// allows you to specify a preferred codec.
597    ///
598    /// # Common Audio Codecs:
599    /// | Codec | Description |
600    /// |-------|-------------|
601    /// | `aac` | AAC, commonly used for MP4 and streaming |
602    /// | `mp3` | MP3, widely supported but lower efficiency |
603    /// | `opus` | Opus, high-quality open-source codec |
604    /// | `vorbis` | Vorbis, used in Ogg containers |
605    /// | `flac` | FLAC, lossless audio format |
606    ///
607    /// # Arguments
608    /// * `audio_codec` - A string representing the desired audio codec (e.g., `"aac"`, `"mp3"`).
609    ///
610    /// # Returns
611    /// * `Self` - Returns the modified `Input` struct, allowing for method chaining.
612    ///
613    /// # Example:
614    /// ```rust,ignore
615    /// let input = Input::from("audio.mp3").set_audio_codec("aac");
616    /// ```
617    pub fn set_audio_codec(mut self, audio_codec: impl Into<String>) -> Self {
618        self.audio_codec = Some(audio_codec.into());
619        self
620    }
621
622    /// Sets the **subtitle codec** to be used for decoding.
623    ///
624    /// By default, FFmpeg will automatically select an appropriate subtitle codec
625    /// based on the input format and available decoders. This method lets you specify
626    /// a particular subtitle codec.
627    ///
628    /// # Common Subtitle Codecs:
629    /// | Codec | Description |
630    /// |-------|-------------|
631    /// | `ass` | Advanced SubStation Alpha (ASS) subtitles |
632    /// | `srt` | SubRip Subtitle format (SRT) |
633    /// | `mov_text` | Subtitles in MP4 containers |
634    /// | `subrip` | Plain-text subtitle format |
635    ///
636    /// # Arguments
637    /// * `subtitle_codec` - A string representing the desired subtitle codec (e.g., `"mov_text"`, `"ass"`, `"srt"`).
638    ///
639    /// # Returns
640    /// * `Self` - Returns the modified `Input` struct, allowing for method chaining.
641    ///
642    /// # Example:
643    /// ```rust,ignore
644    /// let input = Input::from("movie.mkv").set_subtitle_codec("ass");
645    /// ```
646    pub fn set_subtitle_codec(mut self, subtitle_codec: impl Into<String>) -> Self {
647        self.subtitle_codec = Some(subtitle_codec.into());
648        self
649    }
650
651    /// Enables or disables **exit on error** behavior for the input.
652    ///
653    /// If set to `true`, FFmpeg will exit (stop processing) if it encounters any
654    /// decoding or demuxing error on this input. If set to `false` (the default),
655    /// FFmpeg may attempt to continue despite errors, skipping damaged portions.
656    ///
657    /// # Parameters
658    /// - `exit_on_error`: `true` to stop on errors, `false` to keep going.
659    ///
660    /// # Returns
661    /// * `Self` - allowing method chaining.
662    ///
663    /// # Example
664    /// ```rust,ignore
665    /// let input = Input::from("test.mp4")
666    ///     .set_exit_on_error(true);
667    /// ```
668    pub fn set_exit_on_error(mut self, exit_on_error: bool) -> Self {
669        self.exit_on_error = Some(exit_on_error);
670        self
671    }
672
673    /// Sets a **read rate** for this input, controlling how quickly frames are read.
674    ///
675    /// - If set to `1.0`, frames are read at their native frame rate.
676    /// - If set to another value (e.g., `0.5` or `2.0`), FFmpeg may attempt to read
677    ///   slower or faster, simulating changes in real-time playback speed.
678    ///
679    /// # Parameters
680    /// - `rate`: A floating-point value indicating the read rate multiplier.
681    ///
682    /// # Returns
683    /// * `Self` - allowing method chaining.
684    ///
685    /// # Example
686    /// ```rust,ignore
687    /// let input = Input::from("video.mp4")
688    ///     .set_readrate(0.5); // read at half speed
689    /// ```
690    pub fn set_readrate(mut self, rate: f32) -> Self {
691        self.readrate = Some(rate);
692        self
693    }
694
695    /// Sets a **log-level offset** for this input's decoders
696    /// (`AVCodecContext.log_level_offset`).
697    ///
698    /// FFmpeg shifts the effective level of every message a decoder emits by
699    /// this offset. Expected decoder noise — e.g. h264 `Missing reference
700    /// picture` / `decode_slice_header error` bursts right after seeking to a
701    /// non-keyframe (open GOP) — is logged at ERROR level; an offset of `8`
702    /// (one AV_LOG step) demotes those to WARNING for this input only,
703    /// without hiding errors from other inputs.
704    ///
705    /// # Arguments
706    /// * `offset` - Added to each message's log level; positive values make
707    ///   this input's decoders quieter, negative values make them louder.
708    ///
709    /// # Returns
710    /// * `Self` - allowing method chaining.
711    ///
712    /// # Example
713    /// ```rust,ignore
714    /// // Screenshot after seek: demote expected h264 reference errors.
715    /// let input = Input::from("video.mp4")
716    ///     .set_log_level_offset(8);
717    /// ```
718    pub fn set_log_level_offset(mut self, offset: i32) -> Self {
719        self.log_level_offset = Some(offset);
720        self
721    }
722
723    /// Sets the **start time** (in microseconds) from which to begin reading.
724    ///
725    /// FFmpeg will skip all data before this timestamp. This can be used to
726    /// implement “input seeking” or to only process a portion of the input.
727    ///
728    /// # Parameters
729    /// - `start_time_us`: The timestamp (in microseconds) at which to start reading.
730    ///
731    /// # Returns
732    /// * `Self` - allowing method chaining.
733    ///
734    /// # Example
735    /// ```rust,ignore
736    /// let input = Input::from("long_clip.mp4")
737    ///     .set_start_time_us(2_000_000); // Start at 2 seconds
738    /// ```
739    pub fn set_start_time_us(mut self, start_time_us: i64) -> Self {
740        self.start_time_us = Some(start_time_us);
741        self
742    }
743
744    /// Sets the **recording time** (in microseconds) for this input.
745    ///
746    /// FFmpeg will only read for the specified duration, ignoring data past this
747    /// limit. This can be used to trim or limit how much of the input is processed.
748    ///
749    /// # Parameters
750    /// - `recording_time_us`: The number of microseconds to read from the input.
751    ///
752    /// # Returns
753    /// * `Self` - allowing method chaining.
754    ///
755    /// # Example
756    /// ```rust,ignore
757    /// let input = Input::from("long_clip.mp4")
758    ///     .set_recording_time_us(5_000_000); // Only read 5 seconds
759    /// ```
760    pub fn set_recording_time_us(mut self, recording_time_us: i64) -> Self {
761        self.recording_time_us = Some(recording_time_us);
762        self
763    }
764
765    /// Sets a **stop time** (in microseconds) beyond which input data will be ignored.
766    ///
767    /// This is similar to [`set_recording_time_us`](Self::set_recording_time_us) but
768    /// specifically references an absolute timestamp in the stream. Once this timestamp
769    /// is reached, FFmpeg stops reading.
770    ///
771    /// # Parameters
772    /// - `stop_time_us`: The absolute timestamp (in microseconds) at which to stop reading.
773    ///
774    /// # Returns
775    /// * `Self` - allowing method chaining.
776    ///
777    /// # Example
778    /// ```rust,ignore
779    /// let input = Input::from("long_clip.mp4")
780    ///     .set_stop_time_us(10_000_000); // Stop reading at 10 seconds
781    /// ```
782    pub fn set_stop_time_us(mut self, stop_time_us: i64) -> Self {
783        self.stop_time_us = Some(stop_time_us);
784        self
785    }
786
787    /// Sets the number of **loops** to perform on this input stream.
788    ///
789    /// If FFmpeg reaches the end of the input, it can loop back and start from the
790    /// beginning, effectively repeating the content `stream_loop` times.
791    /// A negative value may indicate infinite looping (depending on FFmpeg’s actual behavior).
792    ///
793    /// # Parameters
794    /// - `count`: How many times to loop (e.g. `1` means one loop, `-1` might mean infinite).
795    ///
796    /// # Returns
797    /// * `Self` - allowing method chaining.
798    ///
799    /// # Example
800    /// ```rust,ignore
801    /// let input = Input::from("music.mp3")
802    ///     .set_stream_loop(2); // play the input 2 extra times
803    /// ```
804    pub fn set_stream_loop(mut self, count: i32) -> Self {
805        self.stream_loop = Some(count);
806        self
807    }
808
809    /// Specifies a **hardware acceleration** name for decoding this input.
810    ///
811    /// Common values might include `"cuda"`, `"vaapi"`, `"dxva2"`, `"videotoolbox"`, etc.
812    /// Whether it works depends on your FFmpeg build and the hardware you have available.
813    ///
814    /// # Parameters
815    /// - `hwaccel_name`: A string naming the hardware accel to use.
816    ///
817    /// # Returns
818    /// * `Self` - allowing method chaining.
819    ///
820    /// # Example
821    /// ```rust,ignore
822    /// let input = Input::from("video.mp4")
823    ///     .set_hwaccel("cuda");
824    /// ```
825    pub fn set_hwaccel(mut self, hwaccel_name: impl Into<String>) -> Self {
826        self.hwaccel = Some(hwaccel_name.into());
827        self
828    }
829
830    /// Selects a **hardware acceleration device** for decoding.
831    ///
832    /// For example, if you have multiple GPUs or want to specify a device node (like
833    /// `"/dev/dri/renderD128"` on Linux for VAAPI), you can pass it here. This option
834    /// must match the hardware accel you set via [`set_hwaccel`](Self::set_hwaccel) if
835    /// you expect decoding to succeed.
836    ///
837    /// # Parameters
838    /// - `device`: A string indicating the device path or identifier.
839    ///
840    /// # Returns
841    /// * `Self` - allowing method chaining.
842    ///
843    /// # Example
844    /// ```rust,ignore
845    /// let input = Input::from("video.mp4")
846    ///     .set_hwaccel("vaapi")
847    ///     .set_hwaccel_device("/dev/dri/renderD128");
848    /// ```
849    pub fn set_hwaccel_device(mut self, device: impl Into<String>) -> Self {
850        self.hwaccel_device = Some(device.into());
851        self
852    }
853
854    /// Sets the **output pixel format** to be used with hardware-accelerated decoding.
855    ///
856    /// Certain hardware decoders can produce various output pixel formats. This option
857    /// lets you specify which format (e.g., `"nv12"`, `"vaapi"`, etc.) is used during
858    /// the decode process.
859    /// Must be compatible with the chosen hardware accel and device.
860    ///
861    /// # Parameters
862    /// - `format`: A string naming the desired output pixel format (e.g. `"nv12"`).
863    ///
864    /// # Returns
865    /// * `Self` - allowing method chaining.
866    ///
867    /// # Example
868    /// ```rust,ignore
869    /// let input = Input::from("video.mp4")
870    ///     .set_hwaccel("cuda")
871    ///     .set_hwaccel_output_format("cuda");
872    /// ```
873    pub fn set_hwaccel_output_format(mut self, format: impl Into<String>) -> Self {
874        self.hwaccel_output_format = Some(format.into());
875        self
876    }
877
878    /// Sets a single input option for avformat_open_input.
879    ///
880    /// This method configures options that will be passed to FFmpeg's `avformat_open_input()`
881    /// function. The options can control behavior at different levels including format detection,
882    /// protocol handling, device configuration, and general input processing.
883    ///
884    /// **Example Usage:**
885    /// ```rust,ignore
886    /// let input = Input::new("avfoundation:0")
887    ///     .set_input_opt("framerate", "30")
888    ///     .set_input_opt("probesize", "5000000");
889    /// ```
890    ///
891    /// ### Parameters:
892    /// - `key`: The option name (e.g., `"framerate"`, `"probesize"`, `"timeout"`).
893    /// - `value`: The option value (e.g., `"30"`, `"5000000"`, `"10000000"`).
894    ///
895    /// ### Return Value:
896    /// - Returns the modified `Input` instance for method chaining.
897    pub fn set_input_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
898        if let Some(ref mut opts) = self.input_opts {
899            opts.insert(key.into(), value.into());
900        } else {
901            let mut opts = HashMap::new();
902            opts.insert(key.into(), value.into());
903            self.input_opts = Some(opts);
904        }
905        self
906    }
907
908    /// Sets multiple input options at once for avformat_open_input.
909    ///
910    /// This method allows setting multiple options in a single call, which will all be
911    /// passed to FFmpeg's `avformat_open_input()` function. Each key-value pair will be
912    /// inserted into the options map, overwriting any existing keys with the same name.
913    ///
914    /// **Example Usage:**
915    /// ```rust,ignore
916    /// let input = Input::new("http://example.com/stream.m3u8")
917    ///     .set_input_opts(vec![
918    ///         ("user_agent", "MyApp/1.0"),
919    ///         ("timeout", "10000000"),
920    ///         ("probesize", "5000000"),
921    ///     ]);
922    /// ```
923    ///
924    /// ### Parameters:
925    /// - `opts`: A vector of key-value pairs representing input options.
926    ///
927    /// ### Return Value:
928    /// - Returns the modified `Input` instance for method chaining.
929    pub fn set_input_opts(mut self, opts: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
930        if let Some(ref mut input_opts) = self.input_opts {
931            for (key, value) in opts {
932                input_opts.insert(key.into(), value.into());
933            }
934        } else {
935            let mut input_opts = HashMap::new();
936            for (key, value) in opts {
937                input_opts.insert(key.into(), value.into());
938            }
939            self.input_opts = Some(input_opts);
940        }
941        self
942    }
943
944    /// Sets whether to automatically rotate video based on display matrix metadata.
945    ///
946    /// When enabled (default is `true`), videos with rotation metadata (common in
947    /// smartphone recordings) will be automatically rotated to the correct orientation
948    /// using transpose/hflip/vflip filters.
949    ///
950    /// # Parameters
951    /// - `autorotate`: `true` to enable automatic rotation (default), `false` to disable.
952    ///
953    /// # Returns
954    /// * `Self` - allowing method chaining.
955    ///
956    /// # FFmpeg CLI equivalent
957    /// ```bash
958    /// ffmpeg -autorotate 0 -i input.mp4 output.mp4
959    /// ```
960    ///
961    /// # Example
962    /// ```rust,ignore
963    /// // Disable automatic rotation to preserve original video orientation
964    /// let input = Input::from("smartphone_video.mp4")
965    ///     .set_autorotate(false);
966    /// ```
967    pub fn set_autorotate(mut self, autorotate: bool) -> Self {
968        self.autorotate = Some(autorotate);
969        self
970    }
971
972    /// Sets a timestamp scale factor for pts/dts values.
973    ///
974    /// This multiplier is applied to packet timestamps after ts_offset addition.
975    /// Default is `1.0` (no scaling). Values must be positive.
976    ///
977    /// This is useful for fixing videos with incorrect timestamps or for
978    /// special timestamp manipulation scenarios.
979    ///
980    /// # Parameters
981    /// - `scale`: A positive floating-point value for timestamp scaling.
982    ///
983    /// # Returns
984    /// * `Self` - allowing method chaining.
985    ///
986    /// # FFmpeg CLI equivalent
987    /// ```bash
988    /// ffmpeg -itsscale 2.0 -i input.mp4 output.mp4
989    /// ```
990    ///
991    /// # Example
992    /// ```rust,ignore
993    /// // Scale timestamps by 2x (double the playback speed effect on timestamps)
994    /// let input = Input::from("video.mp4")
995    ///     .set_ts_scale(2.0);
996    /// ```
997    ///
998    /// # Panics
999    /// Panics if `scale` is not a positive finite number.
1000    pub fn set_ts_scale(mut self, scale: f64) -> Self {
1001        assert!(scale.is_finite(), "ts_scale must be finite, got {scale}");
1002        assert!(scale > 0.0, "ts_scale must be positive, got {scale}");
1003        self.ts_scale = Some(scale);
1004        self
1005    }
1006
1007    /// Sets a forced framerate for the input video stream.
1008    ///
1009    /// When set, this overrides the default DTS estimation behavior. By default,
1010    /// ez-ffmpeg uses the actual packet duration for DTS estimation (matching FFmpeg
1011    /// CLI behavior without `-r`). Setting a framerate forces DTS estimation to use
1012    /// the specified rate instead, which snaps timestamps to a fixed frame grid.
1013    ///
1014    /// # Parameters
1015    /// - `num`: Framerate numerator (e.g., 30 for 30fps, 24000 for 23.976fps)
1016    /// - `den`: Framerate denominator (e.g., 1 for 30fps, 1001 for 23.976fps)
1017    ///
1018    /// # Returns
1019    /// * `Self` - allowing method chaining.
1020    ///
1021    /// # FFmpeg CLI equivalent
1022    /// ```bash
1023    /// ffmpeg -r 30 -i input.mp4 output.mp4
1024    /// ffmpeg -r 24000/1001 -i input.mp4 output.mp4
1025    /// ```
1026    ///
1027    /// # Example
1028    /// ```rust,ignore
1029    /// // Force 30fps framerate for DTS estimation
1030    /// let input = Input::from("video.mp4")
1031    ///     .set_framerate(30, 1);
1032    ///
1033    /// // Force 23.976fps framerate
1034    /// let input = Input::from("video.mp4")
1035    ///     .set_framerate(24000, 1001);
1036    /// ```
1037    ///
1038    /// # Panics
1039    /// Panics if `num` or `den` is not positive.
1040    pub fn set_framerate(mut self, num: i32, den: i32) -> Self {
1041        assert!(num > 0, "framerate numerator must be positive, got {num}");
1042        assert!(den > 0, "framerate denominator must be positive, got {den}");
1043        self.framerate = Some((num, den));
1044        self
1045    }
1046}
1047
1048impl From<Box<dyn FnMut(&mut [u8]) -> i32 + Send>> for Input {
1049    fn from(read_callback: Box<dyn FnMut(&mut [u8]) -> i32 + Send>) -> Self {
1050        Self {
1051            url: None,
1052            read_callback: Some(read_callback),
1053            seek_callback: None,
1054            frame_pipelines: None,
1055            format: None,
1056            video_codec: None,
1057            audio_codec: None,
1058            subtitle_codec: None,
1059            exit_on_error: None,
1060            readrate: None,
1061            start_time_us: None,
1062            recording_time_us: None,
1063            stop_time_us: None,
1064            stream_loop: None,
1065            hwaccel: None,
1066            hwaccel_device: None,
1067            hwaccel_output_format: None,
1068            log_level_offset: None,
1069            input_opts: None,
1070            autorotate: None,
1071            ts_scale: None,
1072            framerate: None,
1073        }
1074    }
1075}
1076
1077impl From<String> for Input {
1078    fn from(url: String) -> Self {
1079        Self {
1080            url: Some(url),
1081            read_callback: None,
1082            seek_callback: None,
1083            frame_pipelines: None,
1084            format: None,
1085            video_codec: None,
1086            audio_codec: None,
1087            subtitle_codec: None,
1088            exit_on_error: None,
1089            readrate: None,
1090            start_time_us: None,
1091            recording_time_us: None,
1092            stop_time_us: None,
1093            stream_loop: None,
1094            hwaccel: None,
1095            hwaccel_device: None,
1096            hwaccel_output_format: None,
1097            log_level_offset: None,
1098            input_opts: None,
1099            autorotate: None,
1100            ts_scale: None,
1101            framerate: None,
1102        }
1103    }
1104}
1105
1106impl From<&str> for Input {
1107    fn from(url: &str) -> Self {
1108        Self::from(String::from(url))
1109    }
1110}
1111
1112
1113#[cfg(test)]
1114mod tests {
1115    use crate::core::context::input::Input;
1116
1117    #[test]
1118    fn set_framerate_valid() {
1119        let input = Input::from("test.mp4").set_framerate(24000, 1001);
1120        assert_eq!(input.framerate, Some((24000, 1001)));
1121    }
1122
1123    #[test]
1124    fn set_framerate_simple() {
1125        let input = Input::from("test.mp4").set_framerate(30, 1);
1126        assert_eq!(input.framerate, Some((30, 1)));
1127    }
1128
1129    #[test]
1130    #[should_panic(expected = "framerate numerator must be positive")]
1131    fn set_framerate_zero_num() {
1132        Input::from("test.mp4").set_framerate(0, 1);
1133    }
1134
1135    #[test]
1136    #[should_panic(expected = "framerate denominator must be positive")]
1137    fn set_framerate_zero_den() {
1138        Input::from("test.mp4").set_framerate(24, 0);
1139    }
1140
1141    #[test]
1142    #[should_panic(expected = "framerate numerator must be positive")]
1143    fn set_framerate_negative_num() {
1144        Input::from("test.mp4").set_framerate(-1, 1);
1145    }
1146
1147    #[test]
1148    #[should_panic(expected = "framerate denominator must be positive")]
1149    fn set_framerate_negative_den() {
1150        Input::from("test.mp4").set_framerate(24, -1);
1151    }
1152
1153    #[test]
1154    fn set_ts_scale_valid() {
1155        let input = Input::from("test.mp4").set_ts_scale(2.0);
1156        assert_eq!(input.ts_scale, Some(2.0));
1157    }
1158
1159    #[test]
1160    fn set_ts_scale_fractional() {
1161        let input = Input::from("test.mp4").set_ts_scale(0.5);
1162        assert_eq!(input.ts_scale, Some(0.5));
1163    }
1164
1165    #[test]
1166    #[should_panic(expected = "ts_scale must be finite")]
1167    fn set_ts_scale_nan() {
1168        Input::from("test.mp4").set_ts_scale(f64::NAN);
1169    }
1170
1171    #[test]
1172    #[should_panic(expected = "ts_scale must be finite")]
1173    fn set_ts_scale_infinity() {
1174        Input::from("test.mp4").set_ts_scale(f64::INFINITY);
1175    }
1176
1177    #[test]
1178    #[should_panic(expected = "ts_scale must be finite")]
1179    fn set_ts_scale_neg_infinity() {
1180        Input::from("test.mp4").set_ts_scale(f64::NEG_INFINITY);
1181    }
1182
1183    #[test]
1184    #[should_panic(expected = "ts_scale must be positive")]
1185    fn set_ts_scale_zero() {
1186        Input::from("test.mp4").set_ts_scale(0.0);
1187    }
1188
1189    #[test]
1190    #[should_panic(expected = "ts_scale must be positive")]
1191    fn set_ts_scale_negative() {
1192        Input::from("test.mp4").set_ts_scale(-1.0);
1193    }
1194
1195    #[test]
1196    fn test_new_by_read_callback() {
1197        let data_source = b"example custom data source".to_vec();
1198        let _input = Input::new_by_read_callback(move |buf| {
1199            let len = data_source.len().min(buf.len());
1200            buf[..len].copy_from_slice(&data_source[..len]);
1201            len as i32 // Return the number of bytes written
1202        });
1203
1204        let data_source2 = b"example custom data source2".to_vec();
1205        let _input = Input::new_by_read_callback(move |buf2| {
1206            let len = data_source2.len().min(buf2.len());
1207            buf2[..len].copy_from_slice(&data_source2[..len]);
1208            len as i32 // Return the number of bytes written
1209        });
1210    }
1211}