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