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::AVDiscard;
339 use crate::ffi::AVStream;
340 use crate::io::Input;
341 use crate::rational::Rational;
342 use crate::stream::AVMediaType;
343
344 #[test]
345 fn test_best_stream() {
346 let valid_file_path = "../../assets/avc_aac_large.mp4";
347 let input = Input::open(valid_file_path).expect("Failed to open valid file");
348 let streams = input.streams();
349
350 let media_type = AVMediaType::Video;
351 let best_stream = streams.best(media_type);
352
353 assert!(best_stream.is_some(), "Expected best stream to be found");
354 let best_stream = best_stream.unwrap();
355 assert!(best_stream.index() >= 0, "Expected a valid stream index");
356 }
357
358 #[test]
359 fn test_best_none_stream() {
360 let valid_file_path = "../../assets/avc_aac_large.mp4";
361 let input = Input::open(valid_file_path).expect("Failed to open valid file");
362 let streams = input.streams();
363 let invalid_media_type = AVMediaType::Subtitle;
364 let best_stream = streams.best(invalid_media_type);
365
366 assert!(
367 best_stream.is_none(),
368 "Expected `best` to return None for unsupported media type"
369 );
370 }
371
372 #[test]
373 fn test_best_mut_stream() {
374 let valid_file_path = "../../assets/avc_aac_large.mp4";
375 let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
376 let mut streams = input.streams_mut();
377
378 let media_type = AVMediaType::Video;
379 let best_mut_stream = streams.best_mut(media_type);
380
381 assert!(best_mut_stream.is_some(), "Expected best mutable stream to be found");
382 let best_mut_stream = best_mut_stream.unwrap();
383 assert!(best_mut_stream.index() >= 0, "Expected a valid stream index");
384 }
385
386 #[test]
387 fn test_streams_into_iter() {
388 let valid_file_path = "../../assets/avc_aac_large.mp4";
389 let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
390 let streams = input.streams_mut();
391 let streams_len = streams.len();
392 let iter = streams.into_iter();
393 let collected_streams: Vec<_> = iter.collect();
394
395 assert_eq!(
396 collected_streams.len(),
397 streams_len,
398 "Expected the iterator to yield the same number of streams as `streams.len()`"
399 );
400
401 for stream in collected_streams {
402 assert!(stream.index() >= 0, "Expected a valid stream index");
403 }
404 }
405
406 #[test]
407 fn test_streams_iter() {
408 let valid_file_path = "../../assets/avc_aac_large.mp4";
409 let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
410 let streams = input.streams_mut();
411 let iter = streams.iter();
412 let collected_streams: Vec<_> = iter.collect();
413
414 assert_eq!(
415 collected_streams.len(),
416 streams.len(),
417 "Expected iterator to yield the same number of streams as `streams.len()`"
418 );
419
420 for stream in collected_streams {
421 assert!(stream.index() >= 0, "Expected a valid stream index");
422 }
423 }
424
425 #[test]
426 fn test_streams_get_valid_index() {
427 let valid_file_path = "../../assets/avc_aac_large.mp4";
428 let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
429 let mut streams = input.streams_mut();
430 let stream_index = 0;
431 let stream = streams.get(stream_index);
432
433 assert!(stream.is_some(), "Expected `get` to return Some for a valid index");
434 let stream = stream.unwrap();
435
436 assert_eq!(stream.index(), stream_index as i32, "Stream index should match");
437 assert!(stream.id() >= 0, "Stream ID should be valid");
438 }
439
440 #[test]
441 fn test_streams_get_invalid_index() {
442 let valid_file_path = "../../assets/avc_aac_large.mp4";
443 let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
444 let mut streams = input.streams_mut();
445 let invalid_index = streams.len();
446 let stream = streams.get(invalid_index);
447
448 assert!(stream.is_none(), "Expected `get` to return None for an invalid index");
449 }
450
451 #[test]
452 fn test_stream_as_mut_ptr() {
453 let valid_file_path = "../../assets/avc_aac_large.mp4";
454 let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
455 let mut streams = input.streams_mut();
456 let stream_index = 0;
457 let mut stream = streams.get(stream_index).expect("Expected a valid stream");
458 let stream_mut_ptr = stream.as_mut_ptr();
459
460 assert!(!stream_mut_ptr.is_null(), "Expected a non-null mutable pointer");
461 assert_eq!(
462 stream_mut_ptr,
463 stream.as_ptr() as *mut AVStream,
464 "Mutable pointer should match the constant pointer cast to mutable"
465 );
466 }
467
468 #[test]
469 fn test_stream_nb_frames() {
470 let valid_file_path = "../../assets/avc_aac_large.mp4";
471 let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
472 let mut streams = input.streams_mut();
473 let mut stream = streams.get(0).expect("Expected a valid stream");
474
475 let test_nb_frames = 100;
476 stream.set_nb_frames(test_nb_frames);
477 assert_eq!(
478 stream.nb_frames(),
479 Some(test_nb_frames),
480 "Expected `nb_frames` to match the set value"
481 );
482 }
483
484 #[test]
485 fn test_stream_disposition() {
486 let valid_file_path = "../../assets/avc_aac_large.mp4";
487 let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
488 let mut streams = input.streams_mut();
489 let mut stream = streams.get(0).expect("Expected a valid stream");
490
491 let test_disposition = 0x01;
492 stream.set_disposition(test_disposition);
493 assert_eq!(
494 stream.disposition(),
495 test_disposition,
496 "Expected `disposition` to match the set value"
497 );
498 }
499
500 #[test]
501 fn test_stream_discard() {
502 let valid_file_path = "../../assets/avc_aac_large.mp4";
503 let mut input = Input::open(valid_file_path).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_discard = AVDiscard::All;
508 stream.set_discard(test_discard);
509 assert_eq!(stream.discard(), test_discard, "Expected `discard` to match the set value");
510 }
511
512 #[test]
513 fn test_stream_sample_aspect_ratio() {
514 let valid_file_path = "../../assets/avc_aac_large.mp4";
515 let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
516 let mut streams = input.streams_mut();
517 let mut stream = streams.get(0).expect("Expected a valid stream");
518
519 let test_aspect_ratio = Rational::new(4, NonZero::new(3).unwrap());
520 stream.set_sample_aspect_ratio(test_aspect_ratio);
521 assert_eq!(
522 stream.sample_aspect_ratio(),
523 test_aspect_ratio,
524 "Expected `sample_aspect_ratio` to match the set value"
525 );
526 }
527
528 #[test]
529 fn test_stream_metadata_insta() {
530 let valid_file_path = "../../assets/avc_aac_large.mp4";
531 let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
532 let mut streams = input.streams_mut();
533 let mut stream = streams.get(0).expect("Expected a valid stream");
534 let mut metadata = stream.metadata_mut();
535 metadata.set(c"test_key", c"test_value").expect("Failed to set test_key");
536 metadata
537 .set(c"test_key_2", c"test_value_2")
538 .expect("Failed to set test_key_2");
539 let metadata = stream.metadata();
540
541 let sorted_metadata: BTreeMap<_, _> = metadata
543 .iter()
544 .filter_map(|(key, value)| {
545 Some((key.to_str().ok()?.to_string(), value.to_str().ok()?.to_string()))
547 })
548 .collect();
549
550 assert_debug_snapshot!(sorted_metadata, @r###"
551 {
552 "encoder": "Lavc60.9.100 libx264",
553 "handler_name": "GPAC ISO Video Handler",
554 "language": "und",
555 "test_key": "test_value",
556 "test_key_2": "test_value_2",
557 "vendor_id": "[0][0][0][0]",
558 }
559 "###);
560 }
561
562 #[test]
563 fn test_stream_frame_rates() {
564 let valid_file_path = "../../assets/avc_aac_large.mp4";
565 let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
566 let mut streams = input.streams_mut();
567 let stream = streams.get(0).expect("Expected a valid stream");
568 let avg_frame_rate = stream.avg_frame_rate();
569 let real_frame_rate = stream.r_frame_rate();
570
571 assert!(avg_frame_rate.as_f64() > 0.0, "Expected non-zero avg_frame_rate numerator");
572 assert!(real_frame_rate.as_f64() > 0.0, "Expected non-zero r_frame_rate numerator");
573 }
574
575 #[test]
576 fn test_stream_format_context() {
577 let valid_file_path = "../../assets/avc_aac_large.mp4";
578 let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
579 let mut streams = input.streams_mut();
580 let stream = streams.get(0).expect("Expected a valid stream");
581
582 let format_context = unsafe { stream.format_context() };
584
585 assert_eq!(
586 format_context as *const _,
587 input.as_ptr(),
588 "Expected `format_context` to match the input's context"
589 );
590 }
591
592 #[test]
593 fn test_stream_debug() {
594 let valid_file_path = "../../assets/avc_aac_large.mp4";
595 let mut input = Input::open(valid_file_path).expect("Failed to open valid file");
596 let mut streams = input.streams_mut();
597 let stream = streams.get(0).expect("Expected a valid stream");
598
599 let metadata = stream.metadata();
600 let sorted_metadata: BTreeMap<_, _> = metadata
602 .iter()
603 .filter_map(|(key, value)| {
604 Some((key.to_str().ok()?.to_string(), value.to_str().ok()?.to_string()))
606 })
607 .collect();
608
609 let serialized_metadata = sorted_metadata
610 .iter()
611 .map(|(key, value)| format!(" \"{key}\": \"{value}\","))
612 .collect::<Vec<_>>()
613 .join("\n");
614
615 let replacement_metadata = format!("metadata: {{\n{serialized_metadata}\n }}");
616 let mut settings = Settings::new();
617 let metadata_regex = r"metadata: \{[^}]*\}";
618 settings.add_filter(metadata_regex, &replacement_metadata);
619
620 settings.bind(|| {
621 assert_debug_snapshot!(stream, @r#"
622 Stream {
623 index: 0,
624 id: 1,
625 time_base: Rational {
626 numerator: 1,
627 denominator: 15360,
628 },
629 start_time: Some(
630 0,
631 ),
632 duration: Some(
633 16384,
634 ),
635 nb_frames: Some(
636 64,
637 ),
638 disposition: 1,
639 discard: AVDiscard::Default,
640 sample_aspect_ratio: Rational {
641 numerator: 1,
642 denominator: 1,
643 },
644 metadata: {
645 "encoder": "Lavc60.9.100 libx264",
646 "handler_name": "GPAC ISO Video Handler",
647 "language": "und",
648 "vendor_id": "[0][0][0][0]",
649 },
650 avg_frame_rate: Rational {
651 numerator: 60,
652 denominator: 1,
653 },
654 r_frame_rate: Rational {
655 numerator: 60,
656 denominator: 1,
657 },
658 }
659 "#);
660 });
661 }
662}