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