ez_ffmpeg/core/context/
input.rs

1use std::collections::HashMap;
2use crate::core::filter::frame_pipeline_builder::FramePipelineBuilder;
3
4unsafe impl Send for Input {}
5
6pub struct Input {
7    /// The URL of the input source.
8    ///
9    /// This specifies the source from which the input stream is obtained. It can be:
10    /// - A local file path (e.g., `file:///path/to/video.mp4`).
11    /// - A network stream (e.g., `rtmp://example.com/live/stream`).
12    /// - Any other URL supported by FFmpeg (e.g., `http://example.com/video.mp4`, `udp://...`).
13    ///
14    /// The URL must be valid. If the URL is invalid or unsupported,
15    /// the library will return an error when attempting to open the input stream.
16    pub(crate) url: Option<String>,
17
18    /// A callback function for custom data reading.
19    ///
20    /// The `read_callback` function allows you to provide custom logic for feeding data into
21    /// the input stream. This is useful for scenarios where the input does not come directly
22    /// from a standard source (like a file or URL), but instead from a custom data source,
23    /// such as an in-memory buffer or a custom network stream.
24    ///
25    /// ### Parameters:
26    /// - `buf: &mut [u8]`: A mutable buffer into which the data should be written.
27    ///   The callback should fill this buffer with as much data as possible, up to its length.
28    ///
29    /// ### Return Value:
30    /// - **Positive Value**: The number of bytes successfully read into `buf`.
31    /// - **`ffmpeg_sys_next::AVERROR_EOF`**: Indicates the end of the input stream. No more data will be read.
32    /// - **Negative Value**: Indicates an error occurred, such as:
33    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
34    ///   - Custom-defined error codes depending on your implementation.
35    ///
36    /// ### Example:
37    /// ```rust
38    /// fn custom_read_callback(buf: &mut [u8]) -> i32 {
39    ///     let data = b"example data stream";
40    ///     let len = data.len().min(buf.len());
41    ///     buf[..len].copy_from_slice(&data[..len]);
42    ///     len as i32 // Return the number of bytes written into the buffer
43    /// }
44    /// ```
45    pub(crate) read_callback: Option<Box<dyn FnMut(&mut [u8]) -> i32>>,
46
47    /// A callback function for custom seeking within the input stream.
48    ///
49    /// The `seek_callback` function allows defining custom seeking behavior.
50    /// This is useful for data sources that support seeking, such as files or memory-mapped data.
51    /// For non-seekable streams (e.g., live network streams), this function may return an error.
52    ///
53    /// **FFmpeg may invoke `seek_callback` from multiple threads, so thread safety is required.**
54    /// When using a `File` as an input source, **use `Arc<Mutex<File>>` to ensure safe access.**
55    ///
56    /// ### Parameters:
57    /// - `offset: i64`: The target position in the stream for seeking.
58    /// - `whence: i32`: The seek mode defining how the `offset` should be interpreted:
59    ///   - `ffmpeg_sys_next::SEEK_SET` (0): Seek to an absolute position.
60    ///   - `ffmpeg_sys_next::SEEK_CUR` (1): Seek relative to the current position.
61    ///   - `ffmpeg_sys_next::SEEK_END` (2): Seek relative to the end of the stream.
62    ///   - `ffmpeg_sys_next::SEEK_HOLE` (3): Find the next file hole (sparse file support).
63    ///   - `ffmpeg_sys_next::SEEK_DATA` (4): Find the next data block (sparse file support).
64    ///   - `ffmpeg_sys_next::AVSEEK_FLAG_BYTE` (2): Seek using **byte offsets** instead of timestamps.
65    ///   - `ffmpeg_sys_next::AVSEEK_SIZE` (65536): Query the **total size** of the stream.
66    ///   - `ffmpeg_sys_next::AVSEEK_FORCE` (131072): **Force seeking even if normally restricted.**
67    ///
68    /// ### Return Value:
69    /// - **Positive Value**: The new offset position after seeking.
70    /// - **Negative Value**: An error occurred. Common errors include:
71    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE)`: Seek is not supported.
72    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
73    ///
74    /// ### Example (Handling multi-threaded access safely with `Arc<Mutex<File>>`):
75    /// Since FFmpeg may call `read_callback` and `seek_callback` from different threads,
76    /// **`Arc<Mutex<File>>` is used to ensure safe access across threads.**
77    ///
78    /// ```rust
79    /// use std::fs::File;
80    /// use std::io::{Seek, SeekFrom};
81    /// use std::sync::{Arc, Mutex};
82    ///
83    /// let file = Arc::new(Mutex::new(File::open("test.mp4").expect("Failed to open file")));
84    ///
85    /// let seek_callback = {
86    ///     let file = Arc::clone(&file);
87    ///     Box::new(move |offset: i64, whence: i32| -> i64 {
88    ///         let mut file = file.lock().unwrap(); // Acquire lock
89    ///
90    ///         // ✅ Handle AVSEEK_SIZE: Return total file size
91    ///         if whence == ffmpeg_sys_next::AVSEEK_SIZE {
92    ///             if let Ok(size) = file.metadata().map(|m| m.len() as i64) {
93    ///                 println!("FFmpeg requested stream size: {}", size);
94    ///                 return size;
95    ///             }
96    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
97    ///         }
98    ///
99    ///         // ✅ Handle AVSEEK_FORCE: Ignore this flag when processing seek
100    ///         let actual_whence = whence & !ffmpeg_sys_next::AVSEEK_FORCE;
101    ///
102    ///         // ✅ Handle AVSEEK_FLAG_BYTE: Perform byte-based seek
103    ///         if actual_whence & ffmpeg_sys_next::AVSEEK_FLAG_BYTE != 0 {
104    ///             println!("FFmpeg requested byte-based seeking. Seeking to byte offset: {}", offset);
105    ///             if let Ok(new_pos) = file.seek(SeekFrom::Start(offset as u64)) {
106    ///                 return new_pos as i64;
107    ///             }
108    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
109    ///         }
110    ///
111    ///         // ✅ Handle SEEK_HOLE and SEEK_DATA (Linux only)
112    ///         #[cfg(target_os = "linux")]
113    ///         if actual_whence == ffmpeg_sys_next::SEEK_HOLE {
114    ///             println!("FFmpeg requested SEEK_HOLE, but Rust std::fs does not support it.");
115    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
116    ///         }
117    ///         #[cfg(target_os = "linux")]
118    ///         if actual_whence == ffmpeg_sys_next::SEEK_DATA {
119    ///             println!("FFmpeg requested SEEK_DATA, but Rust std::fs does not support it.");
120    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
121    ///         }
122    ///
123    ///         // ✅ Standard seek modes
124    ///         let seek_result = match actual_whence {
125    ///             ffmpeg_sys_next::SEEK_SET => file.seek(SeekFrom::Start(offset as u64)),
126    ///             ffmpeg_sys_next::SEEK_CUR => file.seek(SeekFrom::Current(offset)),
127    ///             ffmpeg_sys_next::SEEK_END => file.seek(SeekFrom::End(offset)),
128    ///             _ => {
129    ///                 println!("Unsupported seek mode: {}", whence);
130    ///                 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
131    ///             }
132    ///         };
133    ///
134    ///         match seek_result {
135    ///             Ok(new_pos) => {
136    ///                 println!("Seek successful, new position: {}", new_pos);
137    ///                 new_pos as i64
138    ///             }
139    ///             Err(e) => {
140    ///                 println!("Seek failed: {}", e);
141    ///                 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64
142    ///             }
143    ///         }
144    ///     })
145    /// };
146    /// ```
147    pub(crate) seek_callback: Option<Box<dyn FnMut(i64, i32) -> i64>>,
148
149    /// The pipeline that provides custom processing for decoded frames.
150    ///
151    /// After the input data is decoded into `Frame` objects, these frames
152    /// are passed through the `frame_pipeline`. Each frame goes through
153    /// a series of `FrameFilter` objects in the pipeline, allowing for
154    /// customized processing (e.g., filtering, transformation, etc.).
155    ///
156    /// If `None`, no processing pipeline is applied to the decoded frames.
157    pub(crate) frame_pipelines: Option<Vec<FramePipelineBuilder>>,
158
159    /// The codec to be used for **video** decoding.
160    ///
161    /// If set, this forces FFmpeg to use the specified video codec for decoding.
162    /// Otherwise, FFmpeg will attempt to auto-detect the best available codec.
163    pub(crate) video_codec: Option<String>,
164
165    /// The codec to be used for **audio** decoding.
166    ///
167    /// If set, this forces FFmpeg to use the specified audio codec for decoding.
168    /// Otherwise, FFmpeg will attempt to auto-detect the best available codec.
169    pub(crate) audio_codec: Option<String>,
170
171    /// The codec to be used for **subtitle** decoding.
172    ///
173    /// If set, this forces FFmpeg to use the specified subtitle codec for decoding.
174    /// Otherwise, FFmpeg will attempt to auto-detect the best available codec.
175    pub(crate) subtitle_codec: Option<String>,
176
177    pub(crate) exit_on_error: Option<bool>,
178
179    /// read input at specified rate.
180    /// when set 1. read input at native frame rate.
181    pub(crate) readrate: Option<f32>,
182    pub(crate) start_time_us: Option<i64>,
183    pub(crate) recording_time_us: Option<i64>,
184    pub(crate) stop_time_us: Option<i64>,
185
186    /// set number of times input stream shall be looped
187    pub(crate) stream_loop: Option<i32>,
188
189    /// Hardware Acceleration name
190    /// use Hardware accelerated decoding
191    pub(crate) hwaccel: Option<String>,
192    /// select a device for HW acceleration
193    pub(crate) hwaccel_device: Option<String>,
194    /// select output format used with HW accelerated decoding
195    pub(crate) hwaccel_output_format: Option<String>,
196
197    /// The input format options.
198    ///
199    /// This field stores additional input-specific options that can be passed to the FFmpeg demuxer.
200    /// It is a collection of key-value pairs that modify the behavior of the input format.
201    ///
202    /// **Example Usage:**
203    /// ```rust
204    /// let input = Input::from("rtsp://example.com/stream")
205    ///     .set_input_opt("rtsp_transport", "tcp")
206    ///     .set_input_opt("timeout", "5000000"); // 5 seconds timeout
207    /// ```
208    ///
209    /// ### Common Input Options:
210    /// | Format | Option | Description |
211    /// |--------|--------|-------------|
212    /// | `rtsp` | `rtsp_transport=tcp` | Forces RTSP to use TCP instead of UDP |
213    /// | `rtmp` | `buffer_size=1024000` | Sets RTMP buffer size |
214    /// | `udp`  | `fifo_size=1000000` | Sets FIFO buffer size |
215    pub(crate) input_opts: Option<HashMap<String, String>>,
216}
217
218impl Input {
219    pub fn new(url: impl Into<String>) -> Self {
220        url.into().into()
221    }
222
223    /// Creates a new `Input` instance with a custom read callback.
224    ///
225    /// This method initializes an `Input` object that uses a provided `read_callback` function
226    /// to supply data to the input stream. This is particularly useful for custom data sources
227    /// such as in-memory buffers, network streams, or other non-standard input mechanisms.
228    ///
229    /// ### Parameters:
230    /// - `read_callback: fn(buf: &mut [u8]) -> i32`: A function pointer that fills the provided
231    ///   mutable buffer with data and returns the number of bytes read.
232    ///
233    /// ### Return Value:
234    /// - Returns a new `Input` instance configured with the specified `read_callback`.
235    ///
236    /// ### Behavior of `read_callback`:
237    /// - **Positive Value**: Indicates the number of bytes successfully read.
238    /// - **`ffmpeg_sys_next::AVERROR_EOF`**: Indicates the end of the stream. The library will stop requesting data.
239    /// - **Negative Value**: Indicates an error occurred. For example:
240    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: Represents an input/output error.
241    ///   - Other custom-defined error codes can also be returned to signal specific issues.
242    ///
243    /// ### Example:
244    /// ```rust
245    /// let input = Input::new_by_read_callback(move |buf| {
246    ///     let data = b"example custom data source";
247    ///     let len = data.len().min(buf.len());
248    ///     buf[..len].copy_from_slice(&data[..len]);
249    ///     len as i32 // Return the number of bytes written
250    /// });
251    /// ```
252    pub fn new_by_read_callback<F>(read_callback: F) -> Self
253    where
254        F: FnMut(&mut [u8]) -> i32 + 'static,
255    {
256        (Box::new(read_callback) as Box<dyn FnMut(&mut [u8]) -> i32>).into()
257    }
258
259    /// Sets a custom seek callback for the input stream.
260    ///
261    /// This function assigns a user-defined function that handles seeking within the input stream.
262    /// It is required when using custom data sources that support random access, such as files,
263    /// memory-mapped buffers, or seekable network streams.
264    ///
265    /// **FFmpeg may invoke `seek_callback` from different threads.**
266    /// If using a `File` as the data source, **wrap it in `Arc<Mutex<File>>`** to ensure
267    /// thread-safe access across multiple threads.
268    ///
269    /// ### Parameters:
270    /// - `seek_callback: FnMut(i64, i32) -> i64`: A function that handles seek operations.
271    ///   - `offset: i64`: The target seek position in the stream.
272    ///   - `whence: i32`: The seek mode, which determines how `offset` should be interpreted:
273    ///     - `ffmpeg_sys_next::SEEK_SET` (0) - Seek to an absolute position.
274    ///     - `ffmpeg_sys_next::SEEK_CUR` (1) - Seek relative to the current position.
275    ///     - `ffmpeg_sys_next::SEEK_END` (2) - Seek relative to the end of the stream.
276    ///     - `ffmpeg_sys_next::SEEK_HOLE` (3) - Find the next hole in a sparse file (Linux only).
277    ///     - `ffmpeg_sys_next::SEEK_DATA` (4) - Find the next data block in a sparse file (Linux only).
278    ///     - `ffmpeg_sys_next::AVSEEK_FLAG_BYTE` (2) - Seek using byte offset instead of timestamps.
279    ///     - `ffmpeg_sys_next::AVSEEK_SIZE` (65536) - Query the total size of the stream.
280    ///     - `ffmpeg_sys_next::AVSEEK_FORCE` (131072) - Force seeking, even if normally restricted.
281    ///
282    /// ### Return Value:
283    /// - Returns `Self`, allowing for method chaining.
284    ///
285    /// ### Behavior of `seek_callback`:
286    /// - **Positive Value**: The new offset position after seeking.
287    /// - **Negative Value**: An error occurred, such as:
288    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE)`: Seek is not supported.
289    ///   - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
290    ///
291    /// ### Example (Thread-safe seek callback using `Arc<Mutex<File>>`):
292    /// Since `FFmpeg` may call `read_callback` and `seek_callback` from different threads,
293    /// **use `Arc<Mutex<File>>` to ensure safe concurrent access.**
294    ///
295    /// ```rust
296    /// use std::fs::File;
297    /// use std::io::{Read, Seek, SeekFrom};
298    /// use std::sync::{Arc, Mutex};
299    ///
300    /// // ✅ Wrap the file in Arc<Mutex<>> for safe shared access
301    /// let file = Arc::new(Mutex::new(File::open("test.mp4").expect("Failed to open file")));
302    ///
303    /// // ✅ Thread-safe read callback
304    /// let read_callback = {
305    ///     let file = Arc::clone(&file);
306    ///     move |buf: &mut [u8]| -> i32 {
307    ///         let mut file = file.lock().unwrap();
308    ///         match file.read(buf) {
309    ///             Ok(0) => {
310    ///                 println!("Read EOF");
311    ///                 ffmpeg_sys_next::AVERROR_EOF
312    ///             }
313    ///             Ok(bytes_read) => bytes_read as i32,
314    ///             Err(e) => {
315    ///                 println!("Read error: {}", e);
316    ///                 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)
317    ///             }
318    ///         }
319    ///     }
320    /// };
321    ///
322    /// // ✅ Thread-safe seek callback
323    /// let seek_callback = {
324    ///     let file = Arc::clone(&file);
325    ///     Box::new(move |offset: i64, whence: i32| -> i64 {
326    ///         let mut file = file.lock().unwrap();
327    ///
328    ///         // ✅ Handle AVSEEK_SIZE: Return total file size
329    ///         if whence == ffmpeg_sys_next::AVSEEK_SIZE {
330    ///             if let Ok(size) = file.metadata().map(|m| m.len() as i64) {
331    ///                 println!("FFmpeg requested stream size: {}", size);
332    ///                 return size;
333    ///             }
334    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
335    ///         }
336    ///
337    ///         // ✅ Ignore AVSEEK_FORCE flag
338    ///         let actual_whence = whence & !ffmpeg_sys_next::AVSEEK_FORCE;
339    ///
340    ///         // ✅ Handle AVSEEK_FLAG_BYTE: Perform byte-based seek
341    ///         if actual_whence & ffmpeg_sys_next::AVSEEK_FLAG_BYTE != 0 {
342    ///             println!("FFmpeg requested byte-based seeking. Seeking to byte offset: {}", offset);
343    ///             if let Ok(new_pos) = file.seek(SeekFrom::Start(offset as u64)) {
344    ///                 return new_pos as i64;
345    ///             }
346    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
347    ///         }
348    ///
349    ///         // ✅ Handle SEEK_HOLE and SEEK_DATA (Linux only)
350    ///         #[cfg(target_os = "linux")]
351    ///         if actual_whence == ffmpeg_sys_next::SEEK_HOLE {
352    ///             println!("FFmpeg requested SEEK_HOLE, but Rust std::fs does not support it.");
353    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
354    ///         }
355    ///         #[cfg(target_os = "linux")]
356    ///         if actual_whence == ffmpeg_sys_next::SEEK_DATA {
357    ///             println!("FFmpeg requested SEEK_DATA, but Rust std::fs does not support it.");
358    ///             return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
359    ///         }
360    ///
361    ///         // ✅ Standard seek modes
362    ///         let seek_result = match actual_whence {
363    ///             ffmpeg_sys_next::SEEK_SET => file.seek(SeekFrom::Start(offset as u64)),
364    ///             ffmpeg_sys_next::SEEK_CUR => file.seek(SeekFrom::Current(offset)),
365    ///             ffmpeg_sys_next::SEEK_END => file.seek(SeekFrom::End(offset)),
366    ///             _ => {
367    ///                 println!("Unsupported seek mode: {}", whence);
368    ///                 return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
369    ///             }
370    ///         };
371    ///
372    ///         match seek_result {
373    ///             Ok(new_pos) => {
374    ///                 println!("Seek successful, new position: {}", new_pos);
375    ///                 new_pos as i64
376    ///             }
377    ///             Err(e) => {
378    ///                 println!("Seek failed: {}", e);
379    ///                 ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64
380    ///             }
381    ///         }
382    ///     })
383    /// };
384    ///
385    /// let input = Input::new_by_read_callback(read_callback).set_seek_callback(seek_callback);
386    /// ```
387    pub fn set_seek_callback<F>(mut self, seek_callback: F) -> Self
388    where
389        F: FnMut(i64, i32) -> i64 + 'static,
390    {
391        self.seek_callback = Some(Box::new(seek_callback) as Box<dyn FnMut(i64, i32) -> i64>);
392        self
393    }
394
395    /// Replaces the entire frame-processing pipeline with a new sequence
396    /// of transformations for **post-decoding** frames on this `Input`.
397    ///
398    /// This method clears any previously set pipelines and replaces them with the provided list.
399    ///
400    /// **Note:** This method accepts [`FramePipelineBuilder`] instead of [`FramePipeline`](crate::core::filter::frame_pipeline::FramePipeline).
401    /// For details on why [`FramePipelineBuilder`] is used, see its documentation.
402    ///
403    /// # Parameters
404    /// * `frame_pipelines` - A list of [`FramePipelineBuilder`] instances defining the
405    ///   transformations to apply to decoded frames.
406    ///
407    /// # Returns
408    /// * `Self` - Returns the modified `Input`, enabling method chaining.
409    ///
410    /// # Example
411    /// ```rust
412    /// let input = Input::from("my_video.mp4")
413    ///     .set_frame_pipelines(vec![
414    ///         FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_VIDEO).filter("opengl", Box::new(my_filter)),
415    ///         // Additional pipelines...
416    ///     ]);
417    /// ```
418    pub fn set_frame_pipelines(mut self, frame_pipelines: Vec<FramePipelineBuilder>) -> Self {
419        self.frame_pipelines = Some(frame_pipelines);
420        self
421    }
422
423    /// Adds a single [`FramePipelineBuilder`] to the existing pipeline list.
424    ///
425    /// If no pipelines are currently defined, this method creates a new pipeline list.
426    /// Otherwise, it appends the provided pipeline to the existing transformations.
427    ///
428    /// **Note:** This method accepts [`FramePipelineBuilder`] instead of [`FramePipeline`](crate::core::filter::frame_pipeline::FramePipeline).
429    /// For details on why [`FramePipelineBuilder`] is used, see its documentation.
430    ///
431    /// # Parameters
432    /// * `frame_pipeline` - A [`FramePipelineBuilder`] defining a transformation.
433    ///
434    /// # Returns
435    /// * `Self` - Returns the modified `Input`, enabling method chaining.
436    ///
437    /// # Example
438    /// ```rust
439    /// let input = Input::from("my_video.mp4")
440    ///     .add_frame_pipeline(FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_VIDEO).filter("opengl", Box::new(my_filter)))
441    ///     .add_frame_pipeline(FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_AUDIO).filter("my_custom_filter1", Box::new(...)).filter("my_custom_filter2", Box::new(...)));
442    /// ```
443    pub fn add_frame_pipeline(mut self, frame_pipeline: FramePipelineBuilder) -> Self {
444        if self.frame_pipelines.is_none() {
445            self.frame_pipelines = Some(vec![frame_pipeline]);
446        } else {
447            self.frame_pipelines
448                .as_mut()
449                .unwrap()
450                .push(frame_pipeline);
451        }
452        self
453    }
454
455    /// Sets the **video codec** to be used for decoding.
456    ///
457    /// By default, FFmpeg will automatically select an appropriate video codec
458    /// based on the input format and available decoders. However, this method
459    /// allows you to override that selection and force a specific codec.
460    ///
461    /// # Common Video Codecs:
462    /// | Codec | Description |
463    /// |-------|-------------|
464    /// | `h264` | H.264 (AVC), widely supported and efficient |
465    /// | `hevc` | H.265 (HEVC), better compression at higher complexity |
466    /// | `vp9` | VP9, open-source alternative to H.265 |
467    /// | `av1` | AV1, newer open-source codec with improved compression |
468    /// | `mpeg4` | MPEG-4 Part 2, older but still used in some cases |
469    ///
470    /// # Arguments
471    /// * `video_codec` - A string representing the desired video codec (e.g., `"h264"`, `"hevc"`).
472    ///
473    /// # Returns
474    /// * `Self` - Returns the modified `Input` struct, allowing for method chaining.
475    ///
476    /// # Example:
477    /// ```rust
478    /// let input = Input::from("video.mp4").set_video_codec("h264");
479    /// ```
480    pub fn set_video_codec(mut self, video_codec: impl Into<String>) -> Self {
481        self.video_codec = Some(video_codec.into());
482        self
483    }
484
485    /// Sets the **audio codec** to be used for decoding.
486    ///
487    /// By default, FFmpeg will automatically select an appropriate audio codec
488    /// based on the input format and available decoders. However, this method
489    /// allows you to specify a preferred codec.
490    ///
491    /// # Common Audio Codecs:
492    /// | Codec | Description |
493    /// |-------|-------------|
494    /// | `aac` | AAC, commonly used for MP4 and streaming |
495    /// | `mp3` | MP3, widely supported but lower efficiency |
496    /// | `opus` | Opus, high-quality open-source codec |
497    /// | `vorbis` | Vorbis, used in Ogg containers |
498    /// | `flac` | FLAC, lossless audio format |
499    ///
500    /// # Arguments
501    /// * `audio_codec` - A string representing the desired audio codec (e.g., `"aac"`, `"mp3"`).
502    ///
503    /// # Returns
504    /// * `Self` - Returns the modified `Input` struct, allowing for method chaining.
505    ///
506    /// # Example:
507    /// ```rust
508    /// let input = Input::from("audio.mp3").set_audio_codec("aac");
509    /// ```
510    pub fn set_audio_codec(mut self, audio_codec: impl Into<String>) -> Self {
511        self.audio_codec = Some(audio_codec.into());
512        self
513    }
514
515    /// Sets the **subtitle codec** to be used for decoding.
516    ///
517    /// By default, FFmpeg will automatically select an appropriate subtitle codec
518    /// based on the input format and available decoders. This method lets you specify
519    /// a particular subtitle codec.
520    ///
521    /// # Common Subtitle Codecs:
522    /// | Codec | Description |
523    /// |-------|-------------|
524    /// | `ass` | Advanced SubStation Alpha (ASS) subtitles |
525    /// | `srt` | SubRip Subtitle format (SRT) |
526    /// | `mov_text` | Subtitles in MP4 containers |
527    /// | `subrip` | Plain-text subtitle format |
528    ///
529    /// # Arguments
530    /// * `subtitle_codec` - A string representing the desired subtitle codec (e.g., `"mov_text"`, `"ass"`, `"srt"`).
531    ///
532    /// # Returns
533    /// * `Self` - Returns the modified `Input` struct, allowing for method chaining.
534    ///
535    /// # Example:
536    /// ```rust
537    /// let input = Input::from("movie.mkv").set_subtitle_codec("ass");
538    /// ```
539    pub fn set_subtitle_codec(mut self, subtitle_codec: impl Into<String>) -> Self {
540        self.subtitle_codec = Some(subtitle_codec.into());
541        self
542    }
543
544    /// Enables or disables **exit on error** behavior for the input.
545    ///
546    /// If set to `true`, FFmpeg will exit (stop processing) if it encounters any
547    /// decoding or demuxing error on this input. If set to `false` (the default),
548    /// FFmpeg may attempt to continue despite errors, skipping damaged portions.
549    ///
550    /// # Parameters
551    /// - `exit_on_error`: `true` to stop on errors, `false` to keep going.
552    ///
553    /// # Returns
554    /// * `Self` - allowing method chaining.
555    ///
556    /// # Example
557    /// ```rust
558    /// let input = Input::from("test.mp4")
559    ///     .set_exit_on_error(true);
560    /// ```
561    pub fn set_exit_on_error(mut self, exit_on_error: bool) -> Self {
562        self.exit_on_error = Some(exit_on_error);
563        self
564    }
565
566    /// Sets a **read rate** for this input, controlling how quickly frames are read.
567    ///
568    /// - If set to `1.0`, frames are read at their native frame rate.
569    /// - If set to another value (e.g., `0.5` or `2.0`), FFmpeg may attempt to read
570    ///   slower or faster, simulating changes in real-time playback speed.
571    ///
572    /// # Parameters
573    /// - `rate`: A floating-point value indicating the read rate multiplier.
574    ///
575    /// # Returns
576    /// * `Self` - allowing method chaining.
577    ///
578    /// # Example
579    /// ```rust
580    /// let input = Input::from("video.mp4")
581    ///     .set_readrate(0.5); // read at half speed
582    /// ```
583    pub fn set_readrate(mut self, rate: f32) -> Self {
584        self.readrate = Some(rate);
585        self
586    }
587
588    /// Sets the **start time** (in microseconds) from which to begin reading.
589    ///
590    /// FFmpeg will skip all data before this timestamp. This can be used to
591    /// implement “input seeking” or to only process a portion of the input.
592    ///
593    /// # Parameters
594    /// - `start_time_us`: The timestamp (in microseconds) at which to start reading.
595    ///
596    /// # Returns
597    /// * `Self` - allowing method chaining.
598    ///
599    /// # Example
600    /// ```rust
601    /// let input = Input::from("long_clip.mp4")
602    ///     .set_start_time_us(2_000_000); // Start at 2 seconds
603    /// ```
604    pub fn set_start_time_us(mut self, start_time_us: i64) -> Self {
605        self.start_time_us = Some(start_time_us);
606        self
607    }
608
609    /// Sets the **recording time** (in microseconds) for this input.
610    ///
611    /// FFmpeg will only read for the specified duration, ignoring data past this
612    /// limit. This can be used to trim or limit how much of the input is processed.
613    ///
614    /// # Parameters
615    /// - `recording_time_us`: The number of microseconds to read from the input.
616    ///
617    /// # Returns
618    /// * `Self` - allowing method chaining.
619    ///
620    /// # Example
621    /// ```rust
622    /// let input = Input::from("long_clip.mp4")
623    ///     .set_recording_time_us(5_000_000); // Only read 5 seconds
624    /// ```
625    pub fn set_recording_time_us(mut self, recording_time_us: i64) -> Self {
626        self.recording_time_us = Some(recording_time_us);
627        self
628    }
629
630    /// Sets a **stop time** (in microseconds) beyond which input data will be ignored.
631    ///
632    /// This is similar to [`set_recording_time_us`](Self::set_recording_time_us) but
633    /// specifically references an absolute timestamp in the stream. Once this timestamp
634    /// is reached, FFmpeg stops reading.
635    ///
636    /// # Parameters
637    /// - `stop_time_us`: The absolute timestamp (in microseconds) at which to stop reading.
638    ///
639    /// # Returns
640    /// * `Self` - allowing method chaining.
641    ///
642    /// # Example
643    /// ```rust
644    /// let input = Input::from("long_clip.mp4")
645    ///     .set_stop_time_us(10_000_000); // Stop reading at 10 seconds
646    /// ```
647    pub fn set_stop_time_us(mut self, stop_time_us: i64) -> Self {
648        self.stop_time_us = Some(stop_time_us);
649        self
650    }
651
652    /// Sets the number of **loops** to perform on this input stream.
653    ///
654    /// If FFmpeg reaches the end of the input, it can loop back and start from the
655    /// beginning, effectively repeating the content `stream_loop` times.
656    /// A negative value may indicate infinite looping (depending on FFmpeg’s actual behavior).
657    ///
658    /// # Parameters
659    /// - `count`: How many times to loop (e.g. `1` means one loop, `-1` might mean infinite).
660    ///
661    /// # Returns
662    /// * `Self` - allowing method chaining.
663    ///
664    /// # Example
665    /// ```rust
666    /// let input = Input::from("music.mp3")
667    ///     .set_stream_loop(2); // play the input 2 extra times
668    /// ```
669    pub fn set_stream_loop(mut self, count: i32) -> Self {
670        self.stream_loop = Some(count);
671        self
672    }
673
674    /// Specifies a **hardware acceleration** name for decoding this input.
675    ///
676    /// Common values might include `"cuda"`, `"vaapi"`, `"dxva2"`, `"videotoolbox"`, etc.
677    /// Whether it works depends on your FFmpeg build and the hardware you have available.
678    ///
679    /// # Parameters
680    /// - `hwaccel_name`: A string naming the hardware accel to use.
681    ///
682    /// # Returns
683    /// * `Self` - allowing method chaining.
684    ///
685    /// # Example
686    /// ```rust
687    /// let input = Input::from("video.mp4")
688    ///     .set_hwaccel("cuda");
689    /// ```
690    pub fn set_hwaccel(mut self, hwaccel_name: impl Into<String>) -> Self {
691        self.hwaccel = Some(hwaccel_name.into());
692        self
693    }
694
695    /// Selects a **hardware acceleration device** for decoding.
696    ///
697    /// For example, if you have multiple GPUs or want to specify a device node (like
698    /// `"/dev/dri/renderD128"` on Linux for VAAPI), you can pass it here. This option
699    /// must match the hardware accel you set via [`set_hwaccel`](Self::set_hwaccel) if
700    /// you expect decoding to succeed.
701    ///
702    /// # Parameters
703    /// - `device`: A string indicating the device path or identifier.
704    ///
705    /// # Returns
706    /// * `Self` - allowing method chaining.
707    ///
708    /// # Example
709    /// ```rust
710    /// let input = Input::from("video.mp4")
711    ///     .set_hwaccel("vaapi")
712    ///     .set_hwaccel_device("/dev/dri/renderD128");
713    /// ```
714    pub fn set_hwaccel_device(mut self, device: impl Into<String>) -> Self {
715        self.hwaccel_device = Some(device.into());
716        self
717    }
718
719    /// Sets the **output pixel format** to be used with hardware-accelerated decoding.
720    ///
721    /// Certain hardware decoders can produce various output pixel formats. This option
722    /// lets you specify which format (e.g., `"nv12"`, `"vaapi"`, etc.) is used during
723    /// the decode process.
724    /// Must be compatible with the chosen hardware accel and device.
725    ///
726    /// # Parameters
727    /// - `format`: A string naming the desired output pixel format (e.g. `"nv12"`).
728    ///
729    /// # Returns
730    /// * `Self` - allowing method chaining.
731    ///
732    /// # Example
733    /// ```rust
734    /// let input = Input::from("video.mp4")
735    ///     .set_hwaccel("cuda")
736    ///     .set_hwaccel_output_format("cuda");
737    /// ```
738    pub fn set_hwaccel_output_format(mut self, format: impl Into<String>) -> Self {
739        self.hwaccel_output_format = Some(format.into());
740        self
741    }
742
743    /// Sets an input-specific option.
744    ///
745    /// FFmpeg supports various input-specific options that can be passed to the demuxer.
746    /// These options allow fine-tuning of how the input stream is processed.
747    ///
748    /// **Example Usage:**
749    /// ```rust
750    /// let input = Input::new("rtsp://example.com/stream")
751    ///     .set_input_opt("rtsp_transport", "tcp")
752    ///     .set_input_opt("buffer_size", "1024000");
753    /// ```
754    ///
755    /// ### Common Input Options:
756    /// | Format | Option | Description |
757    /// |--------|--------|-------------|
758    /// | `rtsp` | `rtsp_transport=tcp` | Forces RTSP to use TCP instead of UDP |
759    /// | `udp`  | `fifo_size=1000000` | Sets FIFO buffer size |
760    /// | `rtmp` | `buffer_size=1024000` | Sets RTMP buffer size |
761    ///
762    /// **Parameters:**
763    /// - `key`: The input option name (e.g., `"rtsp_transport"`, `"buffer_size"`).
764    /// - `value`: The value to set (e.g., `"tcp"`, `"1024000"`).
765    ///
766    /// Returns the modified `Input` struct for chaining.
767    pub fn set_input_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
768        if let Some(ref mut opts) = self.input_opts {
769            opts.insert(key.into(), value.into());
770        } else {
771            let mut opts = HashMap::new();
772            opts.insert(key.into(), value.into());
773            self.input_opts = Some(opts);
774        }
775        self
776    }
777
778    /// Sets multiple input-specific options at once.
779    ///
780    /// This method allows setting multiple input options in a single call.
781    ///
782    /// **Example Usage:**
783    /// ```rust
784    /// let input = Input::new("rtsp://example.com/stream")
785    ///     .set_input_opts(vec![
786    ///         ("rtsp_transport", "tcp"),
787    ///         ("timeout", "5000000")
788    ///     ]);
789    /// ```
790    ///
791    /// **Parameters:**
792    /// - `opts`: A vector of key-value pairs representing input options.
793    ///
794    /// Returns the modified `Input` struct for chaining.
795    pub fn set_input_opts(mut self, opts: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
796        if let Some(ref mut input_opts) = self.input_opts {
797            for (key, value) in opts {
798                input_opts.insert(key.into(), value.into());
799            }
800        } else {
801            let mut input_opts = HashMap::new();
802            for (key, value) in opts {
803                input_opts.insert(key.into(), value.into());
804            }
805            self.input_opts = Some(input_opts);
806        }
807        self
808    }
809
810}
811
812impl From<Box<dyn FnMut(&mut [u8]) -> i32>> for Input {
813    fn from(read_callback: Box<dyn FnMut(&mut [u8]) -> i32>) -> Self {
814        Self {
815            url: None,
816            read_callback: Some(read_callback),
817            seek_callback: None,
818            frame_pipelines: None,
819            video_codec: None,
820            audio_codec: None,
821            subtitle_codec: None,
822            exit_on_error: None,
823            readrate: None,
824            start_time_us: None,
825            recording_time_us: None,
826            stop_time_us: None,
827            stream_loop: None,
828            hwaccel: None,
829            hwaccel_device: None,
830            hwaccel_output_format: None,
831            input_opts: None,
832        }
833    }
834}
835
836impl From<String> for Input {
837    fn from(url: String) -> Self {
838        Self {
839            url: Some(url),
840            read_callback: None,
841            seek_callback: None,
842            frame_pipelines: None,
843            video_codec: None,
844            audio_codec: None,
845            subtitle_codec: None,
846            exit_on_error: None,
847            readrate: None,
848            start_time_us: None,
849            recording_time_us: None,
850            stop_time_us: None,
851            stream_loop: None,
852            hwaccel: None,
853            hwaccel_device: None,
854            hwaccel_output_format: None,
855            input_opts: None,
856        }
857    }
858}
859
860impl From<&str> for Input {
861    fn from(url: &str) -> Self {
862        Self::from(String::from(url))
863    }
864}
865
866
867#[cfg(test)]
868mod tests {
869    use crate::core::context::input::Input;
870
871    #[test]
872    fn test_new_by_read_callback() {
873        let data_source = b"example custom data source".to_vec();
874        let input = Input::new_by_read_callback(move |buf| {
875            let len = data_source.len().min(buf.len());
876            buf[..len].copy_from_slice(&data_source[..len]);
877            len as i32 // Return the number of bytes written
878        });
879
880        let data_source2 = b"example custom data source2".to_vec();
881        let input = Input::new_by_read_callback(move |buf2| {
882            let len = data_source2.len().min(buf2.len());
883            buf2[..len].copy_from_slice(&data_source2[..len]);
884            len as i32 // Return the number of bytes written
885        });
886    }
887}