1use std::marker::PhantomData;
2
3use crate::consts::{Const, Mut};
4use crate::dict::Dictionary;
5use crate::ffi::*;
6use crate::rational::Rational;
7use crate::utils::check_i64;
8use crate::{AVDiscard, AVMediaType};
9
10pub struct Streams<'a> {
12 input: *mut AVFormatContext,
13 _marker: PhantomData<&'a mut AVFormatContext>,
14}
15
16impl std::fmt::Debug for Streams<'_> {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 let mut streams = Vec::new();
19 for stream in self.iter() {
20 streams.push(stream);
21 }
22
23 f.debug_struct("Streams")
24 .field("input", &self.input)
25 .field("streams", &streams)
26 .finish()
27 }
28}
29
30unsafe impl Send for Streams<'_> {}
32
33impl<'a> Streams<'a> {
34 pub const unsafe fn new(input: *mut AVFormatContext) -> Self {
40 Self {
41 input,
42 _marker: PhantomData,
43 }
44 }
45
46 pub fn best_index(&self, media_type: AVMediaType) -> Option<usize> {
48 let stream = unsafe { av_find_best_stream(self.input, media_type.into(), -1, -1, std::ptr::null_mut(), 0) };
52 if stream < 0 {
53 return None;
54 }
55
56 Some(stream as usize)
57 }
58
59 pub fn best(&'a self, media_type: AVMediaType) -> Option<Const<'a, Stream<'a>>> {
61 let stream = self.best_index(media_type)?;
62
63 let stream = unsafe { self.get_unchecked(stream)? };
66
67 Some(Const::new(stream))
68 }
69
70 pub fn best_mut(&'a mut self, media_type: AVMediaType) -> Option<Stream<'a>> {
72 self.best(media_type).map(|s| s.0)
73 }
74
75 pub const fn iter(&'a self) -> StreamIter<'a> {
77 StreamIter {
78 input: Self {
79 input: self.input,
80 _marker: PhantomData,
81 },
82 index: 0,
83 }
84 }
85
86 pub const fn len(&self) -> usize {
88 let input = unsafe { &*self.input };
91 input.nb_streams as usize
92 }
93
94 pub const fn is_empty(&self) -> bool {
96 self.len() == 0
97 }
98
99 pub const fn get(&'a mut self, index: usize) -> Option<Stream<'a>> {
101 unsafe { self.get_unchecked(index) }
104 }
105
106 pub const unsafe fn get_unchecked(&self, index: usize) -> Option<Stream<'a>> {
112 if index >= self.len() {
113 return None;
114 }
115
116 let input = unsafe { &*self.input };
119 let stream = unsafe { input.streams.add(index) };
121 let stream = unsafe { *stream };
123 let stream = unsafe { &mut *stream };
125
126 Some(Stream::new(stream, self.input))
127 }
128}
129
130impl<'a> IntoIterator for Streams<'a> {
131 type IntoIter = StreamIter<'a>;
132 type Item = Const<'a, Stream<'a>>;
133
134 fn into_iter(self) -> Self::IntoIter {
135 StreamIter { input: self, index: 0 }
136 }
137}
138
139pub struct StreamIter<'a> {
141 input: Streams<'a>,
142 index: usize,
143}
144
145impl<'a> Iterator for StreamIter<'a> {
146 type Item = Const<'a, Stream<'a>>;
147
148 fn next(&mut self) -> Option<Self::Item> {
149 let stream = unsafe { self.input.get_unchecked(self.index)? };
152 self.index += 1;
153 Some(Const::new(stream))
154 }
155
156 fn size_hint(&self) -> (usize, Option<usize>) {
157 let remaining = self.input.len() - self.index;
158 (remaining, Some(remaining))
159 }
160}
161
162impl std::iter::ExactSizeIterator for StreamIter<'_> {}
163
164pub struct Stream<'a>(&'a mut AVStream, *mut AVFormatContext);
166
167impl<'a> Stream<'a> {
168 pub(crate) const fn new(stream: &'a mut AVStream, input: *mut AVFormatContext) -> Self {
170 Self(stream, input)
171 }
172
173 pub const fn as_ptr(&self) -> *const AVStream {
175 self.0
176 }
177
178 pub const fn as_mut_ptr(&mut self) -> *mut AVStream {
180 self.0
181 }
182}
183
184impl<'a> Stream<'a> {
185 pub const fn index(&self) -> i32 {
187 self.0.index
188 }
189
190 pub const fn id(&self) -> i32 {
192 self.0.id
193 }
194
195 pub const fn codec_parameters(&self) -> Option<&'a AVCodecParameters> {
197 unsafe { self.0.codecpar.as_ref() }
199 }
200
201 pub fn time_base(&self) -> Rational {
203 self.0.time_base.into()
204 }
205
206 pub fn set_time_base(&mut self, time_base: impl Into<Rational>) {
208 self.0.time_base = time_base.into().into();
209 }
210
211 pub const fn start_time(&self) -> Option<i64> {
213 check_i64(self.0.start_time)
214 }
215
216 pub const fn set_start_time(&mut self, start_time: Option<i64>) {
218 self.0.start_time = match start_time {
219 Some(start_time) => start_time,
220 None => AV_NOPTS_VALUE,
221 }
222 }
223
224 pub const fn duration(&self) -> Option<i64> {
226 check_i64(self.0.duration)
227 }
228
229 pub const fn set_duration(&mut self, duration: Option<i64>) {
231 self.0.duration = match duration {
232 Some(duration) => duration,
233 None => AV_NOPTS_VALUE,
234 }
235 }
236
237 pub const fn nb_frames(&self) -> Option<i64> {
239 check_i64(self.0.nb_frames)
240 }
241
242 pub const fn set_nb_frames(&mut self, nb_frames: i64) {
244 self.0.nb_frames = nb_frames;
245 }
246
247 pub const fn disposition(&self) -> i32 {
249 self.0.disposition
250 }
251
252 pub const fn set_disposition(&mut self, disposition: i32) {
254 self.0.disposition = disposition;
255 }
256
257 pub const fn discard(&self) -> AVDiscard {
259 AVDiscard(self.0.discard)
260 }
261
262 pub fn set_discard(&mut self, discard: AVDiscard) {
264 self.0.discard = discard.into();
265 }
266
267 pub fn sample_aspect_ratio(&self) -> Rational {
269 self.0.sample_aspect_ratio.into()
270 }
271
272 pub fn set_sample_aspect_ratio(&mut self, sample_aspect_ratio: impl Into<Rational>) {
274 self.0.sample_aspect_ratio = sample_aspect_ratio.into().into();
275 }
276
277 pub const fn metadata(&self) -> Const<'_, Dictionary> {
279 Const::new(unsafe { Dictionary::from_ptr_ref(self.0.metadata) })
282 }
283
284 pub const fn metadata_mut(&mut self) -> Mut<'_, Dictionary> {
286 Mut::new(unsafe { Dictionary::from_ptr_ref(self.0.metadata) })
289 }
290
291 pub fn avg_frame_rate(&self) -> Rational {
293 self.0.avg_frame_rate.into()
294 }
295
296 pub fn r_frame_rate(&self) -> Rational {
298 self.0.r_frame_rate.into()
299 }
300
301 pub const unsafe fn format_context(&self) -> *mut AVFormatContext {
307 self.1
308 }
309}
310
311impl std::fmt::Debug for Stream<'_> {
312 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
313 f.debug_struct("Stream")
314 .field("index", &self.index())
315 .field("id", &self.id())
316 .field("time_base", &self.time_base())
317 .field("start_time", &self.start_time())
318 .field("duration", &self.duration())
319 .field("nb_frames", &self.nb_frames())
320 .field("disposition", &self.disposition())
321 .field("discard", &self.discard())
322 .field("sample_aspect_ratio", &self.sample_aspect_ratio())
323 .field("metadata", &self.metadata())
324 .field("avg_frame_rate", &self.avg_frame_rate())
325 .field("r_frame_rate", &self.r_frame_rate())
326 .finish()
327 }
328}
329
330#[cfg(test)]
331#[cfg_attr(all(test, coverage_nightly), coverage(off))]
332mod tests {
333 use std::collections::BTreeMap;
334 use std::num::NonZero;
335
336 use insta::{Settings, assert_debug_snapshot};
337
338 use crate::ffi::AVStream;
339 use crate::io::Input;
340 use crate::rational::Rational;
341 use crate::stream::AVMediaType;
342 use crate::{AVDiscard, file_path};
343
344 #[test]
345 fn test_best_stream() {
346 let input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
347 let streams = input.streams();
348
349 let media_type = AVMediaType::Video;
350 let best_stream = streams.best(media_type);
351
352 assert!(best_stream.is_some(), "Expected best stream to be found");
353 let best_stream = best_stream.unwrap();
354 assert!(best_stream.index() >= 0, "Expected a valid stream index");
355 }
356
357 #[test]
358 fn test_best_none_stream() {
359 let input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
360 let streams = input.streams();
361 let invalid_media_type = AVMediaType::Subtitle;
362 let best_stream = streams.best(invalid_media_type);
363
364 assert!(
365 best_stream.is_none(),
366 "Expected `best` to return None for unsupported media type"
367 );
368 }
369
370 #[test]
371 fn test_best_mut_stream() {
372 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
373 let mut streams = input.streams_mut();
374
375 let media_type = AVMediaType::Video;
376 let best_mut_stream = streams.best_mut(media_type);
377
378 assert!(best_mut_stream.is_some(), "Expected best mutable stream to be found");
379 let best_mut_stream = best_mut_stream.unwrap();
380 assert!(best_mut_stream.index() >= 0, "Expected a valid stream index");
381 }
382
383 #[test]
384 fn test_streams_into_iter() {
385 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
386 let streams = input.streams_mut();
387 let streams_len = streams.len();
388 let iter = streams.into_iter();
389 let collected_streams: Vec<_> = iter.collect();
390
391 assert_eq!(
392 collected_streams.len(),
393 streams_len,
394 "Expected the iterator to yield the same number of streams as `streams.len()`"
395 );
396
397 for stream in collected_streams {
398 assert!(stream.index() >= 0, "Expected a valid stream index");
399 }
400 }
401
402 #[test]
403 fn test_streams_iter() {
404 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
405 let streams = input.streams_mut();
406 let iter = streams.iter();
407 let collected_streams: Vec<_> = iter.collect();
408
409 assert_eq!(
410 collected_streams.len(),
411 streams.len(),
412 "Expected iterator to yield the same number of streams as `streams.len()`"
413 );
414
415 for stream in collected_streams {
416 assert!(stream.index() >= 0, "Expected a valid stream index");
417 }
418 }
419
420 #[test]
421 fn test_streams_get_valid_index() {
422 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
423 let mut streams = input.streams_mut();
424 let stream_index = 0;
425 let stream = streams.get(stream_index);
426
427 assert!(stream.is_some(), "Expected `get` to return Some for a valid index");
428 let stream = stream.unwrap();
429
430 assert_eq!(stream.index(), stream_index as i32, "Stream index should match");
431 assert!(stream.id() >= 0, "Stream ID should be valid");
432 }
433
434 #[test]
435 fn test_streams_get_invalid_index() {
436 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
437 let mut streams = input.streams_mut();
438 let invalid_index = streams.len();
439 let stream = streams.get(invalid_index);
440
441 assert!(stream.is_none(), "Expected `get` to return None for an invalid index");
442 }
443
444 #[test]
445 fn test_stream_as_mut_ptr() {
446 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
447 let mut streams = input.streams_mut();
448 let stream_index = 0;
449 let mut stream = streams.get(stream_index).expect("Expected a valid stream");
450 let stream_mut_ptr = stream.as_mut_ptr();
451
452 assert!(!stream_mut_ptr.is_null(), "Expected a non-null mutable pointer");
453 assert_eq!(
454 stream_mut_ptr,
455 stream.as_ptr() as *mut AVStream,
456 "Mutable pointer should match the constant pointer cast to mutable"
457 );
458 }
459
460 #[test]
461 fn test_stream_nb_frames() {
462 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
463 let mut streams = input.streams_mut();
464 let mut stream = streams.get(0).expect("Expected a valid stream");
465
466 let test_nb_frames = 100;
467 stream.set_nb_frames(test_nb_frames);
468 assert_eq!(
469 stream.nb_frames(),
470 Some(test_nb_frames),
471 "Expected `nb_frames` to match the set value"
472 );
473 }
474
475 #[test]
476 fn test_stream_disposition() {
477 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
478 let mut streams = input.streams_mut();
479 let mut stream = streams.get(0).expect("Expected a valid stream");
480
481 let test_disposition = 0x01;
482 stream.set_disposition(test_disposition);
483 assert_eq!(
484 stream.disposition(),
485 test_disposition,
486 "Expected `disposition` to match the set value"
487 );
488 }
489
490 #[test]
491 fn test_stream_discard() {
492 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
493 let mut streams = input.streams_mut();
494 let mut stream = streams.get(0).expect("Expected a valid stream");
495
496 let test_discard = AVDiscard::All;
497 stream.set_discard(test_discard);
498 assert_eq!(stream.discard(), test_discard, "Expected `discard` to match the set value");
499 }
500
501 #[test]
502 fn test_stream_sample_aspect_ratio() {
503 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
504 let mut streams = input.streams_mut();
505 let mut stream = streams.get(0).expect("Expected a valid stream");
506
507 let test_aspect_ratio = Rational::new(4, NonZero::new(3).unwrap());
508 stream.set_sample_aspect_ratio(test_aspect_ratio);
509 assert_eq!(
510 stream.sample_aspect_ratio(),
511 test_aspect_ratio,
512 "Expected `sample_aspect_ratio` to match the set value"
513 );
514 }
515
516 #[test]
517 fn test_stream_metadata_insta() {
518 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
519 let mut streams = input.streams_mut();
520 let mut stream = streams.get(0).expect("Expected a valid stream");
521 let mut metadata = stream.metadata_mut();
522 metadata.set(c"test_key", c"test_value").expect("Failed to set test_key");
523 metadata
524 .set(c"test_key_2", c"test_value_2")
525 .expect("Failed to set test_key_2");
526 let metadata = stream.metadata();
527
528 let sorted_metadata: BTreeMap<_, _> = metadata
530 .iter()
531 .filter_map(|(key, value)| {
532 Some((key.to_str().ok()?.to_string(), value.to_str().ok()?.to_string()))
534 })
535 .collect();
536
537 assert_debug_snapshot!(sorted_metadata, @r###"
538 {
539 "encoder": "Lavc60.9.100 libx264",
540 "handler_name": "GPAC ISO Video Handler",
541 "language": "und",
542 "test_key": "test_value",
543 "test_key_2": "test_value_2",
544 "vendor_id": "[0][0][0][0]",
545 }
546 "###);
547 }
548
549 #[test]
550 fn test_stream_frame_rates() {
551 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
552 let mut streams = input.streams_mut();
553 let stream = streams.get(0).expect("Expected a valid stream");
554 let avg_frame_rate = stream.avg_frame_rate();
555 let real_frame_rate = stream.r_frame_rate();
556
557 assert!(avg_frame_rate.as_f64() > 0.0, "Expected non-zero avg_frame_rate numerator");
558 assert!(real_frame_rate.as_f64() > 0.0, "Expected non-zero r_frame_rate numerator");
559 }
560
561 #[test]
562 fn test_stream_format_context() {
563 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
564 let mut streams = input.streams_mut();
565 let stream = streams.get(0).expect("Expected a valid stream");
566
567 let format_context = unsafe { stream.format_context() };
569
570 assert_eq!(
571 format_context as *const _,
572 input.as_ptr(),
573 "Expected `format_context` to match the input's context"
574 );
575 }
576
577 #[test]
578 fn test_stream_debug() {
579 let mut input = Input::open(file_path("avc_aac_large.mp4")).expect("Failed to open valid file");
580 let mut streams = input.streams_mut();
581 let stream = streams.get(0).expect("Expected a valid stream");
582
583 let metadata = stream.metadata();
584 let sorted_metadata: BTreeMap<_, _> = metadata
586 .iter()
587 .filter_map(|(key, value)| {
588 Some((key.to_str().ok()?.to_string(), value.to_str().ok()?.to_string()))
590 })
591 .collect();
592
593 let serialized_metadata = sorted_metadata
594 .iter()
595 .map(|(key, value)| format!(" \"{key}\": \"{value}\","))
596 .collect::<Vec<_>>()
597 .join("\n");
598
599 let replacement_metadata = format!("metadata: {{\n{serialized_metadata}\n }}");
600 let mut settings = Settings::new();
601 let metadata_regex = r"metadata: \{[^}]*\}";
602 settings.add_filter(metadata_regex, &replacement_metadata);
603
604 settings.bind(|| {
605 assert_debug_snapshot!(stream, @r#"
606 Stream {
607 index: 0,
608 id: 1,
609 time_base: Rational {
610 numerator: 1,
611 denominator: 15360,
612 },
613 start_time: Some(
614 0,
615 ),
616 duration: Some(
617 16384,
618 ),
619 nb_frames: Some(
620 64,
621 ),
622 disposition: 1,
623 discard: AVDiscard::Default,
624 sample_aspect_ratio: Rational {
625 numerator: 1,
626 denominator: 1,
627 },
628 metadata: {
629 "encoder": "Lavc60.9.100 libx264",
630 "handler_name": "GPAC ISO Video Handler",
631 "language": "und",
632 "vendor_id": "[0][0][0][0]",
633 },
634 avg_frame_rate: Rational {
635 numerator: 60,
636 denominator: 1,
637 },
638 r_frame_rate: Rational {
639 numerator: 60,
640 denominator: 1,
641 },
642 }
643 "#);
644 });
645 }
646}