1use std::ffi::CStr;
2use std::path::Path;
3
4use super::internal::{Inner, InnerOptions, read_packet, seek};
5use crate::consts::{Const, DEFAULT_BUFFER_SIZE};
6use crate::dict::Dictionary;
7use crate::error::{FfmpegError, FfmpegErrorCode};
8use crate::ffi::*;
9use crate::packet::{Packet, Packets};
10use crate::smart_object::SmartObject;
11use crate::stream::Streams;
12
13pub struct Input<T: Send + Sync> {
15 inner: SmartObject<Inner<T>>,
16}
17
18unsafe impl<T: Send + Sync> Send for Input<T> {}
20
21#[derive(Debug, Clone)]
23pub struct InputOptions<I: FnMut() -> bool> {
24 pub buffer_size: usize,
26 pub dictionary: Dictionary,
28 pub interrupt_callback: Option<I>,
30}
31
32impl Default for InputOptions<fn() -> bool> {
34 fn default() -> Self {
35 Self {
36 buffer_size: DEFAULT_BUFFER_SIZE,
37 dictionary: Dictionary::new(),
38 interrupt_callback: None,
39 }
40 }
41}
42
43impl<T: std::io::Read + Send + Sync> Input<T> {
44 pub fn new(input: T) -> Result<Self, FfmpegError> {
46 Self::with_options(input, &mut InputOptions::default())
47 }
48
49 pub fn with_options(input: T, options: &mut InputOptions<impl FnMut() -> bool>) -> Result<Self, FfmpegError> {
51 Self::create_input(
52 Inner::new(
53 input,
54 InnerOptions {
55 buffer_size: options.buffer_size,
56 read_fn: Some(read_packet::<T>),
57 ..Default::default()
58 },
59 )?,
60 None,
61 &mut options.dictionary,
62 )
63 }
64
65 pub fn seekable(input: T) -> Result<Self, FfmpegError>
67 where
68 T: std::io::Seek,
69 {
70 Self::seekable_with_options(input, InputOptions::default())
71 }
72
73 pub fn seekable_with_options(input: T, mut options: InputOptions<impl FnMut() -> bool>) -> Result<Self, FfmpegError>
75 where
76 T: std::io::Seek,
77 {
78 Self::create_input(
79 Inner::new(
80 input,
81 InnerOptions {
82 buffer_size: options.buffer_size,
83 read_fn: Some(read_packet::<T>),
84 seek_fn: Some(seek::<T>),
85 ..Default::default()
86 },
87 )?,
88 None,
89 &mut options.dictionary,
90 )
91 }
92}
93
94impl<T: Send + Sync> Input<T> {
95 pub const fn as_ptr(&self) -> *const AVFormatContext {
97 self.inner.inner_ref().context.as_ptr()
98 }
99
100 pub const fn as_mut_ptr(&mut self) -> *mut AVFormatContext {
102 self.inner.inner_mut().context.as_mut_ptr()
103 }
104
105 pub const fn streams(&self) -> Const<'_, Streams<'_>> {
107 unsafe { Const::new(Streams::new(self.inner.inner_ref().context.as_ptr() as *mut _)) }
112 }
113
114 pub const fn streams_mut(&mut self) -> Streams<'_> {
116 unsafe { Streams::new(self.inner.inner_mut().context.as_mut_ptr()) }
118 }
119
120 pub const fn packets(&mut self) -> Packets<'_> {
122 unsafe { Packets::new(self.inner.inner_mut().context.as_mut_ptr()) }
124 }
125
126 pub fn receive_packet(&mut self) -> Result<Option<Packet>, FfmpegError> {
128 self.packets().receive()
129 }
130
131 fn create_input(mut inner: Inner<T>, path: Option<&CStr>, dictionary: &mut Dictionary) -> Result<Self, FfmpegError> {
132 FfmpegErrorCode(unsafe {
134 avformat_open_input(
135 inner.context.as_mut(),
136 path.map(|p| p.as_ptr()).unwrap_or(std::ptr::null()),
137 std::ptr::null(),
138 dictionary.as_mut_ptr_ref(),
139 )
140 })
141 .result()?;
142
143 if inner.context.as_ptr().is_null() {
144 return Err(FfmpegError::Alloc);
145 }
146
147 let mut inner = SmartObject::new(inner, |inner| {
148 unsafe { avformat_close_input(inner.context.as_mut()) };
150 });
151
152 inner.context.set_destructor(|_| {});
154
155 FfmpegErrorCode(unsafe { avformat_find_stream_info(inner.context.as_mut_ptr(), std::ptr::null_mut()) }).result()?;
157
158 Ok(Self { inner })
159 }
160}
161
162impl Input<()> {
163 pub fn open(path: impl AsRef<Path>) -> Result<Self, FfmpegError> {
165 let inner = unsafe { Inner::empty() };
168
169 let path = path.as_ref().to_string_lossy();
170
171 Self::create_input(
172 inner,
173 Some(&std::ffi::CString::new(path.as_bytes()).unwrap()),
174 &mut Dictionary::new(),
175 )
176 }
177}
178
179#[cfg(test)]
180#[cfg_attr(all(test, coverage_nightly), coverage(off))]
181mod tests {
182 use std::io::Cursor;
183
184 use insta::Settings;
185
186 use super::{DEFAULT_BUFFER_SIZE, FfmpegError, Input, InputOptions};
187 use crate::file_path;
188
189 fn configure_insta_filters(settings: &mut Settings) {
190 settings.add_filter(r"0x0000000000000000", "[NULL_POINTER]");
191 settings.add_filter(r"0x[0-9a-f]{16}", "[NON_NULL_POINTER]");
192 }
193
194 #[test]
195 fn test_input_options_default() {
196 let default_options = InputOptions::default();
197
198 assert_eq!(default_options.buffer_size, DEFAULT_BUFFER_SIZE);
199 assert!(default_options.dictionary.is_empty());
200 assert!(default_options.interrupt_callback.is_none());
201 }
202
203 #[test]
204 fn test_open_valid_file() {
205 let valid_file_path = file_path("avc_aac_large.mp4");
206 assert!(valid_file_path.exists(), "Test file does not exist");
207
208 let result = Input::open(valid_file_path);
209 assert!(result.is_ok(), "Expected success but got error");
210 }
211
212 #[test]
213 fn test_open_invalid_path() {
214 let invalid_path = "invalid_file.mp4";
215 let result = Input::open(invalid_path);
216 assert!(result.is_err(), "Expected an error for invalid path");
217 if let Err(err) = result {
218 match err {
219 FfmpegError::Code(_) => (),
220 _ => panic!("Unexpected error type: {err:?}"),
221 }
222 }
223 }
224
225 #[test]
226 fn test_new_with_default_options() {
227 let valid_media_data = std::fs::read(file_path("avc_aac_large.mp4")).unwrap();
228 let data = Cursor::new(valid_media_data);
229 let result = Input::new(data);
230
231 if let Err(e) = &result {
232 eprintln!("Error encountered: {e:?}");
233 }
234
235 assert!(result.is_ok(), "Expected success but got error");
236 }
237
238 #[test]
239 fn test_seekable_with_valid_input() {
240 let valid_media_data = std::fs::read(file_path("avc_aac_large.mp4")).unwrap();
241 let data = Cursor::new(valid_media_data);
242 let result = Input::seekable(data);
243
244 if let Err(e) = &result {
245 eprintln!("Error encountered: {e:?}");
246 }
247
248 assert!(result.is_ok(), "Expected success but got error");
249 }
250
251 #[test]
252 fn test_as_ptr() {
253 let input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
254
255 let ptr = input.as_ptr();
256 assert!(!ptr.is_null(), "Expected non-null pointer");
257 }
258
259 #[test]
260 fn test_as_mut_ptr() {
261 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
262
263 let ptr = input.as_mut_ptr();
264 assert!(!ptr.is_null(), "Expected non-null mutable pointer");
265 }
266
267 #[test]
268 fn test_streams() {
269 let input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
270 let streams = input.streams();
271
272 assert!(!streams.is_empty(), "Expected at least one stream");
273
274 let mut settings = Settings::new();
275 configure_insta_filters(&mut settings);
276
277 settings.bind(|| {
278 insta::assert_debug_snapshot!(streams, @r#"
279 Streams {
280 input: [NON_NULL_POINTER],
281 streams: [
282 Stream {
283 index: 0,
284 id: 1,
285 time_base: Rational {
286 numerator: 1,
287 denominator: 15360,
288 },
289 start_time: Some(
290 0,
291 ),
292 duration: Some(
293 16384,
294 ),
295 nb_frames: Some(
296 64,
297 ),
298 disposition: 1,
299 discard: AVDiscard::Default,
300 sample_aspect_ratio: Rational {
301 numerator: 1,
302 denominator: 1,
303 },
304 metadata: {
305 "language": "und",
306 "handler_name": "GPAC ISO Video Handler",
307 "vendor_id": "[0][0][0][0]",
308 "encoder": "Lavc60.9.100 libx264",
309 },
310 avg_frame_rate: Rational {
311 numerator: 60,
312 denominator: 1,
313 },
314 r_frame_rate: Rational {
315 numerator: 60,
316 denominator: 1,
317 },
318 },
319 Stream {
320 index: 1,
321 id: 2,
322 time_base: Rational {
323 numerator: 1,
324 denominator: 48000,
325 },
326 start_time: Some(
327 0,
328 ),
329 duration: Some(
330 48096,
331 ),
332 nb_frames: Some(
333 48,
334 ),
335 disposition: 1,
336 discard: AVDiscard::Default,
337 sample_aspect_ratio: Rational {
338 numerator: 0,
339 denominator: 1,
340 },
341 metadata: {
342 "language": "und",
343 "handler_name": "GPAC ISO Audio Handler",
344 "vendor_id": "[0][0][0][0]",
345 },
346 avg_frame_rate: Rational {
347 numerator: 0,
348 denominator: 1,
349 },
350 r_frame_rate: Rational {
351 numerator: 0,
352 denominator: 1,
353 },
354 },
355 ],
356 }
357 "#);
358 });
359 }
360
361 #[test]
362 fn test_packets() {
363 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
364 let mut packets = input.packets();
365
366 for _ in 0..5 {
367 match packets.next() {
368 Some(Ok(_)) => (),
369 Some(Err(e)) => panic!("Error encountered while reading packets: {e:?}"),
370 None => break,
371 }
372 }
373
374 let mut settings = insta::Settings::new();
375 configure_insta_filters(&mut settings);
376
377 settings.bind(|| {
378 insta::assert_debug_snapshot!(packets, @r"
379 Packets {
380 context: [NON_NULL_POINTER],
381 }
382 ");
383 });
384 }
385
386 #[test]
387 fn test_receive_packet() {
388 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
389
390 let mut packets = Vec::new();
391 while let Ok(Some(packet)) = input.receive_packet() {
392 assert!(!packet.data().is_empty(), "Expected a non-empty packet");
393 assert!(packet.stream_index() >= 0, "Expected a valid stream index");
394 packets.push(packet);
395 }
396
397 if packets.is_empty() {
398 panic!("Expected at least one packet but received none");
399 }
400
401 insta::assert_debug_snapshot!(packets);
402 }
403}