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