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