ez_ffmpeg/core/context/input.rs
1use std::collections::HashMap;
2use crate::filter::frame_pipeline::FramePipeline;
3
4// Note: Input is Send if all callback fields are Send.
5// We require `+ Send` on callback types to ensure this.
6// Input is !Sync because FnMut callbacks require exclusive access.
7
8pub struct Input {
9 /// The URL of the input source.
10 ///
11 /// This specifies the source from which the input stream is obtained. It can be:
12 /// - A local file path (e.g., `file:///path/to/video.mp4`).
13 /// - A network stream (e.g., `rtmp://example.com/live/stream`).
14 /// - Any other URL supported by FFmpeg (e.g., `http://example.com/video.mp4`, `udp://...`).
15 ///
16 /// The URL must be valid. If the URL is invalid or unsupported,
17 /// the library will return an error when attempting to open the input stream.
18 pub(crate) url: Option<String>,
19
20 /// A callback function for custom data reading.
21 ///
22 /// The `read_callback` function allows you to provide custom logic for feeding data into
23 /// the input stream. This is useful for scenarios where the input does not come directly
24 /// from a standard source (like a file or URL), but instead from a custom data source,
25 /// such as an in-memory buffer or a custom network stream.
26 ///
27 /// ### Parameters:
28 /// - `buf: &mut [u8]`: A mutable buffer into which the data should be written.
29 /// The callback should fill this buffer with as much data as possible, up to its length.
30 ///
31 /// ### Return Value:
32 /// - **Positive Value**: The number of bytes successfully read into `buf`.
33 /// - **`ffmpeg_sys_next::AVERROR_EOF`**: Indicates the end of the input stream. No more data will be read.
34 /// - **Negative Value**: Indicates an error occurred, such as:
35 /// - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
36 /// - Custom-defined error codes depending on your implementation.
37 ///
38 /// ### Example:
39 /// ```rust,ignore
40 /// fn custom_read_callback(buf: &mut [u8]) -> i32 {
41 /// let data = b"example data stream";
42 /// let len = data.len().min(buf.len());
43 /// buf[..len].copy_from_slice(&data[..len]);
44 /// len as i32 // Return the number of bytes written into the buffer
45 /// }
46 /// ```
47 pub(crate) read_callback: Option<Box<dyn FnMut(&mut [u8]) -> i32 + Send>>,
48
49 /// A callback function for custom seeking within the input stream.
50 ///
51 /// The `seek_callback` function allows defining custom seeking behavior.
52 /// This is useful for data sources that support seeking, such as files or memory-mapped data.
53 /// For non-seekable streams (e.g., live network streams), this function may return an error.
54 ///
55 /// **FFmpeg may invoke `seek_callback` from multiple threads, so thread safety is required.**
56 /// When using a `File` as an input source, **use `Arc<Mutex<File>>` to ensure safe access.**
57 ///
58 /// ### Parameters:
59 /// - `offset: i64`: The target position in the stream for seeking.
60 /// - `whence: i32`: The seek mode defining how the `offset` should be interpreted:
61 /// - `ffmpeg_sys_next::SEEK_SET` (0): Seek to an absolute position.
62 /// - `ffmpeg_sys_next::SEEK_CUR` (1): Seek relative to the current position.
63 /// - `ffmpeg_sys_next::SEEK_END` (2): Seek relative to the end of the stream.
64 /// - `ffmpeg_sys_next::SEEK_HOLE` (3): Find the next file hole (sparse file support).
65 /// - `ffmpeg_sys_next::SEEK_DATA` (4): Find the next data block (sparse file support).
66 /// - `ffmpeg_sys_next::AVSEEK_FLAG_BYTE` (2): Seek using **byte offsets** instead of timestamps.
67 /// - `ffmpeg_sys_next::AVSEEK_SIZE` (65536): Query the **total size** of the stream.
68 /// - `ffmpeg_sys_next::AVSEEK_FORCE` (131072): **Force seeking even if normally restricted.**
69 ///
70 /// ### Return Value:
71 /// - **Positive Value**: The new offset position after seeking.
72 /// - **Negative Value**: An error occurred. Common errors include:
73 /// - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE)`: Seek is not supported.
74 /// - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
75 ///
76 /// ### Example (Handling multi-threaded access safely with `Arc<Mutex<File>>`):
77 /// Since FFmpeg may call `read_callback` and `seek_callback` from different threads,
78 /// **`Arc<Mutex<File>>` is used to ensure safe access across threads.**
79 ///
80 /// ```rust,ignore
81 /// use std::fs::File;
82 /// use std::io::{Seek, SeekFrom};
83 /// use std::sync::{Arc, Mutex};
84 ///
85 /// let file = Arc::new(Mutex::new(File::open("test.mp4").expect("Failed to open file")));
86 ///
87 /// let seek_callback = {
88 /// let file = Arc::clone(&file);
89 /// Box::new(move |offset: i64, whence: i32| -> i64 {
90 /// let mut file = file.lock().unwrap(); // Acquire lock
91 ///
92 /// // ✅ Handle AVSEEK_SIZE: Return total file size
93 /// if whence == ffmpeg_sys_next::AVSEEK_SIZE {
94 /// if let Ok(size) = file.metadata().map(|m| m.len() as i64) {
95 /// println!("FFmpeg requested stream size: {}", size);
96 /// return size;
97 /// }
98 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
99 /// }
100 ///
101 /// // ✅ Handle AVSEEK_FORCE: Ignore this flag when processing seek
102 /// let actual_whence = whence & !ffmpeg_sys_next::AVSEEK_FORCE;
103 ///
104 /// // ✅ Handle AVSEEK_FLAG_BYTE: Perform byte-based seek
105 /// if actual_whence & ffmpeg_sys_next::AVSEEK_FLAG_BYTE != 0 {
106 /// println!("FFmpeg requested byte-based seeking. Seeking to byte offset: {}", offset);
107 /// if let Ok(new_pos) = file.seek(SeekFrom::Start(offset as u64)) {
108 /// return new_pos as i64;
109 /// }
110 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
111 /// }
112 ///
113 /// // ✅ Handle SEEK_HOLE and SEEK_DATA (Linux only)
114 /// #[cfg(target_os = "linux")]
115 /// if actual_whence == ffmpeg_sys_next::SEEK_HOLE {
116 /// println!("FFmpeg requested SEEK_HOLE, but Rust std::fs does not support it.");
117 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
118 /// }
119 /// #[cfg(target_os = "linux")]
120 /// if actual_whence == ffmpeg_sys_next::SEEK_DATA {
121 /// println!("FFmpeg requested SEEK_DATA, but Rust std::fs does not support it.");
122 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
123 /// }
124 ///
125 /// // ✅ Standard seek modes
126 /// let seek_result = match actual_whence {
127 /// ffmpeg_sys_next::SEEK_SET => file.seek(SeekFrom::Start(offset as u64)),
128 /// ffmpeg_sys_next::SEEK_CUR => file.seek(SeekFrom::Current(offset)),
129 /// ffmpeg_sys_next::SEEK_END => file.seek(SeekFrom::End(offset)),
130 /// _ => {
131 /// println!("Unsupported seek mode: {}", whence);
132 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
133 /// }
134 /// };
135 ///
136 /// match seek_result {
137 /// Ok(new_pos) => {
138 /// println!("Seek successful, new position: {}", new_pos);
139 /// new_pos as i64
140 /// }
141 /// Err(e) => {
142 /// println!("Seek failed: {}", e);
143 /// ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64
144 /// }
145 /// }
146 /// })
147 /// };
148 /// ```
149 pub(crate) seek_callback: Option<Box<dyn FnMut(i64, i32) -> i64 + Send>>,
150
151 /// The pipeline that provides custom processing for decoded frames.
152 ///
153 /// After the input data is decoded into `Frame` objects, these frames
154 /// are passed through the `frame_pipeline`. Each frame goes through
155 /// a series of `FrameFilter` objects in the pipeline, allowing for
156 /// customized processing (e.g., filtering, transformation, etc.).
157 ///
158 /// If `None`, no processing pipeline is applied to the decoded frames.
159 pub(crate) frame_pipelines: Option<Vec<FramePipeline>>,
160
161 /// The input format for the source.
162 ///
163 /// This field specifies which container or device format FFmpeg should use to read the input.
164 /// If `None`, FFmpeg will attempt to automatically detect the format based on the source URL,
165 /// file extension, or stream data.
166 ///
167 /// You might need to specify a format explicitly in cases where automatic detection fails or
168 /// when you must force a particular format. For example:
169 /// - When capturing from a specific device on macOS (using `avfoundation`).
170 /// - When capturing on Windows devices (using `dshow`).
171 /// - When dealing with raw streams or unusual data sources.
172 pub(crate) format: Option<String>,
173
174 /// The codec to be used for **video** decoding.
175 ///
176 /// If set, this forces FFmpeg to use the specified video codec for decoding.
177 /// Otherwise, FFmpeg will attempt to auto-detect the best available codec.
178 pub(crate) video_codec: Option<String>,
179
180 /// The codec to be used for **audio** decoding.
181 ///
182 /// If set, this forces FFmpeg to use the specified audio codec for decoding.
183 /// Otherwise, FFmpeg will attempt to auto-detect the best available codec.
184 pub(crate) audio_codec: Option<String>,
185
186 /// The codec to be used for **subtitle** decoding.
187 ///
188 /// If set, this forces FFmpeg to use the specified subtitle codec for decoding.
189 /// Otherwise, FFmpeg will attempt to auto-detect the best available codec.
190 pub(crate) subtitle_codec: Option<String>,
191
192 pub(crate) exit_on_error: Option<bool>,
193
194 /// read input at specified rate.
195 /// when set 1. read input at native frame rate.
196 pub(crate) readrate: Option<f32>,
197 pub(crate) start_time_us: Option<i64>,
198 pub(crate) recording_time_us: Option<i64>,
199 pub(crate) stop_time_us: Option<i64>,
200
201 /// set number of times input stream shall be looped
202 pub(crate) stream_loop: Option<i32>,
203
204 /// Hardware Acceleration name
205 /// use Hardware accelerated decoding
206 pub(crate) hwaccel: Option<String>,
207 /// select a device for HW acceleration
208 pub(crate) hwaccel_device: Option<String>,
209 /// select output format used with HW accelerated decoding
210 pub(crate) hwaccel_output_format: Option<String>,
211
212 /// Log-level offset applied to this input's decoders
213 /// (`AVCodecContext.log_level_offset`).
214 pub(crate) log_level_offset: Option<i32>,
215
216 /// Input options for avformat_open_input.
217 ///
218 /// This field stores options that are passed to FFmpeg's `avformat_open_input()` function.
219 /// These options can affect different layers of the input processing pipeline:
220 ///
221 /// **Format/Demuxer options:**
222 /// - `probesize` - Maximum data to probe for format detection
223 /// - `analyzeduration` - Duration to analyze for stream info
224 /// - `fflags` - Format flags (e.g., "+genpts")
225 ///
226 /// **Protocol options:**
227 /// - `user_agent` - HTTP User-Agent header
228 /// - `timeout` - Network timeout in microseconds
229 /// - `headers` - Custom HTTP headers
230 ///
231 /// **Device options:**
232 /// - `framerate` - Input framerate (for avfoundation, dshow, etc.)
233 /// - `video_size` - Input video resolution
234 /// - `pixel_format` - Input pixel format
235 ///
236 /// **General input options:**
237 /// - `thread_queue_size` - Input thread queue size
238 /// - `re` - Read input at native frame rate
239 ///
240 /// These options allow fine-tuning of input behavior across different components
241 /// of the FFmpeg input pipeline.
242 pub(crate) input_opts: Option<HashMap<String, String>>,
243
244 /// Automatically rotate video based on display matrix metadata.
245 ///
246 /// When enabled (default), videos with rotation metadata (common in smartphone
247 /// recordings) will be automatically rotated to the correct orientation using
248 /// transpose/hflip/vflip filters.
249 ///
250 /// Set to `false` to disable automatic rotation and preserve the original
251 /// video orientation.
252 ///
253 /// ## FFmpeg CLI equivalent
254 /// ```bash
255 /// # Disable autorotate
256 /// ffmpeg -autorotate 0 -i input.mp4 output.mp4
257 ///
258 /// # Enable autorotate (default)
259 /// ffmpeg -autorotate 1 -i input.mp4 output.mp4
260 /// ```
261 ///
262 /// ## FFmpeg source reference (FFmpeg 7.x)
263 /// - Default value: `ffmpeg_demux.c:1270` (`ds->autorotate = 1`)
264 /// - Flag setting: `ffmpeg_demux.c:1088` (`IFILTER_FLAG_AUTOROTATE`)
265 /// - Filter insertion: `ffmpeg_filter.c:1744-1778`
266 pub(crate) autorotate: Option<bool>,
267
268 /// Timestamp scale factor for pts/dts values.
269 ///
270 /// This multiplier is applied to packet timestamps after ts_offset addition.
271 /// Default is 1.0 (no scaling). Values must be positive.
272 ///
273 /// This is useful for fixing videos with incorrect timestamps or for
274 /// special timestamp manipulation scenarios.
275 ///
276 /// ## FFmpeg CLI equivalent
277 /// ```bash
278 /// # Scale timestamps by 2x
279 /// ffmpeg -itsscale 2.0 -i input.mp4 output.mp4
280 ///
281 /// # Scale timestamps by 0.5x (half speed effect on timestamps)
282 /// ffmpeg -itsscale 0.5 -i input.mp4 output.mp4
283 /// ```
284 ///
285 /// ## FFmpeg source reference (FFmpeg 7.x)
286 /// - Default value: `ffmpeg_demux.c:1267` (`ds->ts_scale = 1.0`)
287 /// - Application: `ffmpeg_demux.c:404-406` (applied after ts_offset)
288 pub(crate) ts_scale: Option<f64>,
289
290 /// Forced framerate for the input video stream.
291 ///
292 /// When set, this overrides the DTS estimation logic to use the specified
293 /// framerate for computing `next_dts` in the video stream. By default (None),
294 /// the actual packet duration is used for DTS estimation, matching FFmpeg CLI
295 /// behavior when `-r` is not specified.
296 ///
297 /// This affects all video DTS estimation, including recording_time cutoff
298 /// decisions during stream copy and the output stream time_base when set via
299 /// `streamcopy_init`.
300 ///
301 /// ## FFmpeg CLI equivalent
302 /// ```bash
303 /// # Force input framerate to 30fps
304 /// ffmpeg -r 30 -i input.mp4 output.mp4
305 /// ```
306 ///
307 /// ## FFmpeg source reference (FFmpeg 7.x)
308 /// - Field: `ffmpeg.h:452` (`ist->framerate`, only set with `-r`)
309 /// - Application: `ffmpeg_demux.c:329-333` (used in `ist_dts_update`)
310 pub(crate) framerate: Option<(i32, i32)>,
311}
312
313impl Input {
314 pub fn new(url: impl Into<String>) -> Self {
315 url.into().into()
316 }
317
318 /// Creates a new `Input` instance with a custom read callback.
319 ///
320 /// This method initializes an `Input` object that uses a provided `read_callback` function
321 /// to supply data to the input stream. This is particularly useful for custom data sources
322 /// such as in-memory buffers, network streams, or other non-standard input mechanisms.
323 ///
324 /// ### Parameters:
325 /// - `read_callback: fn(buf: &mut [u8]) -> i32`: A function pointer that fills the provided
326 /// mutable buffer with data and returns the number of bytes read.
327 ///
328 /// ### Return Value:
329 /// - Returns a new `Input` instance configured with the specified `read_callback`.
330 ///
331 /// ### Behavior of `read_callback`:
332 /// - **Positive Value**: Indicates the number of bytes successfully read.
333 /// - **`ffmpeg_sys_next::AVERROR_EOF`**: Indicates the end of the stream. The library will stop requesting data.
334 /// - **Negative Value**: Indicates an error occurred. For example:
335 /// - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: Represents an input/output error.
336 /// - Other custom-defined error codes can also be returned to signal specific issues.
337 ///
338 /// ### Example:
339 /// ```rust,ignore
340 /// let input = Input::new_by_read_callback(move |buf| {
341 /// let data = b"example custom data source";
342 /// let len = data.len().min(buf.len());
343 /// buf[..len].copy_from_slice(&data[..len]);
344 /// len as i32 // Return the number of bytes written
345 /// });
346 /// ```
347 pub fn new_by_read_callback<F>(read_callback: F) -> Self
348 where
349 F: FnMut(&mut [u8]) -> i32 + Send + 'static,
350 {
351 (Box::new(read_callback) as Box<dyn FnMut(&mut [u8]) -> i32 + Send>).into()
352 }
353
354 /// Sets a custom seek callback for the input stream.
355 ///
356 /// This function assigns a user-defined function that handles seeking within the input stream.
357 /// It is required when using custom data sources that support random access, such as files,
358 /// memory-mapped buffers, or seekable network streams.
359 ///
360 /// **FFmpeg may invoke `seek_callback` from different threads.**
361 /// If using a `File` as the data source, **wrap it in `Arc<Mutex<File>>`** to ensure
362 /// thread-safe access across multiple threads.
363 ///
364 /// ### Parameters:
365 /// - `seek_callback: FnMut(i64, i32) -> i64`: A function that handles seek operations.
366 /// - `offset: i64`: The target seek position in the stream.
367 /// - `whence: i32`: The seek mode, which determines how `offset` should be interpreted:
368 /// - `ffmpeg_sys_next::SEEK_SET` (0) - Seek to an absolute position.
369 /// - `ffmpeg_sys_next::SEEK_CUR` (1) - Seek relative to the current position.
370 /// - `ffmpeg_sys_next::SEEK_END` (2) - Seek relative to the end of the stream.
371 /// - `ffmpeg_sys_next::SEEK_HOLE` (3) - Find the next hole in a sparse file (Linux only).
372 /// - `ffmpeg_sys_next::SEEK_DATA` (4) - Find the next data block in a sparse file (Linux only).
373 /// - `ffmpeg_sys_next::AVSEEK_FLAG_BYTE` (2) - Seek using byte offset instead of timestamps.
374 /// - `ffmpeg_sys_next::AVSEEK_SIZE` (65536) - Query the total size of the stream.
375 /// - `ffmpeg_sys_next::AVSEEK_FORCE` (131072) - Force seeking, even if normally restricted.
376 ///
377 /// ### Return Value:
378 /// - Returns `Self`, allowing for method chaining.
379 ///
380 /// ### Behavior of `seek_callback`:
381 /// - **Positive Value**: The new offset position after seeking.
382 /// - **Negative Value**: An error occurred, such as:
383 /// - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE)`: Seek is not supported.
384 /// - `ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)`: General I/O error.
385 ///
386 /// ### Example (Thread-safe seek callback using `Arc<Mutex<File>>`):
387 /// Since `FFmpeg` may call `read_callback` and `seek_callback` from different threads,
388 /// **use `Arc<Mutex<File>>` to ensure safe concurrent access.**
389 ///
390 /// ```rust,ignore
391 /// use std::fs::File;
392 /// use std::io::{Read, Seek, SeekFrom};
393 /// use std::sync::{Arc, Mutex};
394 ///
395 /// // ✅ Wrap the file in Arc<Mutex<>> for safe shared access
396 /// let file = Arc::new(Mutex::new(File::open("test.mp4").expect("Failed to open file")));
397 ///
398 /// // ✅ Thread-safe read callback
399 /// let read_callback = {
400 /// let file = Arc::clone(&file);
401 /// move |buf: &mut [u8]| -> i32 {
402 /// let mut file = file.lock().unwrap();
403 /// match file.read(buf) {
404 /// Ok(0) => {
405 /// println!("Read EOF");
406 /// ffmpeg_sys_next::AVERROR_EOF
407 /// }
408 /// Ok(bytes_read) => bytes_read as i32,
409 /// Err(e) => {
410 /// println!("Read error: {}", e);
411 /// ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO)
412 /// }
413 /// }
414 /// }
415 /// };
416 ///
417 /// // ✅ Thread-safe seek callback
418 /// let seek_callback = {
419 /// let file = Arc::clone(&file);
420 /// Box::new(move |offset: i64, whence: i32| -> i64 {
421 /// let mut file = file.lock().unwrap();
422 ///
423 /// // ✅ Handle AVSEEK_SIZE: Return total file size
424 /// if whence == ffmpeg_sys_next::AVSEEK_SIZE {
425 /// if let Ok(size) = file.metadata().map(|m| m.len() as i64) {
426 /// println!("FFmpeg requested stream size: {}", size);
427 /// return size;
428 /// }
429 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
430 /// }
431 ///
432 /// // ✅ Ignore AVSEEK_FORCE flag
433 /// let actual_whence = whence & !ffmpeg_sys_next::AVSEEK_FORCE;
434 ///
435 /// // ✅ Handle AVSEEK_FLAG_BYTE: Perform byte-based seek
436 /// if actual_whence & ffmpeg_sys_next::AVSEEK_FLAG_BYTE != 0 {
437 /// println!("FFmpeg requested byte-based seeking. Seeking to byte offset: {}", offset);
438 /// if let Ok(new_pos) = file.seek(SeekFrom::Start(offset as u64)) {
439 /// return new_pos as i64;
440 /// }
441 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64;
442 /// }
443 ///
444 /// // ✅ Handle SEEK_HOLE and SEEK_DATA (Linux only)
445 /// #[cfg(target_os = "linux")]
446 /// if actual_whence == ffmpeg_sys_next::SEEK_HOLE {
447 /// println!("FFmpeg requested SEEK_HOLE, but Rust std::fs does not support it.");
448 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
449 /// }
450 /// #[cfg(target_os = "linux")]
451 /// if actual_whence == ffmpeg_sys_next::SEEK_DATA {
452 /// println!("FFmpeg requested SEEK_DATA, but Rust std::fs does not support it.");
453 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
454 /// }
455 ///
456 /// // ✅ Standard seek modes
457 /// let seek_result = match actual_whence {
458 /// ffmpeg_sys_next::SEEK_SET => file.seek(SeekFrom::Start(offset as u64)),
459 /// ffmpeg_sys_next::SEEK_CUR => file.seek(SeekFrom::Current(offset)),
460 /// ffmpeg_sys_next::SEEK_END => file.seek(SeekFrom::End(offset)),
461 /// _ => {
462 /// println!("Unsupported seek mode: {}", whence);
463 /// return ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::ESPIPE) as i64;
464 /// }
465 /// };
466 ///
467 /// match seek_result {
468 /// Ok(new_pos) => {
469 /// println!("Seek successful, new position: {}", new_pos);
470 /// new_pos as i64
471 /// }
472 /// Err(e) => {
473 /// println!("Seek failed: {}", e);
474 /// ffmpeg_sys_next::AVERROR(ffmpeg_sys_next::EIO) as i64
475 /// }
476 /// }
477 /// })
478 /// };
479 ///
480 /// let input = Input::new_by_read_callback(read_callback).set_seek_callback(seek_callback);
481 /// ```
482 pub fn set_seek_callback<F>(mut self, seek_callback: F) -> Self
483 where
484 F: FnMut(i64, i32) -> i64 + Send + 'static,
485 {
486 self.seek_callback = Some(Box::new(seek_callback) as Box<dyn FnMut(i64, i32) -> i64 + Send>);
487 self
488 }
489
490 /// Replaces the entire frame-processing pipeline with a new sequence
491 /// of transformations for **post-decoding** frames on this `Input`.
492 ///
493 /// This method clears any previously set pipelines and replaces them with the provided list.
494 ///
495 /// # Parameters
496 /// * `frame_pipelines` - A list of [`FramePipeline`] instances defining the
497 /// transformations to apply to decoded frames.
498 ///
499 /// # Returns
500 /// * `Self` - Returns the modified `Input`, enabling method chaining.
501 ///
502 /// # Example
503 /// ```rust,ignore
504 /// let input = Input::from("my_video.mp4")
505 /// .set_frame_pipelines(vec![
506 /// FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_VIDEO).filter("opengl", Box::new(my_filter)),
507 /// // Additional pipelines...
508 /// ]);
509 /// ```
510 pub fn set_frame_pipelines(mut self, frame_pipelines: Vec<impl Into<FramePipeline>>) -> Self {
511 self.frame_pipelines = Some(frame_pipelines.into_iter().map(|frame_pipeline| frame_pipeline.into()).collect());
512 self
513 }
514
515 /// Adds a single [`FramePipeline`] to the existing pipeline list.
516 ///
517 /// If no pipelines are currently defined, this method creates a new pipeline list.
518 /// Otherwise, it appends the provided pipeline to the existing transformations.
519 ///
520 /// # Parameters
521 /// * `frame_pipeline` - A [`FramePipeline`] defining a transformation.
522 ///
523 /// # Returns
524 /// * `Self` - Returns the modified `Input`, enabling method chaining.
525 ///
526 /// # Example
527 /// ```rust,ignore
528 /// let input = Input::from("my_video.mp4")
529 /// .add_frame_pipeline(FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_VIDEO).filter("opengl", Box::new(my_filter)).build())
530 /// .add_frame_pipeline(FramePipelineBuilder::new(AVMediaType::AVMEDIA_TYPE_AUDIO).filter("my_custom_filter1", Box::new(...)).filter("my_custom_filter2", Box::new(...)).build());
531 /// ```
532 pub fn add_frame_pipeline(mut self, frame_pipeline: impl Into<FramePipeline>) -> Self {
533 if self.frame_pipelines.is_none() {
534 self.frame_pipelines = Some(vec![frame_pipeline.into()]);
535 } else {
536 self.frame_pipelines
537 .as_mut()
538 .unwrap()
539 .push(frame_pipeline.into());
540 }
541 self
542 }
543
544 /// Sets the input format for the container or device.
545 ///
546 /// By default, if no format is specified,
547 /// FFmpeg will attempt to detect the format automatically. However, certain
548 /// use cases require specifying the format explicitly:
549 /// - Using device-specific inputs (e.g., `avfoundation` on macOS, `dshow` on Windows).
550 /// - Handling raw streams or formats that FFmpeg may not detect automatically.
551 ///
552 /// ### Parameters:
553 /// - `format`: A string specifying the desired input format (e.g., `mp4`, `flv`, `avfoundation`).
554 ///
555 /// ### Return Value:
556 /// - Returns the `Input` instance with the newly set format.
557 pub fn set_format(mut self, format: impl Into<String>) -> Self {
558 self.format = Some(format.into());
559 self
560 }
561
562 /// Sets the **video codec** to be used for decoding.
563 ///
564 /// By default, FFmpeg will automatically select an appropriate video codec
565 /// based on the input format and available decoders. However, this method
566 /// allows you to override that selection and force a specific codec.
567 ///
568 /// # Common Video Codecs:
569 /// | Codec | Description |
570 /// |-------|-------------|
571 /// | `h264` | H.264 (AVC), widely supported and efficient |
572 /// | `hevc` | H.265 (HEVC), better compression at higher complexity |
573 /// | `vp9` | VP9, open-source alternative to H.265 |
574 /// | `av1` | AV1, newer open-source codec with improved compression |
575 /// | `mpeg4` | MPEG-4 Part 2, older but still used in some cases |
576 ///
577 /// # Arguments
578 /// * `video_codec` - A string representing the desired video codec (e.g., `"h264"`, `"hevc"`).
579 ///
580 /// # Returns
581 /// * `Self` - Returns the modified `Input` struct, allowing for method chaining.
582 ///
583 /// # Example:
584 /// ```rust,ignore
585 /// let input = Input::from("video.mp4").set_video_codec("h264");
586 /// ```
587 pub fn set_video_codec(mut self, video_codec: impl Into<String>) -> Self {
588 self.video_codec = Some(video_codec.into());
589 self
590 }
591
592 /// Sets the **audio codec** to be used for decoding.
593 ///
594 /// By default, FFmpeg will automatically select an appropriate audio codec
595 /// based on the input format and available decoders. However, this method
596 /// allows you to specify a preferred codec.
597 ///
598 /// # Common Audio Codecs:
599 /// | Codec | Description |
600 /// |-------|-------------|
601 /// | `aac` | AAC, commonly used for MP4 and streaming |
602 /// | `mp3` | MP3, widely supported but lower efficiency |
603 /// | `opus` | Opus, high-quality open-source codec |
604 /// | `vorbis` | Vorbis, used in Ogg containers |
605 /// | `flac` | FLAC, lossless audio format |
606 ///
607 /// # Arguments
608 /// * `audio_codec` - A string representing the desired audio codec (e.g., `"aac"`, `"mp3"`).
609 ///
610 /// # Returns
611 /// * `Self` - Returns the modified `Input` struct, allowing for method chaining.
612 ///
613 /// # Example:
614 /// ```rust,ignore
615 /// let input = Input::from("audio.mp3").set_audio_codec("aac");
616 /// ```
617 pub fn set_audio_codec(mut self, audio_codec: impl Into<String>) -> Self {
618 self.audio_codec = Some(audio_codec.into());
619 self
620 }
621
622 /// Sets the **subtitle codec** to be used for decoding.
623 ///
624 /// By default, FFmpeg will automatically select an appropriate subtitle codec
625 /// based on the input format and available decoders. This method lets you specify
626 /// a particular subtitle codec.
627 ///
628 /// # Common Subtitle Codecs:
629 /// | Codec | Description |
630 /// |-------|-------------|
631 /// | `ass` | Advanced SubStation Alpha (ASS) subtitles |
632 /// | `srt` | SubRip Subtitle format (SRT) |
633 /// | `mov_text` | Subtitles in MP4 containers |
634 /// | `subrip` | Plain-text subtitle format |
635 ///
636 /// # Arguments
637 /// * `subtitle_codec` - A string representing the desired subtitle codec (e.g., `"mov_text"`, `"ass"`, `"srt"`).
638 ///
639 /// # Returns
640 /// * `Self` - Returns the modified `Input` struct, allowing for method chaining.
641 ///
642 /// # Example:
643 /// ```rust,ignore
644 /// let input = Input::from("movie.mkv").set_subtitle_codec("ass");
645 /// ```
646 pub fn set_subtitle_codec(mut self, subtitle_codec: impl Into<String>) -> Self {
647 self.subtitle_codec = Some(subtitle_codec.into());
648 self
649 }
650
651 /// Enables or disables **exit on error** behavior for the input.
652 ///
653 /// If set to `true`, FFmpeg will exit (stop processing) if it encounters any
654 /// decoding or demuxing error on this input. If set to `false` (the default),
655 /// FFmpeg may attempt to continue despite errors, skipping damaged portions.
656 ///
657 /// # Parameters
658 /// - `exit_on_error`: `true` to stop on errors, `false` to keep going.
659 ///
660 /// # Returns
661 /// * `Self` - allowing method chaining.
662 ///
663 /// # Example
664 /// ```rust,ignore
665 /// let input = Input::from("test.mp4")
666 /// .set_exit_on_error(true);
667 /// ```
668 pub fn set_exit_on_error(mut self, exit_on_error: bool) -> Self {
669 self.exit_on_error = Some(exit_on_error);
670 self
671 }
672
673 /// Sets a **read rate** for this input, controlling how quickly frames are read.
674 ///
675 /// - If set to `1.0`, frames are read at their native frame rate.
676 /// - If set to another value (e.g., `0.5` or `2.0`), FFmpeg may attempt to read
677 /// slower or faster, simulating changes in real-time playback speed.
678 ///
679 /// # Parameters
680 /// - `rate`: A floating-point value indicating the read rate multiplier.
681 ///
682 /// # Returns
683 /// * `Self` - allowing method chaining.
684 ///
685 /// # Example
686 /// ```rust,ignore
687 /// let input = Input::from("video.mp4")
688 /// .set_readrate(0.5); // read at half speed
689 /// ```
690 pub fn set_readrate(mut self, rate: f32) -> Self {
691 self.readrate = Some(rate);
692 self
693 }
694
695 /// Sets a **log-level offset** for this input's decoders
696 /// (`AVCodecContext.log_level_offset`).
697 ///
698 /// FFmpeg shifts the effective level of every message a decoder emits by
699 /// this offset. Expected decoder noise — e.g. h264 `Missing reference
700 /// picture` / `decode_slice_header error` bursts right after seeking to a
701 /// non-keyframe (open GOP) — is logged at ERROR level; an offset of `8`
702 /// (one AV_LOG step) demotes those to WARNING for this input only,
703 /// without hiding errors from other inputs.
704 ///
705 /// # Arguments
706 /// * `offset` - Added to each message's log level; positive values make
707 /// this input's decoders quieter, negative values make them louder.
708 ///
709 /// # Returns
710 /// * `Self` - allowing method chaining.
711 ///
712 /// # Example
713 /// ```rust,ignore
714 /// // Screenshot after seek: demote expected h264 reference errors.
715 /// let input = Input::from("video.mp4")
716 /// .set_log_level_offset(8);
717 /// ```
718 pub fn set_log_level_offset(mut self, offset: i32) -> Self {
719 self.log_level_offset = Some(offset);
720 self
721 }
722
723 /// Sets the **start time** (in microseconds) from which to begin reading.
724 ///
725 /// FFmpeg will skip all data before this timestamp. This can be used to
726 /// implement “input seeking” or to only process a portion of the input.
727 ///
728 /// # Parameters
729 /// - `start_time_us`: The timestamp (in microseconds) at which to start reading.
730 ///
731 /// # Returns
732 /// * `Self` - allowing method chaining.
733 ///
734 /// # Example
735 /// ```rust,ignore
736 /// let input = Input::from("long_clip.mp4")
737 /// .set_start_time_us(2_000_000); // Start at 2 seconds
738 /// ```
739 pub fn set_start_time_us(mut self, start_time_us: i64) -> Self {
740 self.start_time_us = Some(start_time_us);
741 self
742 }
743
744 /// Sets the **recording time** (in microseconds) for this input.
745 ///
746 /// FFmpeg will only read for the specified duration, ignoring data past this
747 /// limit. This can be used to trim or limit how much of the input is processed.
748 ///
749 /// # Parameters
750 /// - `recording_time_us`: The number of microseconds to read from the input.
751 ///
752 /// # Returns
753 /// * `Self` - allowing method chaining.
754 ///
755 /// # Example
756 /// ```rust,ignore
757 /// let input = Input::from("long_clip.mp4")
758 /// .set_recording_time_us(5_000_000); // Only read 5 seconds
759 /// ```
760 pub fn set_recording_time_us(mut self, recording_time_us: i64) -> Self {
761 self.recording_time_us = Some(recording_time_us);
762 self
763 }
764
765 /// Sets a **stop time** (in microseconds) beyond which input data will be ignored.
766 ///
767 /// This is similar to [`set_recording_time_us`](Self::set_recording_time_us) but
768 /// specifically references an absolute timestamp in the stream. Once this timestamp
769 /// is reached, FFmpeg stops reading.
770 ///
771 /// # Parameters
772 /// - `stop_time_us`: The absolute timestamp (in microseconds) at which to stop reading.
773 ///
774 /// # Returns
775 /// * `Self` - allowing method chaining.
776 ///
777 /// # Example
778 /// ```rust,ignore
779 /// let input = Input::from("long_clip.mp4")
780 /// .set_stop_time_us(10_000_000); // Stop reading at 10 seconds
781 /// ```
782 pub fn set_stop_time_us(mut self, stop_time_us: i64) -> Self {
783 self.stop_time_us = Some(stop_time_us);
784 self
785 }
786
787 /// Sets the number of **loops** to perform on this input stream.
788 ///
789 /// If FFmpeg reaches the end of the input, it can loop back and start from the
790 /// beginning, effectively repeating the content `stream_loop` times.
791 /// A negative value may indicate infinite looping (depending on FFmpeg’s actual behavior).
792 ///
793 /// # Parameters
794 /// - `count`: How many times to loop (e.g. `1` means one loop, `-1` might mean infinite).
795 ///
796 /// # Returns
797 /// * `Self` - allowing method chaining.
798 ///
799 /// # Example
800 /// ```rust,ignore
801 /// let input = Input::from("music.mp3")
802 /// .set_stream_loop(2); // play the input 2 extra times
803 /// ```
804 pub fn set_stream_loop(mut self, count: i32) -> Self {
805 self.stream_loop = Some(count);
806 self
807 }
808
809 /// Specifies a **hardware acceleration** name for decoding this input.
810 ///
811 /// Common values might include `"cuda"`, `"vaapi"`, `"dxva2"`, `"videotoolbox"`, etc.
812 /// Whether it works depends on your FFmpeg build and the hardware you have available.
813 ///
814 /// # Parameters
815 /// - `hwaccel_name`: A string naming the hardware accel to use.
816 ///
817 /// # Returns
818 /// * `Self` - allowing method chaining.
819 ///
820 /// # Example
821 /// ```rust,ignore
822 /// let input = Input::from("video.mp4")
823 /// .set_hwaccel("cuda");
824 /// ```
825 pub fn set_hwaccel(mut self, hwaccel_name: impl Into<String>) -> Self {
826 self.hwaccel = Some(hwaccel_name.into());
827 self
828 }
829
830 /// Selects a **hardware acceleration device** for decoding.
831 ///
832 /// For example, if you have multiple GPUs or want to specify a device node (like
833 /// `"/dev/dri/renderD128"` on Linux for VAAPI), you can pass it here. This option
834 /// must match the hardware accel you set via [`set_hwaccel`](Self::set_hwaccel) if
835 /// you expect decoding to succeed.
836 ///
837 /// # Parameters
838 /// - `device`: A string indicating the device path or identifier.
839 ///
840 /// # Returns
841 /// * `Self` - allowing method chaining.
842 ///
843 /// # Example
844 /// ```rust,ignore
845 /// let input = Input::from("video.mp4")
846 /// .set_hwaccel("vaapi")
847 /// .set_hwaccel_device("/dev/dri/renderD128");
848 /// ```
849 pub fn set_hwaccel_device(mut self, device: impl Into<String>) -> Self {
850 self.hwaccel_device = Some(device.into());
851 self
852 }
853
854 /// Sets the **output pixel format** to be used with hardware-accelerated decoding.
855 ///
856 /// Certain hardware decoders can produce various output pixel formats. This option
857 /// lets you specify which format (e.g., `"nv12"`, `"vaapi"`, etc.) is used during
858 /// the decode process.
859 /// Must be compatible with the chosen hardware accel and device.
860 ///
861 /// # Parameters
862 /// - `format`: A string naming the desired output pixel format (e.g. `"nv12"`).
863 ///
864 /// # Returns
865 /// * `Self` - allowing method chaining.
866 ///
867 /// # Example
868 /// ```rust,ignore
869 /// let input = Input::from("video.mp4")
870 /// .set_hwaccel("cuda")
871 /// .set_hwaccel_output_format("cuda");
872 /// ```
873 pub fn set_hwaccel_output_format(mut self, format: impl Into<String>) -> Self {
874 self.hwaccel_output_format = Some(format.into());
875 self
876 }
877
878 /// Sets a single input option for avformat_open_input.
879 ///
880 /// This method configures options that will be passed to FFmpeg's `avformat_open_input()`
881 /// function. The options can control behavior at different levels including format detection,
882 /// protocol handling, device configuration, and general input processing.
883 ///
884 /// **Example Usage:**
885 /// ```rust,ignore
886 /// let input = Input::new("avfoundation:0")
887 /// .set_input_opt("framerate", "30")
888 /// .set_input_opt("probesize", "5000000");
889 /// ```
890 ///
891 /// ### Parameters:
892 /// - `key`: The option name (e.g., `"framerate"`, `"probesize"`, `"timeout"`).
893 /// - `value`: The option value (e.g., `"30"`, `"5000000"`, `"10000000"`).
894 ///
895 /// ### Return Value:
896 /// - Returns the modified `Input` instance for method chaining.
897 pub fn set_input_opt(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
898 if let Some(ref mut opts) = self.input_opts {
899 opts.insert(key.into(), value.into());
900 } else {
901 let mut opts = HashMap::new();
902 opts.insert(key.into(), value.into());
903 self.input_opts = Some(opts);
904 }
905 self
906 }
907
908 /// Sets multiple input options at once for avformat_open_input.
909 ///
910 /// This method allows setting multiple options in a single call, which will all be
911 /// passed to FFmpeg's `avformat_open_input()` function. Each key-value pair will be
912 /// inserted into the options map, overwriting any existing keys with the same name.
913 ///
914 /// **Example Usage:**
915 /// ```rust,ignore
916 /// let input = Input::new("http://example.com/stream.m3u8")
917 /// .set_input_opts(vec![
918 /// ("user_agent", "MyApp/1.0"),
919 /// ("timeout", "10000000"),
920 /// ("probesize", "5000000"),
921 /// ]);
922 /// ```
923 ///
924 /// ### Parameters:
925 /// - `opts`: A vector of key-value pairs representing input options.
926 ///
927 /// ### Return Value:
928 /// - Returns the modified `Input` instance for method chaining.
929 pub fn set_input_opts(mut self, opts: Vec<(impl Into<String>, impl Into<String>)>) -> Self {
930 if let Some(ref mut input_opts) = self.input_opts {
931 for (key, value) in opts {
932 input_opts.insert(key.into(), value.into());
933 }
934 } else {
935 let mut input_opts = HashMap::new();
936 for (key, value) in opts {
937 input_opts.insert(key.into(), value.into());
938 }
939 self.input_opts = Some(input_opts);
940 }
941 self
942 }
943
944 /// Sets whether to automatically rotate video based on display matrix metadata.
945 ///
946 /// When enabled (default is `true`), videos with rotation metadata (common in
947 /// smartphone recordings) will be automatically rotated to the correct orientation
948 /// using transpose/hflip/vflip filters.
949 ///
950 /// # Parameters
951 /// - `autorotate`: `true` to enable automatic rotation (default), `false` to disable.
952 ///
953 /// # Returns
954 /// * `Self` - allowing method chaining.
955 ///
956 /// # FFmpeg CLI equivalent
957 /// ```bash
958 /// ffmpeg -autorotate 0 -i input.mp4 output.mp4
959 /// ```
960 ///
961 /// # Example
962 /// ```rust,ignore
963 /// // Disable automatic rotation to preserve original video orientation
964 /// let input = Input::from("smartphone_video.mp4")
965 /// .set_autorotate(false);
966 /// ```
967 pub fn set_autorotate(mut self, autorotate: bool) -> Self {
968 self.autorotate = Some(autorotate);
969 self
970 }
971
972 /// Sets a timestamp scale factor for pts/dts values.
973 ///
974 /// This multiplier is applied to packet timestamps after ts_offset addition.
975 /// Default is `1.0` (no scaling). Values must be positive.
976 ///
977 /// This is useful for fixing videos with incorrect timestamps or for
978 /// special timestamp manipulation scenarios.
979 ///
980 /// # Parameters
981 /// - `scale`: A positive floating-point value for timestamp scaling.
982 ///
983 /// # Returns
984 /// * `Self` - allowing method chaining.
985 ///
986 /// # FFmpeg CLI equivalent
987 /// ```bash
988 /// ffmpeg -itsscale 2.0 -i input.mp4 output.mp4
989 /// ```
990 ///
991 /// # Example
992 /// ```rust,ignore
993 /// // Scale timestamps by 2x (double the playback speed effect on timestamps)
994 /// let input = Input::from("video.mp4")
995 /// .set_ts_scale(2.0);
996 /// ```
997 ///
998 /// # Panics
999 /// Panics if `scale` is not a positive finite number.
1000 pub fn set_ts_scale(mut self, scale: f64) -> Self {
1001 assert!(scale.is_finite(), "ts_scale must be finite, got {scale}");
1002 assert!(scale > 0.0, "ts_scale must be positive, got {scale}");
1003 self.ts_scale = Some(scale);
1004 self
1005 }
1006
1007 /// Sets a forced framerate for the input video stream.
1008 ///
1009 /// When set, this overrides the default DTS estimation behavior. By default,
1010 /// ez-ffmpeg uses the actual packet duration for DTS estimation (matching FFmpeg
1011 /// CLI behavior without `-r`). Setting a framerate forces DTS estimation to use
1012 /// the specified rate instead, which snaps timestamps to a fixed frame grid.
1013 ///
1014 /// # Parameters
1015 /// - `num`: Framerate numerator (e.g., 30 for 30fps, 24000 for 23.976fps)
1016 /// - `den`: Framerate denominator (e.g., 1 for 30fps, 1001 for 23.976fps)
1017 ///
1018 /// # Returns
1019 /// * `Self` - allowing method chaining.
1020 ///
1021 /// # FFmpeg CLI equivalent
1022 /// ```bash
1023 /// ffmpeg -r 30 -i input.mp4 output.mp4
1024 /// ffmpeg -r 24000/1001 -i input.mp4 output.mp4
1025 /// ```
1026 ///
1027 /// # Example
1028 /// ```rust,ignore
1029 /// // Force 30fps framerate for DTS estimation
1030 /// let input = Input::from("video.mp4")
1031 /// .set_framerate(30, 1);
1032 ///
1033 /// // Force 23.976fps framerate
1034 /// let input = Input::from("video.mp4")
1035 /// .set_framerate(24000, 1001);
1036 /// ```
1037 ///
1038 /// # Panics
1039 /// Panics if `num` or `den` is not positive.
1040 pub fn set_framerate(mut self, num: i32, den: i32) -> Self {
1041 assert!(num > 0, "framerate numerator must be positive, got {num}");
1042 assert!(den > 0, "framerate denominator must be positive, got {den}");
1043 self.framerate = Some((num, den));
1044 self
1045 }
1046}
1047
1048impl From<Box<dyn FnMut(&mut [u8]) -> i32 + Send>> for Input {
1049 fn from(read_callback: Box<dyn FnMut(&mut [u8]) -> i32 + Send>) -> Self {
1050 Self {
1051 url: None,
1052 read_callback: Some(read_callback),
1053 seek_callback: None,
1054 frame_pipelines: None,
1055 format: None,
1056 video_codec: None,
1057 audio_codec: None,
1058 subtitle_codec: None,
1059 exit_on_error: None,
1060 readrate: None,
1061 start_time_us: None,
1062 recording_time_us: None,
1063 stop_time_us: None,
1064 stream_loop: None,
1065 hwaccel: None,
1066 hwaccel_device: None,
1067 hwaccel_output_format: None,
1068 log_level_offset: None,
1069 input_opts: None,
1070 autorotate: None,
1071 ts_scale: None,
1072 framerate: None,
1073 }
1074 }
1075}
1076
1077impl From<String> for Input {
1078 fn from(url: String) -> Self {
1079 Self {
1080 url: Some(url),
1081 read_callback: None,
1082 seek_callback: None,
1083 frame_pipelines: None,
1084 format: None,
1085 video_codec: None,
1086 audio_codec: None,
1087 subtitle_codec: None,
1088 exit_on_error: None,
1089 readrate: None,
1090 start_time_us: None,
1091 recording_time_us: None,
1092 stop_time_us: None,
1093 stream_loop: None,
1094 hwaccel: None,
1095 hwaccel_device: None,
1096 hwaccel_output_format: None,
1097 log_level_offset: None,
1098 input_opts: None,
1099 autorotate: None,
1100 ts_scale: None,
1101 framerate: None,
1102 }
1103 }
1104}
1105
1106impl From<&str> for Input {
1107 fn from(url: &str) -> Self {
1108 Self::from(String::from(url))
1109 }
1110}
1111
1112
1113#[cfg(test)]
1114mod tests {
1115 use crate::core::context::input::Input;
1116
1117 #[test]
1118 fn set_framerate_valid() {
1119 let input = Input::from("test.mp4").set_framerate(24000, 1001);
1120 assert_eq!(input.framerate, Some((24000, 1001)));
1121 }
1122
1123 #[test]
1124 fn set_framerate_simple() {
1125 let input = Input::from("test.mp4").set_framerate(30, 1);
1126 assert_eq!(input.framerate, Some((30, 1)));
1127 }
1128
1129 #[test]
1130 #[should_panic(expected = "framerate numerator must be positive")]
1131 fn set_framerate_zero_num() {
1132 Input::from("test.mp4").set_framerate(0, 1);
1133 }
1134
1135 #[test]
1136 #[should_panic(expected = "framerate denominator must be positive")]
1137 fn set_framerate_zero_den() {
1138 Input::from("test.mp4").set_framerate(24, 0);
1139 }
1140
1141 #[test]
1142 #[should_panic(expected = "framerate numerator must be positive")]
1143 fn set_framerate_negative_num() {
1144 Input::from("test.mp4").set_framerate(-1, 1);
1145 }
1146
1147 #[test]
1148 #[should_panic(expected = "framerate denominator must be positive")]
1149 fn set_framerate_negative_den() {
1150 Input::from("test.mp4").set_framerate(24, -1);
1151 }
1152
1153 #[test]
1154 fn set_ts_scale_valid() {
1155 let input = Input::from("test.mp4").set_ts_scale(2.0);
1156 assert_eq!(input.ts_scale, Some(2.0));
1157 }
1158
1159 #[test]
1160 fn set_ts_scale_fractional() {
1161 let input = Input::from("test.mp4").set_ts_scale(0.5);
1162 assert_eq!(input.ts_scale, Some(0.5));
1163 }
1164
1165 #[test]
1166 #[should_panic(expected = "ts_scale must be finite")]
1167 fn set_ts_scale_nan() {
1168 Input::from("test.mp4").set_ts_scale(f64::NAN);
1169 }
1170
1171 #[test]
1172 #[should_panic(expected = "ts_scale must be finite")]
1173 fn set_ts_scale_infinity() {
1174 Input::from("test.mp4").set_ts_scale(f64::INFINITY);
1175 }
1176
1177 #[test]
1178 #[should_panic(expected = "ts_scale must be finite")]
1179 fn set_ts_scale_neg_infinity() {
1180 Input::from("test.mp4").set_ts_scale(f64::NEG_INFINITY);
1181 }
1182
1183 #[test]
1184 #[should_panic(expected = "ts_scale must be positive")]
1185 fn set_ts_scale_zero() {
1186 Input::from("test.mp4").set_ts_scale(0.0);
1187 }
1188
1189 #[test]
1190 #[should_panic(expected = "ts_scale must be positive")]
1191 fn set_ts_scale_negative() {
1192 Input::from("test.mp4").set_ts_scale(-1.0);
1193 }
1194
1195 #[test]
1196 fn test_new_by_read_callback() {
1197 let data_source = b"example custom data source".to_vec();
1198 let _input = Input::new_by_read_callback(move |buf| {
1199 let len = data_source.len().min(buf.len());
1200 buf[..len].copy_from_slice(&data_source[..len]);
1201 len as i32 // Return the number of bytes written
1202 });
1203
1204 let data_source2 = b"example custom data source2".to_vec();
1205 let _input = Input::new_by_read_callback(move |buf2| {
1206 let len = data_source2.len().min(buf2.len());
1207 buf2[..len].copy_from_slice(&data_source2[..len]);
1208 len as i32 // Return the number of bytes written
1209 });
1210 }
1211}