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 log::warn!(
231 "aspect_ratio unavailable, height is 0, returning 0.0 \
232 width={} height=0 fallback=0.0",
233 self.width
234 );
235 0.0
236 } else {
237 f64::from(self.width) / f64::from(self.height)
238 }
239 }
240
241 #[must_use]
243 #[inline]
244 pub const fn is_hd(&self) -> bool {
245 self.height >= 720
246 }
247
248 #[must_use]
250 #[inline]
251 pub const fn is_full_hd(&self) -> bool {
252 self.height >= 1080
253 }
254
255 #[must_use]
257 #[inline]
258 pub const fn is_4k(&self) -> bool {
259 self.height >= 2160
260 }
261
262 #[must_use]
299 #[inline]
300 pub fn is_hdr(&self) -> bool {
301 self.color_primaries.is_wide_gamut() && self.pixel_format.is_high_bit_depth()
303 }
304}
305
306impl Default for VideoStreamInfo {
307 fn default() -> Self {
308 Self {
309 index: 0,
310 codec: VideoCodec::default(),
311 codec_name: String::new(),
312 width: 0,
313 height: 0,
314 pixel_format: PixelFormat::default(),
315 frame_rate: Rational::new(30, 1),
316 duration: None,
317 bitrate: None,
318 frame_count: None,
319 color_space: ColorSpace::default(),
320 color_range: ColorRange::default(),
321 color_primaries: ColorPrimaries::default(),
322 }
323 }
324}
325
326#[derive(Debug, Clone, Default)]
328pub struct VideoStreamInfoBuilder {
329 index: u32,
330 codec: VideoCodec,
331 codec_name: String,
332 width: u32,
333 height: u32,
334 pixel_format: PixelFormat,
335 frame_rate: Rational,
336 duration: Option<Duration>,
337 bitrate: Option<u64>,
338 frame_count: Option<u64>,
339 color_space: ColorSpace,
340 color_range: ColorRange,
341 color_primaries: ColorPrimaries,
342}
343
344impl VideoStreamInfoBuilder {
345 #[must_use]
347 pub fn index(mut self, index: u32) -> Self {
348 self.index = index;
349 self
350 }
351
352 #[must_use]
354 pub fn codec(mut self, codec: VideoCodec) -> Self {
355 self.codec = codec;
356 self
357 }
358
359 #[must_use]
361 pub fn codec_name(mut self, name: impl Into<String>) -> Self {
362 self.codec_name = name.into();
363 self
364 }
365
366 #[must_use]
368 pub fn width(mut self, width: u32) -> Self {
369 self.width = width;
370 self
371 }
372
373 #[must_use]
375 pub fn height(mut self, height: u32) -> Self {
376 self.height = height;
377 self
378 }
379
380 #[must_use]
382 pub fn pixel_format(mut self, format: PixelFormat) -> Self {
383 self.pixel_format = format;
384 self
385 }
386
387 #[must_use]
389 pub fn frame_rate(mut self, rate: Rational) -> Self {
390 self.frame_rate = rate;
391 self
392 }
393
394 #[must_use]
396 pub fn duration(mut self, duration: Duration) -> Self {
397 self.duration = Some(duration);
398 self
399 }
400
401 #[must_use]
403 pub fn bitrate(mut self, bitrate: u64) -> Self {
404 self.bitrate = Some(bitrate);
405 self
406 }
407
408 #[must_use]
410 pub fn frame_count(mut self, count: u64) -> Self {
411 self.frame_count = Some(count);
412 self
413 }
414
415 #[must_use]
417 pub fn color_space(mut self, space: ColorSpace) -> Self {
418 self.color_space = space;
419 self
420 }
421
422 #[must_use]
424 pub fn color_range(mut self, range: ColorRange) -> Self {
425 self.color_range = range;
426 self
427 }
428
429 #[must_use]
431 pub fn color_primaries(mut self, primaries: ColorPrimaries) -> Self {
432 self.color_primaries = primaries;
433 self
434 }
435
436 #[must_use]
438 pub fn build(self) -> VideoStreamInfo {
439 VideoStreamInfo {
440 index: self.index,
441 codec: self.codec,
442 codec_name: self.codec_name,
443 width: self.width,
444 height: self.height,
445 pixel_format: self.pixel_format,
446 frame_rate: self.frame_rate,
447 duration: self.duration,
448 bitrate: self.bitrate,
449 frame_count: self.frame_count,
450 color_space: self.color_space,
451 color_range: self.color_range,
452 color_primaries: self.color_primaries,
453 }
454 }
455}
456
457#[derive(Debug, Clone)]
480pub struct AudioStreamInfo {
481 index: u32,
483 codec: AudioCodec,
485 codec_name: String,
487 sample_rate: u32,
489 channels: u32,
491 channel_layout: ChannelLayout,
493 sample_format: SampleFormat,
495 duration: Option<Duration>,
497 bitrate: Option<u64>,
499 language: Option<String>,
501}
502
503impl AudioStreamInfo {
504 #[must_use]
521 pub fn builder() -> AudioStreamInfoBuilder {
522 AudioStreamInfoBuilder::default()
523 }
524
525 #[must_use]
527 #[inline]
528 pub const fn index(&self) -> u32 {
529 self.index
530 }
531
532 #[must_use]
534 #[inline]
535 pub const fn codec(&self) -> AudioCodec {
536 self.codec
537 }
538
539 #[must_use]
541 #[inline]
542 pub fn codec_name(&self) -> &str {
543 &self.codec_name
544 }
545
546 #[must_use]
548 #[inline]
549 pub const fn sample_rate(&self) -> u32 {
550 self.sample_rate
551 }
552
553 #[must_use]
555 #[inline]
556 pub const fn channels(&self) -> u32 {
557 self.channels
558 }
559
560 #[must_use]
562 #[inline]
563 pub const fn channel_layout(&self) -> ChannelLayout {
564 self.channel_layout
565 }
566
567 #[must_use]
569 #[inline]
570 pub const fn sample_format(&self) -> SampleFormat {
571 self.sample_format
572 }
573
574 #[must_use]
576 #[inline]
577 pub const fn duration(&self) -> Option<Duration> {
578 self.duration
579 }
580
581 #[must_use]
583 #[inline]
584 pub const fn bitrate(&self) -> Option<u64> {
585 self.bitrate
586 }
587
588 #[must_use]
590 #[inline]
591 pub fn language(&self) -> Option<&str> {
592 self.language.as_deref()
593 }
594
595 #[must_use]
597 #[inline]
598 pub const fn is_mono(&self) -> bool {
599 self.channels == 1
600 }
601
602 #[must_use]
604 #[inline]
605 pub const fn is_stereo(&self) -> bool {
606 self.channels == 2
607 }
608
609 #[must_use]
611 #[inline]
612 pub const fn is_surround(&self) -> bool {
613 self.channels > 2
614 }
615}
616
617impl Default for AudioStreamInfo {
618 fn default() -> Self {
619 Self {
620 index: 0,
621 codec: AudioCodec::default(),
622 codec_name: String::new(),
623 sample_rate: 48000,
624 channels: 2,
625 channel_layout: ChannelLayout::default(),
626 sample_format: SampleFormat::default(),
627 duration: None,
628 bitrate: None,
629 language: None,
630 }
631 }
632}
633
634#[derive(Debug, Clone, Default)]
636pub struct AudioStreamInfoBuilder {
637 index: u32,
638 codec: AudioCodec,
639 codec_name: String,
640 sample_rate: u32,
641 channels: u32,
642 channel_layout: Option<ChannelLayout>,
643 sample_format: SampleFormat,
644 duration: Option<Duration>,
645 bitrate: Option<u64>,
646 language: Option<String>,
647}
648
649impl AudioStreamInfoBuilder {
650 #[must_use]
652 pub fn index(mut self, index: u32) -> Self {
653 self.index = index;
654 self
655 }
656
657 #[must_use]
659 pub fn codec(mut self, codec: AudioCodec) -> Self {
660 self.codec = codec;
661 self
662 }
663
664 #[must_use]
666 pub fn codec_name(mut self, name: impl Into<String>) -> Self {
667 self.codec_name = name.into();
668 self
669 }
670
671 #[must_use]
673 pub fn sample_rate(mut self, rate: u32) -> Self {
674 self.sample_rate = rate;
675 self
676 }
677
678 #[must_use]
682 pub fn channels(mut self, channels: u32) -> Self {
683 self.channels = channels;
684 self
685 }
686
687 #[must_use]
689 pub fn channel_layout(mut self, layout: ChannelLayout) -> Self {
690 self.channel_layout = Some(layout);
691 self
692 }
693
694 #[must_use]
696 pub fn sample_format(mut self, format: SampleFormat) -> Self {
697 self.sample_format = format;
698 self
699 }
700
701 #[must_use]
703 pub fn duration(mut self, duration: Duration) -> Self {
704 self.duration = Some(duration);
705 self
706 }
707
708 #[must_use]
710 pub fn bitrate(mut self, bitrate: u64) -> Self {
711 self.bitrate = Some(bitrate);
712 self
713 }
714
715 #[must_use]
717 pub fn language(mut self, lang: impl Into<String>) -> Self {
718 self.language = Some(lang.into());
719 self
720 }
721
722 #[must_use]
724 pub fn build(self) -> AudioStreamInfo {
725 let channel_layout = self.channel_layout.unwrap_or_else(|| {
726 log::warn!(
727 "channel_layout not set, deriving from channel count \
728 channels={} fallback=from_channels",
729 self.channels
730 );
731 ChannelLayout::from_channels(self.channels)
732 });
733
734 AudioStreamInfo {
735 index: self.index,
736 codec: self.codec,
737 codec_name: self.codec_name,
738 sample_rate: self.sample_rate,
739 channels: self.channels,
740 channel_layout,
741 sample_format: self.sample_format,
742 duration: self.duration,
743 bitrate: self.bitrate,
744 language: self.language,
745 }
746 }
747}
748
749#[cfg(test)]
750mod tests {
751 use super::*;
752
753 mod video_stream_info_tests {
754 use super::*;
755
756 #[test]
757 fn test_builder_basic() {
758 let info = VideoStreamInfo::builder()
759 .index(0)
760 .codec(VideoCodec::H264)
761 .codec_name("h264")
762 .width(1920)
763 .height(1080)
764 .frame_rate(Rational::new(30, 1))
765 .pixel_format(PixelFormat::Yuv420p)
766 .build();
767
768 assert_eq!(info.index(), 0);
769 assert_eq!(info.codec(), VideoCodec::H264);
770 assert_eq!(info.codec_name(), "h264");
771 assert_eq!(info.width(), 1920);
772 assert_eq!(info.height(), 1080);
773 assert!((info.fps() - 30.0).abs() < 0.001);
774 assert_eq!(info.pixel_format(), PixelFormat::Yuv420p);
775 }
776
777 #[test]
778 fn test_builder_full() {
779 let info = VideoStreamInfo::builder()
780 .index(0)
781 .codec(VideoCodec::H265)
782 .codec_name("hevc")
783 .width(3840)
784 .height(2160)
785 .frame_rate(Rational::new(60, 1))
786 .pixel_format(PixelFormat::Yuv420p10le)
787 .duration(Duration::from_secs(120))
788 .bitrate(50_000_000)
789 .frame_count(7200)
790 .color_space(ColorSpace::Bt2020)
791 .color_range(ColorRange::Full)
792 .color_primaries(ColorPrimaries::Bt2020)
793 .build();
794
795 assert_eq!(info.codec(), VideoCodec::H265);
796 assert_eq!(info.width(), 3840);
797 assert_eq!(info.height(), 2160);
798 assert_eq!(info.duration(), Some(Duration::from_secs(120)));
799 assert_eq!(info.bitrate(), Some(50_000_000));
800 assert_eq!(info.frame_count(), Some(7200));
801 assert_eq!(info.color_space(), ColorSpace::Bt2020);
802 assert_eq!(info.color_range(), ColorRange::Full);
803 assert_eq!(info.color_primaries(), ColorPrimaries::Bt2020);
804 }
805
806 #[test]
807 fn test_default() {
808 let info = VideoStreamInfo::default();
809 assert_eq!(info.index(), 0);
810 assert_eq!(info.codec(), VideoCodec::default());
811 assert_eq!(info.width(), 0);
812 assert_eq!(info.height(), 0);
813 assert!(info.duration().is_none());
814 }
815
816 #[test]
817 fn test_aspect_ratio() {
818 let info = VideoStreamInfo::builder().width(1920).height(1080).build();
819 assert!((info.aspect_ratio() - (16.0 / 9.0)).abs() < 0.01);
820
821 let info = VideoStreamInfo::builder().width(1280).height(720).build();
822 assert!((info.aspect_ratio() - (16.0 / 9.0)).abs() < 0.01);
823
824 let info = VideoStreamInfo::builder().width(1920).height(0).build();
826 assert_eq!(info.aspect_ratio(), 0.0);
827 }
828
829 #[test]
830 fn test_resolution_checks() {
831 let sd = VideoStreamInfo::builder().width(720).height(480).build();
833 assert!(!sd.is_hd());
834 assert!(!sd.is_full_hd());
835 assert!(!sd.is_4k());
836
837 let hd = VideoStreamInfo::builder().width(1280).height(720).build();
839 assert!(hd.is_hd());
840 assert!(!hd.is_full_hd());
841 assert!(!hd.is_4k());
842
843 let fhd = VideoStreamInfo::builder().width(1920).height(1080).build();
845 assert!(fhd.is_hd());
846 assert!(fhd.is_full_hd());
847 assert!(!fhd.is_4k());
848
849 let uhd = VideoStreamInfo::builder().width(3840).height(2160).build();
851 assert!(uhd.is_hd());
852 assert!(uhd.is_full_hd());
853 assert!(uhd.is_4k());
854 }
855
856 #[test]
857 fn test_is_hdr() {
858 let hdr = VideoStreamInfo::builder()
860 .width(3840)
861 .height(2160)
862 .color_primaries(ColorPrimaries::Bt2020)
863 .pixel_format(PixelFormat::Yuv420p10le)
864 .build();
865 assert!(hdr.is_hdr());
866
867 let hdr_p010 = VideoStreamInfo::builder()
869 .width(3840)
870 .height(2160)
871 .color_primaries(ColorPrimaries::Bt2020)
872 .pixel_format(PixelFormat::P010le)
873 .build();
874 assert!(hdr_p010.is_hdr());
875
876 let sdr_hd = VideoStreamInfo::builder()
878 .width(1920)
879 .height(1080)
880 .color_primaries(ColorPrimaries::Bt709)
881 .pixel_format(PixelFormat::Yuv420p)
882 .build();
883 assert!(!sdr_hd.is_hdr());
884
885 let wide_gamut_8bit = VideoStreamInfo::builder()
887 .width(3840)
888 .height(2160)
889 .color_primaries(ColorPrimaries::Bt2020)
890 .pixel_format(PixelFormat::Yuv420p) .build();
892 assert!(!wide_gamut_8bit.is_hdr());
893
894 let hd_10bit = VideoStreamInfo::builder()
896 .width(1920)
897 .height(1080)
898 .color_primaries(ColorPrimaries::Bt709)
899 .pixel_format(PixelFormat::Yuv420p10le)
900 .build();
901 assert!(!hd_10bit.is_hdr());
902
903 let default = VideoStreamInfo::default();
905 assert!(!default.is_hdr());
906 }
907
908 #[test]
909 fn test_debug() {
910 let info = VideoStreamInfo::builder()
911 .index(0)
912 .codec(VideoCodec::H264)
913 .width(1920)
914 .height(1080)
915 .build();
916 let debug = format!("{info:?}");
917 assert!(debug.contains("VideoStreamInfo"));
918 assert!(debug.contains("1920"));
919 assert!(debug.contains("1080"));
920 }
921
922 #[test]
923 fn test_clone() {
924 let info = VideoStreamInfo::builder()
925 .index(0)
926 .codec(VideoCodec::H264)
927 .codec_name("h264")
928 .width(1920)
929 .height(1080)
930 .build();
931 let cloned = info.clone();
932 assert_eq!(info.width(), cloned.width());
933 assert_eq!(info.height(), cloned.height());
934 assert_eq!(info.codec_name(), cloned.codec_name());
935 }
936 }
937
938 mod audio_stream_info_tests {
939 use super::*;
940
941 #[test]
942 fn test_builder_basic() {
943 let info = AudioStreamInfo::builder()
944 .index(1)
945 .codec(AudioCodec::Aac)
946 .codec_name("aac")
947 .sample_rate(48000)
948 .channels(2)
949 .sample_format(SampleFormat::F32)
950 .build();
951
952 assert_eq!(info.index(), 1);
953 assert_eq!(info.codec(), AudioCodec::Aac);
954 assert_eq!(info.codec_name(), "aac");
955 assert_eq!(info.sample_rate(), 48000);
956 assert_eq!(info.channels(), 2);
957 assert_eq!(info.sample_format(), SampleFormat::F32);
958 assert_eq!(info.channel_layout(), ChannelLayout::Stereo);
959 }
960
961 #[test]
962 fn test_builder_full() {
963 let info = AudioStreamInfo::builder()
964 .index(2)
965 .codec(AudioCodec::Flac)
966 .codec_name("flac")
967 .sample_rate(96000)
968 .channels(6)
969 .channel_layout(ChannelLayout::Surround5_1)
970 .sample_format(SampleFormat::I32)
971 .duration(Duration::from_secs(300))
972 .bitrate(1_411_200)
973 .language("jpn")
974 .build();
975
976 assert_eq!(info.codec(), AudioCodec::Flac);
977 assert_eq!(info.sample_rate(), 96000);
978 assert_eq!(info.channels(), 6);
979 assert_eq!(info.channel_layout(), ChannelLayout::Surround5_1);
980 assert_eq!(info.duration(), Some(Duration::from_secs(300)));
981 assert_eq!(info.bitrate(), Some(1_411_200));
982 assert_eq!(info.language(), Some("jpn"));
983 }
984
985 #[test]
986 fn test_default() {
987 let info = AudioStreamInfo::default();
988 assert_eq!(info.index(), 0);
989 assert_eq!(info.codec(), AudioCodec::default());
990 assert_eq!(info.sample_rate(), 48000);
991 assert_eq!(info.channels(), 2);
992 assert!(info.duration().is_none());
993 }
994
995 #[test]
996 fn test_auto_channel_layout() {
997 let mono = AudioStreamInfo::builder().channels(1).build();
999 assert_eq!(mono.channel_layout(), ChannelLayout::Mono);
1000
1001 let stereo = AudioStreamInfo::builder().channels(2).build();
1002 assert_eq!(stereo.channel_layout(), ChannelLayout::Stereo);
1003
1004 let surround = AudioStreamInfo::builder().channels(6).build();
1005 assert_eq!(surround.channel_layout(), ChannelLayout::Surround5_1);
1006
1007 let custom = AudioStreamInfo::builder()
1009 .channels(6)
1010 .channel_layout(ChannelLayout::Other(6))
1011 .build();
1012 assert_eq!(custom.channel_layout(), ChannelLayout::Other(6));
1013 }
1014
1015 #[test]
1016 fn test_channel_checks() {
1017 let mono = AudioStreamInfo::builder().channels(1).build();
1018 assert!(mono.is_mono());
1019 assert!(!mono.is_stereo());
1020 assert!(!mono.is_surround());
1021
1022 let stereo = AudioStreamInfo::builder().channels(2).build();
1023 assert!(!stereo.is_mono());
1024 assert!(stereo.is_stereo());
1025 assert!(!stereo.is_surround());
1026
1027 let surround = AudioStreamInfo::builder().channels(6).build();
1028 assert!(!surround.is_mono());
1029 assert!(!surround.is_stereo());
1030 assert!(surround.is_surround());
1031 }
1032
1033 #[test]
1034 fn test_debug() {
1035 let info = AudioStreamInfo::builder()
1036 .index(1)
1037 .codec(AudioCodec::Aac)
1038 .sample_rate(48000)
1039 .channels(2)
1040 .build();
1041 let debug = format!("{info:?}");
1042 assert!(debug.contains("AudioStreamInfo"));
1043 assert!(debug.contains("48000"));
1044 }
1045
1046 #[test]
1047 fn test_clone() {
1048 let info = AudioStreamInfo::builder()
1049 .index(1)
1050 .codec(AudioCodec::Aac)
1051 .codec_name("aac")
1052 .sample_rate(48000)
1053 .channels(2)
1054 .language("eng")
1055 .build();
1056 let cloned = info.clone();
1057 assert_eq!(info.sample_rate(), cloned.sample_rate());
1058 assert_eq!(info.channels(), cloned.channels());
1059 assert_eq!(info.language(), cloned.language());
1060 assert_eq!(info.codec_name(), cloned.codec_name());
1061 }
1062 }
1063}