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}