scuffle_ffmpeg/
stream.rs

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
10/// A collection of streams. Streams implements [`IntoIterator`] to iterate over the streams.
11pub 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
30/// Safety: `Streams` is safe to send between threads.
31unsafe impl Send for Streams<'_> {}
32
33impl<'a> Streams<'a> {
34    /// Creates a new `Streams` instance.
35    ///
36    /// # Safety
37    /// This function is unsafe because the caller must ensure that the lifetime & the mutablity
38    /// of the `AVFormatContext` matches the lifetime & mutability of the `Streams`.
39    pub const unsafe fn new(input: *mut AVFormatContext) -> Self {
40        Self {
41            input,
42            _marker: PhantomData,
43        }
44    }
45
46    /// Returns the index of the best stream of the given media type.
47    pub fn best_index(&self, media_type: AVMediaType) -> Option<usize> {
48        // Safety: av_find_best_stream is safe to call, 'input' is a valid pointer
49        // We upcast the pointer to a mutable pointer because the function signature
50        // requires it, but it does not mutate the pointer.
51        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    /// Returns the best stream of the given media type.
60    pub fn best(&'a self, media_type: AVMediaType) -> Option<Const<'a, Stream<'a>>> {
61        let stream = self.best_index(media_type)?;
62
63        // Safety: This function is safe because we return a Const<Stream> which restricts
64        // the mutability of the stream.
65        let stream = unsafe { self.get_unchecked(stream)? };
66
67        Some(Const::new(stream))
68    }
69
70    /// Returns the best mutable stream of the given media type.
71    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    /// Returns an iterator over the streams.
76    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    /// Returns the length of the streams.
87    pub const fn len(&self) -> usize {
88        // Safety: The lifetime makes sure we have a valid pointer for reading and nobody has
89        // access to the pointer for writing.
90        let input = unsafe { &*self.input };
91        input.nb_streams as usize
92    }
93
94    /// Returns whether the streams are empty.
95    pub const fn is_empty(&self) -> bool {
96        self.len() == 0
97    }
98
99    /// Returns the stream at the given index.
100    pub const fn get(&'a mut self, index: usize) -> Option<Stream<'a>> {
101        // Safety: this function requires mutability, therefore its safe to call the unchecked
102        // version.
103        unsafe { self.get_unchecked(index) }
104    }
105
106    /// Returns the stream at the given index.
107    ///
108    /// # Safety
109    /// This function is unsafe because it does not require mutability. The caller must
110    /// guarantee that the stream is not mutated and that multiple streams of the same index exist.
111    pub const unsafe fn get_unchecked(&self, index: usize) -> Option<Stream<'a>> {
112        if index >= self.len() {
113            return None;
114        }
115
116        // Safety: The lifetime makes sure we have a valid pointer for reading and nobody has
117        // access to the pointer for writing.
118        let input = unsafe { &*self.input };
119        // Safety: we make sure that there are enough streams to access the index.
120        let stream = unsafe { input.streams.add(index) };
121        // Safety: The pointer is valid.
122        let stream = unsafe { *stream };
123        // Safety: The pointer is valid.
124        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
139/// An iterator over the streams.
140pub 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        // Safety: we return a Const version of the stream, so there cannot exist multiple mutable
150        // streams of the same index.
151        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
164/// A Stream is a wrapper around an [`AVStream`].
165pub struct Stream<'a>(&'a mut AVStream, *mut AVFormatContext);
166
167impl<'a> Stream<'a> {
168    /// Creates a new `Stream` instance.
169    pub(crate) const fn new(stream: &'a mut AVStream, input: *mut AVFormatContext) -> Self {
170        Self(stream, input)
171    }
172
173    /// Returns a constant pointer to the stream.
174    pub const fn as_ptr(&self) -> *const AVStream {
175        self.0
176    }
177
178    /// Returns a mutable pointer to the stream.
179    pub const fn as_mut_ptr(&mut self) -> *mut AVStream {
180        self.0
181    }
182}
183
184impl<'a> Stream<'a> {
185    /// Returns the index of the stream.
186    pub const fn index(&self) -> i32 {
187        self.0.index
188    }
189
190    /// Returns the ID of the stream.
191    pub const fn id(&self) -> i32 {
192        self.0.id
193    }
194
195    /// Returns the codec parameters of the stream.
196    pub const fn codec_parameters(&self) -> Option<&'a AVCodecParameters> {
197        // Safety: the pointer is valid
198        unsafe { self.0.codecpar.as_ref() }
199    }
200
201    /// Returns the time base of the stream.
202    pub fn time_base(&self) -> Rational {
203        self.0.time_base.into()
204    }
205
206    /// Sets the time base of the stream.
207    pub fn set_time_base(&mut self, time_base: impl Into<Rational>) {
208        self.0.time_base = time_base.into().into();
209    }
210
211    /// Returns the start time of the stream.
212    pub const fn start_time(&self) -> Option<i64> {
213        check_i64(self.0.start_time)
214    }
215
216    /// Sets the start time of the stream.
217    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    /// Returns the duration of the stream.
225    pub const fn duration(&self) -> Option<i64> {
226        check_i64(self.0.duration)
227    }
228
229    /// Sets the duration of the stream.
230    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    /// Returns the number of frames in the stream.
238    pub const fn nb_frames(&self) -> Option<i64> {
239        check_i64(self.0.nb_frames)
240    }
241
242    /// Sets the number of frames in the stream.
243    pub const fn set_nb_frames(&mut self, nb_frames: i64) {
244        self.0.nb_frames = nb_frames;
245    }
246
247    /// Returns the disposition of the stream.
248    pub const fn disposition(&self) -> i32 {
249        self.0.disposition
250    }
251
252    /// Sets the disposition of the stream.
253    pub const fn set_disposition(&mut self, disposition: i32) {
254        self.0.disposition = disposition;
255    }
256
257    /// Returns the discard flag of the stream.
258    pub const fn discard(&self) -> AVDiscard {
259        AVDiscard(self.0.discard)
260    }
261
262    /// Sets the discard flag of the stream.
263    pub fn set_discard(&mut self, discard: AVDiscard) {
264        self.0.discard = discard.into();
265    }
266
267    /// Returns the sample aspect ratio of the stream.
268    pub fn sample_aspect_ratio(&self) -> Rational {
269        self.0.sample_aspect_ratio.into()
270    }
271
272    /// Sets the sample aspect ratio of the stream.
273    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    /// Returns the metadata of the stream.
278    pub const fn metadata(&self) -> Const<'_, Dictionary> {
279        // Safety: the pointer metadata pointer does not live longer than this object,
280        // see `Const::new`
281        Const::new(unsafe { Dictionary::from_ptr_ref(self.0.metadata) })
282    }
283
284    /// Returns a mutable reference to the metadata of the stream.
285    pub const fn metadata_mut(&mut self) -> Mut<'_, Dictionary> {
286        // Safety: the pointer metadata pointer does not live longer than this object,
287        // see `Mut::new`
288        Mut::new(unsafe { Dictionary::from_ptr_ref(self.0.metadata) })
289    }
290
291    /// Returns the average frame rate of the stream.
292    pub fn avg_frame_rate(&self) -> Rational {
293        self.0.avg_frame_rate.into()
294    }
295
296    /// Returns the real frame rate of the stream.
297    pub fn r_frame_rate(&self) -> Rational {
298        self.0.r_frame_rate.into()
299    }
300
301    /// Returns the format context of the stream.
302    ///
303    /// # Safety
304    /// This function is unsafe because it returns a mutable pointer to the format context.
305    /// The caller must ensure that they have exclusive access to the format context.
306    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        // sorting metadata as the order is not guaranteed
529        let sorted_metadata: BTreeMap<_, _> = metadata
530            .iter()
531            .filter_map(|(key, value)| {
532                // convert `CStr` to `&str` to gracefully handle invalid UTF-8
533                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        // Safety: We are the only ones who have access.
568        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        // sorting metadata as the order is not guaranteed
585        let sorted_metadata: BTreeMap<_, _> = metadata
586            .iter()
587            .filter_map(|(key, value)| {
588                // convert `CStr` to `&str` to gracefully handle invalid UTF-8
589                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}