1use std::time::Duration;
43
44use crate::channel::ChannelLayout;
45use crate::codec::{AudioCodec, VideoCodec};
46use crate::color::{ColorPrimaries, ColorRange, ColorSpace};
47use crate::pixel::PixelFormat;
48use crate::sample::SampleFormat;
49use crate::time::Rational;
50
51#[derive(Debug, Clone)]
75pub struct VideoStreamInfo {
76 index: u32,
78 codec: VideoCodec,
80 codec_name: String,
82 width: u32,
84 height: u32,
86 pixel_format: PixelFormat,
88 frame_rate: Rational,
90 duration: Option<Duration>,
92 bitrate: Option<u64>,
94 frame_count: Option<u64>,
96 color_space: ColorSpace,
98 color_range: ColorRange,
100 color_primaries: ColorPrimaries,
102}
103
104impl VideoStreamInfo {
105 #[must_use]
123 pub fn builder() -> VideoStreamInfoBuilder {
124 VideoStreamInfoBuilder::default()
125 }
126
127 #[must_use]
129 #[inline]
130 pub const fn index(&self) -> u32 {
131 self.index
132 }
133
134 #[must_use]
136 #[inline]
137 pub const fn codec(&self) -> VideoCodec {
138 self.codec
139 }
140
141 #[must_use]
143 #[inline]
144 pub fn codec_name(&self) -> &str {
145 &self.codec_name
146 }
147
148 #[must_use]
150 #[inline]
151 pub const fn width(&self) -> u32 {
152 self.width
153 }
154
155 #[must_use]
157 #[inline]
158 pub const fn height(&self) -> u32 {
159 self.height
160 }
161
162 #[must_use]
164 #[inline]
165 pub const fn pixel_format(&self) -> PixelFormat {
166 self.pixel_format
167 }
168
169 #[must_use]
171 #[inline]
172 pub const fn frame_rate(&self) -> Rational {
173 self.frame_rate
174 }
175
176 #[must_use]
178 #[inline]
179 pub fn fps(&self) -> f64 {
180 self.frame_rate.as_f64()
181 }
182
183 #[must_use]
185 #[inline]
186 pub const fn duration(&self) -> Option<Duration> {
187 self.duration
188 }
189
190 #[must_use]
192 #[inline]
193 pub const fn bitrate(&self) -> Option<u64> {
194 self.bitrate
195 }
196
197 #[must_use]
199 #[inline]
200 pub const fn frame_count(&self) -> Option<u64> {
201 self.frame_count
202 }
203
204 #[must_use]
206 #[inline]
207 pub const fn color_space(&self) -> ColorSpace {
208 self.color_space
209 }
210
211 #[must_use]
213 #[inline]
214 pub const fn color_range(&self) -> ColorRange {
215 self.color_range
216 }
217
218 #[must_use]
220 #[inline]
221 pub const fn color_primaries(&self) -> ColorPrimaries {
222 self.color_primaries
223 }
224
225 #[must_use]
227 #[inline]
228 pub fn aspect_ratio(&self) -> f64 {
229 if self.height == 0 {
230 0.0
231 } else {
232 f64::from(self.width) / f64::from(self.height)
233 }
234 }
235
236 #[must_use]
238 #[inline]
239 pub const fn is_hd(&self) -> bool {
240 self.height >= 720
241 }
242
243 #[must_use]
245 #[inline]
246 pub const fn is_full_hd(&self) -> bool {
247 self.height >= 1080
248 }
249
250 #[must_use]
252 #[inline]
253 pub const fn is_4k(&self) -> bool {
254 self.height >= 2160
255 }
256
257 #[must_use]
294 #[inline]
295 pub fn is_hdr(&self) -> bool {
296 self.color_primaries.is_wide_gamut() && self.pixel_format.is_high_bit_depth()
298 }
299}
300
301impl Default for VideoStreamInfo {
302 fn default() -> Self {
303 Self {
304 index: 0,
305 codec: VideoCodec::default(),
306 codec_name: String::new(),
307 width: 0,
308 height: 0,
309 pixel_format: PixelFormat::default(),
310 frame_rate: Rational::new(30, 1),
311 duration: None,
312 bitrate: None,
313 frame_count: None,
314 color_space: ColorSpace::default(),
315 color_range: ColorRange::default(),
316 color_primaries: ColorPrimaries::default(),
317 }
318 }
319}
320
321#[derive(Debug, Clone, Default)]
323pub struct VideoStreamInfoBuilder {
324 index: u32,
325 codec: VideoCodec,
326 codec_name: String,
327 width: u32,
328 height: u32,
329 pixel_format: PixelFormat,
330 frame_rate: Rational,
331 duration: Option<Duration>,
332 bitrate: Option<u64>,
333 frame_count: Option<u64>,
334 color_space: ColorSpace,
335 color_range: ColorRange,
336 color_primaries: ColorPrimaries,
337}
338
339impl VideoStreamInfoBuilder {
340 #[must_use]
342 pub fn index(mut self, index: u32) -> Self {
343 self.index = index;
344 self
345 }
346
347 #[must_use]
349 pub fn codec(mut self, codec: VideoCodec) -> Self {
350 self.codec = codec;
351 self
352 }
353
354 #[must_use]
356 pub fn codec_name(mut self, name: impl Into<String>) -> Self {
357 self.codec_name = name.into();
358 self
359 }
360
361 #[must_use]
363 pub fn width(mut self, width: u32) -> Self {
364 self.width = width;
365 self
366 }
367
368 #[must_use]
370 pub fn height(mut self, height: u32) -> Self {
371 self.height = height;
372 self
373 }
374
375 #[must_use]
377 pub fn pixel_format(mut self, format: PixelFormat) -> Self {
378 self.pixel_format = format;
379 self
380 }
381
382 #[must_use]
384 pub fn frame_rate(mut self, rate: Rational) -> Self {
385 self.frame_rate = rate;
386 self
387 }
388
389 #[must_use]
391 pub fn duration(mut self, duration: Duration) -> Self {
392 self.duration = Some(duration);
393 self
394 }
395
396 #[must_use]
398 pub fn bitrate(mut self, bitrate: u64) -> Self {
399 self.bitrate = Some(bitrate);
400 self
401 }
402
403 #[must_use]
405 pub fn frame_count(mut self, count: u64) -> Self {
406 self.frame_count = Some(count);
407 self
408 }
409
410 #[must_use]
412 pub fn color_space(mut self, space: ColorSpace) -> Self {
413 self.color_space = space;
414 self
415 }
416
417 #[must_use]
419 pub fn color_range(mut self, range: ColorRange) -> Self {
420 self.color_range = range;
421 self
422 }
423
424 #[must_use]
426 pub fn color_primaries(mut self, primaries: ColorPrimaries) -> Self {
427 self.color_primaries = primaries;
428 self
429 }
430
431 #[must_use]
433 pub fn build(self) -> VideoStreamInfo {
434 VideoStreamInfo {
435 index: self.index,
436 codec: self.codec,
437 codec_name: self.codec_name,
438 width: self.width,
439 height: self.height,
440 pixel_format: self.pixel_format,
441 frame_rate: self.frame_rate,
442 duration: self.duration,
443 bitrate: self.bitrate,
444 frame_count: self.frame_count,
445 color_space: self.color_space,
446 color_range: self.color_range,
447 color_primaries: self.color_primaries,
448 }
449 }
450}
451
452#[derive(Debug, Clone)]
475pub struct AudioStreamInfo {
476 index: u32,
478 codec: AudioCodec,
480 codec_name: String,
482 sample_rate: u32,
484 channels: u32,
486 channel_layout: ChannelLayout,
488 sample_format: SampleFormat,
490 duration: Option<Duration>,
492 bitrate: Option<u64>,
494 language: Option<String>,
496}
497
498impl AudioStreamInfo {
499 #[must_use]
516 pub fn builder() -> AudioStreamInfoBuilder {
517 AudioStreamInfoBuilder::default()
518 }
519
520 #[must_use]
522 #[inline]
523 pub const fn index(&self) -> u32 {
524 self.index
525 }
526
527 #[must_use]
529 #[inline]
530 pub const fn codec(&self) -> AudioCodec {
531 self.codec
532 }
533
534 #[must_use]
536 #[inline]
537 pub fn codec_name(&self) -> &str {
538 &self.codec_name
539 }
540
541 #[must_use]
543 #[inline]
544 pub const fn sample_rate(&self) -> u32 {
545 self.sample_rate
546 }
547
548 #[must_use]
550 #[inline]
551 pub const fn channels(&self) -> u32 {
552 self.channels
553 }
554
555 #[must_use]
557 #[inline]
558 pub const fn channel_layout(&self) -> ChannelLayout {
559 self.channel_layout
560 }
561
562 #[must_use]
564 #[inline]
565 pub const fn sample_format(&self) -> SampleFormat {
566 self.sample_format
567 }
568
569 #[must_use]
571 #[inline]
572 pub const fn duration(&self) -> Option<Duration> {
573 self.duration
574 }
575
576 #[must_use]
578 #[inline]
579 pub const fn bitrate(&self) -> Option<u64> {
580 self.bitrate
581 }
582
583 #[must_use]
585 #[inline]
586 pub fn language(&self) -> Option<&str> {
587 self.language.as_deref()
588 }
589
590 #[must_use]
592 #[inline]
593 pub const fn is_mono(&self) -> bool {
594 self.channels == 1
595 }
596
597 #[must_use]
599 #[inline]
600 pub const fn is_stereo(&self) -> bool {
601 self.channels == 2
602 }
603
604 #[must_use]
606 #[inline]
607 pub const fn is_surround(&self) -> bool {
608 self.channels > 2
609 }
610}
611
612impl Default for AudioStreamInfo {
613 fn default() -> Self {
614 Self {
615 index: 0,
616 codec: AudioCodec::default(),
617 codec_name: String::new(),
618 sample_rate: 48000,
619 channels: 2,
620 channel_layout: ChannelLayout::default(),
621 sample_format: SampleFormat::default(),
622 duration: None,
623 bitrate: None,
624 language: None,
625 }
626 }
627}
628
629#[derive(Debug, Clone, Default)]
631pub struct AudioStreamInfoBuilder {
632 index: u32,
633 codec: AudioCodec,
634 codec_name: String,
635 sample_rate: u32,
636 channels: u32,
637 channel_layout: Option<ChannelLayout>,
638 sample_format: SampleFormat,
639 duration: Option<Duration>,
640 bitrate: Option<u64>,
641 language: Option<String>,
642}
643
644impl AudioStreamInfoBuilder {
645 #[must_use]
647 pub fn index(mut self, index: u32) -> Self {
648 self.index = index;
649 self
650 }
651
652 #[must_use]
654 pub fn codec(mut self, codec: AudioCodec) -> Self {
655 self.codec = codec;
656 self
657 }
658
659 #[must_use]
661 pub fn codec_name(mut self, name: impl Into<String>) -> Self {
662 self.codec_name = name.into();
663 self
664 }
665
666 #[must_use]
668 pub fn sample_rate(mut self, rate: u32) -> Self {
669 self.sample_rate = rate;
670 self
671 }
672
673 #[must_use]
677 pub fn channels(mut self, channels: u32) -> Self {
678 self.channels = channels;
679 self
680 }
681
682 #[must_use]
684 pub fn channel_layout(mut self, layout: ChannelLayout) -> Self {
685 self.channel_layout = Some(layout);
686 self
687 }
688
689 #[must_use]
691 pub fn sample_format(mut self, format: SampleFormat) -> Self {
692 self.sample_format = format;
693 self
694 }
695
696 #[must_use]
698 pub fn duration(mut self, duration: Duration) -> Self {
699 self.duration = Some(duration);
700 self
701 }
702
703 #[must_use]
705 pub fn bitrate(mut self, bitrate: u64) -> Self {
706 self.bitrate = Some(bitrate);
707 self
708 }
709
710 #[must_use]
712 pub fn language(mut self, lang: impl Into<String>) -> Self {
713 self.language = Some(lang.into());
714 self
715 }
716
717 #[must_use]
719 pub fn build(self) -> AudioStreamInfo {
720 let channel_layout = self
721 .channel_layout
722 .unwrap_or_else(|| ChannelLayout::from_channels(self.channels));
723
724 AudioStreamInfo {
725 index: self.index,
726 codec: self.codec,
727 codec_name: self.codec_name,
728 sample_rate: self.sample_rate,
729 channels: self.channels,
730 channel_layout,
731 sample_format: self.sample_format,
732 duration: self.duration,
733 bitrate: self.bitrate,
734 language: self.language,
735 }
736 }
737}
738
739#[cfg(test)]
740mod tests {
741 use super::*;
742
743 mod video_stream_info_tests {
744 use super::*;
745
746 #[test]
747 fn test_builder_basic() {
748 let info = VideoStreamInfo::builder()
749 .index(0)
750 .codec(VideoCodec::H264)
751 .codec_name("h264")
752 .width(1920)
753 .height(1080)
754 .frame_rate(Rational::new(30, 1))
755 .pixel_format(PixelFormat::Yuv420p)
756 .build();
757
758 assert_eq!(info.index(), 0);
759 assert_eq!(info.codec(), VideoCodec::H264);
760 assert_eq!(info.codec_name(), "h264");
761 assert_eq!(info.width(), 1920);
762 assert_eq!(info.height(), 1080);
763 assert!((info.fps() - 30.0).abs() < 0.001);
764 assert_eq!(info.pixel_format(), PixelFormat::Yuv420p);
765 }
766
767 #[test]
768 fn test_builder_full() {
769 let info = VideoStreamInfo::builder()
770 .index(0)
771 .codec(VideoCodec::H265)
772 .codec_name("hevc")
773 .width(3840)
774 .height(2160)
775 .frame_rate(Rational::new(60, 1))
776 .pixel_format(PixelFormat::Yuv420p10le)
777 .duration(Duration::from_secs(120))
778 .bitrate(50_000_000)
779 .frame_count(7200)
780 .color_space(ColorSpace::Bt2020)
781 .color_range(ColorRange::Full)
782 .color_primaries(ColorPrimaries::Bt2020)
783 .build();
784
785 assert_eq!(info.codec(), VideoCodec::H265);
786 assert_eq!(info.width(), 3840);
787 assert_eq!(info.height(), 2160);
788 assert_eq!(info.duration(), Some(Duration::from_secs(120)));
789 assert_eq!(info.bitrate(), Some(50_000_000));
790 assert_eq!(info.frame_count(), Some(7200));
791 assert_eq!(info.color_space(), ColorSpace::Bt2020);
792 assert_eq!(info.color_range(), ColorRange::Full);
793 assert_eq!(info.color_primaries(), ColorPrimaries::Bt2020);
794 }
795
796 #[test]
797 fn test_default() {
798 let info = VideoStreamInfo::default();
799 assert_eq!(info.index(), 0);
800 assert_eq!(info.codec(), VideoCodec::default());
801 assert_eq!(info.width(), 0);
802 assert_eq!(info.height(), 0);
803 assert!(info.duration().is_none());
804 }
805
806 #[test]
807 fn test_aspect_ratio() {
808 let info = VideoStreamInfo::builder().width(1920).height(1080).build();
809 assert!((info.aspect_ratio() - (16.0 / 9.0)).abs() < 0.01);
810
811 let info = VideoStreamInfo::builder().width(1280).height(720).build();
812 assert!((info.aspect_ratio() - (16.0 / 9.0)).abs() < 0.01);
813
814 let info = VideoStreamInfo::builder().width(1920).height(0).build();
816 assert_eq!(info.aspect_ratio(), 0.0);
817 }
818
819 #[test]
820 fn test_resolution_checks() {
821 let sd = VideoStreamInfo::builder().width(720).height(480).build();
823 assert!(!sd.is_hd());
824 assert!(!sd.is_full_hd());
825 assert!(!sd.is_4k());
826
827 let hd = VideoStreamInfo::builder().width(1280).height(720).build();
829 assert!(hd.is_hd());
830 assert!(!hd.is_full_hd());
831 assert!(!hd.is_4k());
832
833 let fhd = VideoStreamInfo::builder().width(1920).height(1080).build();
835 assert!(fhd.is_hd());
836 assert!(fhd.is_full_hd());
837 assert!(!fhd.is_4k());
838
839 let uhd = VideoStreamInfo::builder().width(3840).height(2160).build();
841 assert!(uhd.is_hd());
842 assert!(uhd.is_full_hd());
843 assert!(uhd.is_4k());
844 }
845
846 #[test]
847 fn test_is_hdr() {
848 let hdr = VideoStreamInfo::builder()
850 .width(3840)
851 .height(2160)
852 .color_primaries(ColorPrimaries::Bt2020)
853 .pixel_format(PixelFormat::Yuv420p10le)
854 .build();
855 assert!(hdr.is_hdr());
856
857 let hdr_p010 = VideoStreamInfo::builder()
859 .width(3840)
860 .height(2160)
861 .color_primaries(ColorPrimaries::Bt2020)
862 .pixel_format(PixelFormat::P010le)
863 .build();
864 assert!(hdr_p010.is_hdr());
865
866 let sdr_hd = VideoStreamInfo::builder()
868 .width(1920)
869 .height(1080)
870 .color_primaries(ColorPrimaries::Bt709)
871 .pixel_format(PixelFormat::Yuv420p)
872 .build();
873 assert!(!sdr_hd.is_hdr());
874
875 let wide_gamut_8bit = VideoStreamInfo::builder()
877 .width(3840)
878 .height(2160)
879 .color_primaries(ColorPrimaries::Bt2020)
880 .pixel_format(PixelFormat::Yuv420p) .build();
882 assert!(!wide_gamut_8bit.is_hdr());
883
884 let hd_10bit = VideoStreamInfo::builder()
886 .width(1920)
887 .height(1080)
888 .color_primaries(ColorPrimaries::Bt709)
889 .pixel_format(PixelFormat::Yuv420p10le)
890 .build();
891 assert!(!hd_10bit.is_hdr());
892
893 let default = VideoStreamInfo::default();
895 assert!(!default.is_hdr());
896 }
897
898 #[test]
899 fn test_debug() {
900 let info = VideoStreamInfo::builder()
901 .index(0)
902 .codec(VideoCodec::H264)
903 .width(1920)
904 .height(1080)
905 .build();
906 let debug = format!("{info:?}");
907 assert!(debug.contains("VideoStreamInfo"));
908 assert!(debug.contains("1920"));
909 assert!(debug.contains("1080"));
910 }
911
912 #[test]
913 fn test_clone() {
914 let info = VideoStreamInfo::builder()
915 .index(0)
916 .codec(VideoCodec::H264)
917 .codec_name("h264")
918 .width(1920)
919 .height(1080)
920 .build();
921 let cloned = info.clone();
922 assert_eq!(info.width(), cloned.width());
923 assert_eq!(info.height(), cloned.height());
924 assert_eq!(info.codec_name(), cloned.codec_name());
925 }
926 }
927
928 mod audio_stream_info_tests {
929 use super::*;
930
931 #[test]
932 fn test_builder_basic() {
933 let info = AudioStreamInfo::builder()
934 .index(1)
935 .codec(AudioCodec::Aac)
936 .codec_name("aac")
937 .sample_rate(48000)
938 .channels(2)
939 .sample_format(SampleFormat::F32)
940 .build();
941
942 assert_eq!(info.index(), 1);
943 assert_eq!(info.codec(), AudioCodec::Aac);
944 assert_eq!(info.codec_name(), "aac");
945 assert_eq!(info.sample_rate(), 48000);
946 assert_eq!(info.channels(), 2);
947 assert_eq!(info.sample_format(), SampleFormat::F32);
948 assert_eq!(info.channel_layout(), ChannelLayout::Stereo);
949 }
950
951 #[test]
952 fn test_builder_full() {
953 let info = AudioStreamInfo::builder()
954 .index(2)
955 .codec(AudioCodec::Flac)
956 .codec_name("flac")
957 .sample_rate(96000)
958 .channels(6)
959 .channel_layout(ChannelLayout::Surround5_1)
960 .sample_format(SampleFormat::I32)
961 .duration(Duration::from_secs(300))
962 .bitrate(1_411_200)
963 .language("jpn")
964 .build();
965
966 assert_eq!(info.codec(), AudioCodec::Flac);
967 assert_eq!(info.sample_rate(), 96000);
968 assert_eq!(info.channels(), 6);
969 assert_eq!(info.channel_layout(), ChannelLayout::Surround5_1);
970 assert_eq!(info.duration(), Some(Duration::from_secs(300)));
971 assert_eq!(info.bitrate(), Some(1_411_200));
972 assert_eq!(info.language(), Some("jpn"));
973 }
974
975 #[test]
976 fn test_default() {
977 let info = AudioStreamInfo::default();
978 assert_eq!(info.index(), 0);
979 assert_eq!(info.codec(), AudioCodec::default());
980 assert_eq!(info.sample_rate(), 48000);
981 assert_eq!(info.channels(), 2);
982 assert!(info.duration().is_none());
983 }
984
985 #[test]
986 fn test_auto_channel_layout() {
987 let mono = AudioStreamInfo::builder().channels(1).build();
989 assert_eq!(mono.channel_layout(), ChannelLayout::Mono);
990
991 let stereo = AudioStreamInfo::builder().channels(2).build();
992 assert_eq!(stereo.channel_layout(), ChannelLayout::Stereo);
993
994 let surround = AudioStreamInfo::builder().channels(6).build();
995 assert_eq!(surround.channel_layout(), ChannelLayout::Surround5_1);
996
997 let custom = AudioStreamInfo::builder()
999 .channels(6)
1000 .channel_layout(ChannelLayout::Other(6))
1001 .build();
1002 assert_eq!(custom.channel_layout(), ChannelLayout::Other(6));
1003 }
1004
1005 #[test]
1006 fn test_channel_checks() {
1007 let mono = AudioStreamInfo::builder().channels(1).build();
1008 assert!(mono.is_mono());
1009 assert!(!mono.is_stereo());
1010 assert!(!mono.is_surround());
1011
1012 let stereo = AudioStreamInfo::builder().channels(2).build();
1013 assert!(!stereo.is_mono());
1014 assert!(stereo.is_stereo());
1015 assert!(!stereo.is_surround());
1016
1017 let surround = AudioStreamInfo::builder().channels(6).build();
1018 assert!(!surround.is_mono());
1019 assert!(!surround.is_stereo());
1020 assert!(surround.is_surround());
1021 }
1022
1023 #[test]
1024 fn test_debug() {
1025 let info = AudioStreamInfo::builder()
1026 .index(1)
1027 .codec(AudioCodec::Aac)
1028 .sample_rate(48000)
1029 .channels(2)
1030 .build();
1031 let debug = format!("{info:?}");
1032 assert!(debug.contains("AudioStreamInfo"));
1033 assert!(debug.contains("48000"));
1034 }
1035
1036 #[test]
1037 fn test_clone() {
1038 let info = AudioStreamInfo::builder()
1039 .index(1)
1040 .codec(AudioCodec::Aac)
1041 .codec_name("aac")
1042 .sample_rate(48000)
1043 .channels(2)
1044 .language("eng")
1045 .build();
1046 let cloned = info.clone();
1047 assert_eq!(info.sample_rate(), cloned.sample_rate());
1048 assert_eq!(info.channels(), cloned.channels());
1049 assert_eq!(info.language(), cloned.language());
1050 assert_eq!(info.codec_name(), cloned.codec_name());
1051 }
1052 }
1053}