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