1use std::time::Duration;
4
5use crate::codec::VideoCodec;
6use crate::color::{ColorPrimaries, ColorRange, ColorSpace};
7use crate::pixel::PixelFormat;
8use crate::time::Rational;
9
10#[derive(Debug, Clone)]
34pub struct VideoStreamInfo {
35 index: u32,
37 codec: VideoCodec,
39 codec_name: String,
41 width: u32,
43 height: u32,
45 pixel_format: PixelFormat,
47 frame_rate: Rational,
49 duration: Option<Duration>,
51 bitrate: Option<u64>,
53 frame_count: Option<u64>,
55 color_space: ColorSpace,
57 color_range: ColorRange,
59 color_primaries: ColorPrimaries,
61}
62
63impl VideoStreamInfo {
64 #[must_use]
82 pub fn builder() -> VideoStreamInfoBuilder {
83 VideoStreamInfoBuilder::default()
84 }
85
86 #[must_use]
88 #[inline]
89 pub const fn index(&self) -> u32 {
90 self.index
91 }
92
93 #[must_use]
95 #[inline]
96 pub const fn codec(&self) -> VideoCodec {
97 self.codec
98 }
99
100 #[must_use]
102 #[inline]
103 pub fn codec_name(&self) -> &str {
104 &self.codec_name
105 }
106
107 #[must_use]
109 #[inline]
110 pub const fn width(&self) -> u32 {
111 self.width
112 }
113
114 #[must_use]
116 #[inline]
117 pub const fn height(&self) -> u32 {
118 self.height
119 }
120
121 #[must_use]
123 #[inline]
124 pub const fn pixel_format(&self) -> PixelFormat {
125 self.pixel_format
126 }
127
128 #[must_use]
130 #[inline]
131 pub const fn frame_rate(&self) -> Rational {
132 self.frame_rate
133 }
134
135 #[must_use]
137 #[inline]
138 pub fn fps(&self) -> f64 {
139 self.frame_rate.as_f64()
140 }
141
142 #[must_use]
144 #[inline]
145 pub const fn duration(&self) -> Option<Duration> {
146 self.duration
147 }
148
149 #[must_use]
151 #[inline]
152 pub const fn bitrate(&self) -> Option<u64> {
153 self.bitrate
154 }
155
156 #[must_use]
158 #[inline]
159 pub const fn frame_count(&self) -> Option<u64> {
160 self.frame_count
161 }
162
163 #[must_use]
165 #[inline]
166 pub const fn color_space(&self) -> ColorSpace {
167 self.color_space
168 }
169
170 #[must_use]
172 #[inline]
173 pub const fn color_range(&self) -> ColorRange {
174 self.color_range
175 }
176
177 #[must_use]
179 #[inline]
180 pub const fn color_primaries(&self) -> ColorPrimaries {
181 self.color_primaries
182 }
183
184 #[must_use]
186 #[inline]
187 pub fn aspect_ratio(&self) -> f64 {
188 if self.height == 0 {
189 log::warn!(
190 "aspect_ratio unavailable, height is 0, returning 0.0 \
191 width={} height=0 fallback=0.0",
192 self.width
193 );
194 0.0
195 } else {
196 f64::from(self.width) / f64::from(self.height)
197 }
198 }
199
200 #[must_use]
202 #[inline]
203 pub const fn is_hd(&self) -> bool {
204 self.height >= 720
205 }
206
207 #[must_use]
209 #[inline]
210 pub const fn is_full_hd(&self) -> bool {
211 self.height >= 1080
212 }
213
214 #[must_use]
216 #[inline]
217 pub const fn is_4k(&self) -> bool {
218 self.height >= 2160
219 }
220
221 #[must_use]
258 #[inline]
259 pub fn is_hdr(&self) -> bool {
260 self.color_primaries.is_wide_gamut() && self.pixel_format.is_high_bit_depth()
262 }
263}
264
265impl Default for VideoStreamInfo {
266 fn default() -> Self {
267 Self {
268 index: 0,
269 codec: VideoCodec::default(),
270 codec_name: String::new(),
271 width: 0,
272 height: 0,
273 pixel_format: PixelFormat::default(),
274 frame_rate: Rational::new(30, 1),
275 duration: None,
276 bitrate: None,
277 frame_count: None,
278 color_space: ColorSpace::default(),
279 color_range: ColorRange::default(),
280 color_primaries: ColorPrimaries::default(),
281 }
282 }
283}
284
285#[derive(Debug, Clone, Default)]
287pub struct VideoStreamInfoBuilder {
288 index: u32,
289 codec: VideoCodec,
290 codec_name: String,
291 width: u32,
292 height: u32,
293 pixel_format: PixelFormat,
294 frame_rate: Rational,
295 duration: Option<Duration>,
296 bitrate: Option<u64>,
297 frame_count: Option<u64>,
298 color_space: ColorSpace,
299 color_range: ColorRange,
300 color_primaries: ColorPrimaries,
301}
302
303impl VideoStreamInfoBuilder {
304 #[must_use]
306 pub fn index(mut self, index: u32) -> Self {
307 self.index = index;
308 self
309 }
310
311 #[must_use]
313 pub fn codec(mut self, codec: VideoCodec) -> Self {
314 self.codec = codec;
315 self
316 }
317
318 #[must_use]
320 pub fn codec_name(mut self, name: impl Into<String>) -> Self {
321 self.codec_name = name.into();
322 self
323 }
324
325 #[must_use]
327 pub fn width(mut self, width: u32) -> Self {
328 self.width = width;
329 self
330 }
331
332 #[must_use]
334 pub fn height(mut self, height: u32) -> Self {
335 self.height = height;
336 self
337 }
338
339 #[must_use]
341 pub fn pixel_format(mut self, format: PixelFormat) -> Self {
342 self.pixel_format = format;
343 self
344 }
345
346 #[must_use]
348 pub fn frame_rate(mut self, rate: Rational) -> Self {
349 self.frame_rate = rate;
350 self
351 }
352
353 #[must_use]
355 pub fn duration(mut self, duration: Duration) -> Self {
356 self.duration = Some(duration);
357 self
358 }
359
360 #[must_use]
362 pub fn bitrate(mut self, bitrate: u64) -> Self {
363 self.bitrate = Some(bitrate);
364 self
365 }
366
367 #[must_use]
369 pub fn frame_count(mut self, count: u64) -> Self {
370 self.frame_count = Some(count);
371 self
372 }
373
374 #[must_use]
376 pub fn color_space(mut self, space: ColorSpace) -> Self {
377 self.color_space = space;
378 self
379 }
380
381 #[must_use]
383 pub fn color_range(mut self, range: ColorRange) -> Self {
384 self.color_range = range;
385 self
386 }
387
388 #[must_use]
390 pub fn color_primaries(mut self, primaries: ColorPrimaries) -> Self {
391 self.color_primaries = primaries;
392 self
393 }
394
395 #[must_use]
397 pub fn build(self) -> VideoStreamInfo {
398 VideoStreamInfo {
399 index: self.index,
400 codec: self.codec,
401 codec_name: self.codec_name,
402 width: self.width,
403 height: self.height,
404 pixel_format: self.pixel_format,
405 frame_rate: self.frame_rate,
406 duration: self.duration,
407 bitrate: self.bitrate,
408 frame_count: self.frame_count,
409 color_space: self.color_space,
410 color_range: self.color_range,
411 color_primaries: self.color_primaries,
412 }
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use super::*;
419
420 #[test]
421 fn test_builder_basic() {
422 let info = VideoStreamInfo::builder()
423 .index(0)
424 .codec(VideoCodec::H264)
425 .codec_name("h264")
426 .width(1920)
427 .height(1080)
428 .frame_rate(Rational::new(30, 1))
429 .pixel_format(PixelFormat::Yuv420p)
430 .build();
431
432 assert_eq!(info.index(), 0);
433 assert_eq!(info.codec(), VideoCodec::H264);
434 assert_eq!(info.codec_name(), "h264");
435 assert_eq!(info.width(), 1920);
436 assert_eq!(info.height(), 1080);
437 assert!((info.fps() - 30.0).abs() < 0.001);
438 assert_eq!(info.pixel_format(), PixelFormat::Yuv420p);
439 }
440
441 #[test]
442 fn test_builder_full() {
443 let info = VideoStreamInfo::builder()
444 .index(0)
445 .codec(VideoCodec::H265)
446 .codec_name("hevc")
447 .width(3840)
448 .height(2160)
449 .frame_rate(Rational::new(60, 1))
450 .pixel_format(PixelFormat::Yuv420p10le)
451 .duration(Duration::from_secs(120))
452 .bitrate(50_000_000)
453 .frame_count(7200)
454 .color_space(ColorSpace::Bt2020)
455 .color_range(ColorRange::Full)
456 .color_primaries(ColorPrimaries::Bt2020)
457 .build();
458
459 assert_eq!(info.codec(), VideoCodec::H265);
460 assert_eq!(info.width(), 3840);
461 assert_eq!(info.height(), 2160);
462 assert_eq!(info.duration(), Some(Duration::from_secs(120)));
463 assert_eq!(info.bitrate(), Some(50_000_000));
464 assert_eq!(info.frame_count(), Some(7200));
465 assert_eq!(info.color_space(), ColorSpace::Bt2020);
466 assert_eq!(info.color_range(), ColorRange::Full);
467 assert_eq!(info.color_primaries(), ColorPrimaries::Bt2020);
468 }
469
470 #[test]
471 fn test_default() {
472 let info = VideoStreamInfo::default();
473 assert_eq!(info.index(), 0);
474 assert_eq!(info.codec(), VideoCodec::default());
475 assert_eq!(info.width(), 0);
476 assert_eq!(info.height(), 0);
477 assert!(info.duration().is_none());
478 }
479
480 #[test]
481 fn test_aspect_ratio() {
482 let info = VideoStreamInfo::builder().width(1920).height(1080).build();
483 assert!((info.aspect_ratio() - (16.0 / 9.0)).abs() < 0.01);
484
485 let info = VideoStreamInfo::builder().width(1280).height(720).build();
486 assert!((info.aspect_ratio() - (16.0 / 9.0)).abs() < 0.01);
487
488 let info = VideoStreamInfo::builder().width(1920).height(0).build();
490 assert_eq!(info.aspect_ratio(), 0.0);
491 }
492
493 #[test]
494 fn test_resolution_checks() {
495 let sd = VideoStreamInfo::builder().width(720).height(480).build();
497 assert!(!sd.is_hd());
498 assert!(!sd.is_full_hd());
499 assert!(!sd.is_4k());
500
501 let hd = VideoStreamInfo::builder().width(1280).height(720).build();
503 assert!(hd.is_hd());
504 assert!(!hd.is_full_hd());
505 assert!(!hd.is_4k());
506
507 let fhd = VideoStreamInfo::builder().width(1920).height(1080).build();
509 assert!(fhd.is_hd());
510 assert!(fhd.is_full_hd());
511 assert!(!fhd.is_4k());
512
513 let uhd = VideoStreamInfo::builder().width(3840).height(2160).build();
515 assert!(uhd.is_hd());
516 assert!(uhd.is_full_hd());
517 assert!(uhd.is_4k());
518 }
519
520 #[test]
521 fn test_is_hdr() {
522 let hdr = VideoStreamInfo::builder()
524 .width(3840)
525 .height(2160)
526 .color_primaries(ColorPrimaries::Bt2020)
527 .pixel_format(PixelFormat::Yuv420p10le)
528 .build();
529 assert!(hdr.is_hdr());
530
531 let hdr_p010 = VideoStreamInfo::builder()
533 .width(3840)
534 .height(2160)
535 .color_primaries(ColorPrimaries::Bt2020)
536 .pixel_format(PixelFormat::P010le)
537 .build();
538 assert!(hdr_p010.is_hdr());
539
540 let sdr_hd = VideoStreamInfo::builder()
542 .width(1920)
543 .height(1080)
544 .color_primaries(ColorPrimaries::Bt709)
545 .pixel_format(PixelFormat::Yuv420p)
546 .build();
547 assert!(!sdr_hd.is_hdr());
548
549 let wide_gamut_8bit = VideoStreamInfo::builder()
551 .width(3840)
552 .height(2160)
553 .color_primaries(ColorPrimaries::Bt2020)
554 .pixel_format(PixelFormat::Yuv420p) .build();
556 assert!(!wide_gamut_8bit.is_hdr());
557
558 let hd_10bit = VideoStreamInfo::builder()
560 .width(1920)
561 .height(1080)
562 .color_primaries(ColorPrimaries::Bt709)
563 .pixel_format(PixelFormat::Yuv420p10le)
564 .build();
565 assert!(!hd_10bit.is_hdr());
566
567 let default = VideoStreamInfo::default();
569 assert!(!default.is_hdr());
570 }
571
572 #[test]
573 fn test_debug() {
574 let info = VideoStreamInfo::builder()
575 .index(0)
576 .codec(VideoCodec::H264)
577 .width(1920)
578 .height(1080)
579 .build();
580 let debug = format!("{info:?}");
581 assert!(debug.contains("VideoStreamInfo"));
582 assert!(debug.contains("1920"));
583 assert!(debug.contains("1080"));
584 }
585
586 #[test]
587 fn test_clone() {
588 let info = VideoStreamInfo::builder()
589 .index(0)
590 .codec(VideoCodec::H264)
591 .codec_name("h264")
592 .width(1920)
593 .height(1080)
594 .build();
595 let cloned = info.clone();
596 assert_eq!(info.width(), cloned.width());
597 assert_eq!(info.height(), cloned.height());
598 assert_eq!(info.codec_name(), cloned.codec_name());
599 }
600}