use std::time::Duration;
use crate::codec::VideoCodec;
use crate::color::{ColorPrimaries, ColorRange, ColorSpace};
use crate::pixel::PixelFormat;
use crate::time::Rational;
#[derive(Debug, Clone)]
pub struct VideoStreamInfo {
index: u32,
codec: VideoCodec,
codec_name: String,
width: u32,
height: u32,
pixel_format: PixelFormat,
frame_rate: Rational,
duration: Option<Duration>,
bitrate: Option<u64>,
frame_count: Option<u64>,
color_space: ColorSpace,
color_range: ColorRange,
color_primaries: ColorPrimaries,
}
impl VideoStreamInfo {
#[must_use]
pub fn builder() -> VideoStreamInfoBuilder {
VideoStreamInfoBuilder::default()
}
#[must_use]
#[inline]
pub const fn index(&self) -> u32 {
self.index
}
#[must_use]
#[inline]
pub const fn codec(&self) -> VideoCodec {
self.codec
}
#[must_use]
#[inline]
pub fn codec_name(&self) -> &str {
&self.codec_name
}
#[must_use]
#[inline]
pub const fn width(&self) -> u32 {
self.width
}
#[must_use]
#[inline]
pub const fn height(&self) -> u32 {
self.height
}
#[must_use]
#[inline]
pub const fn pixel_format(&self) -> PixelFormat {
self.pixel_format
}
#[must_use]
#[inline]
pub const fn frame_rate(&self) -> Rational {
self.frame_rate
}
#[must_use]
#[inline]
pub fn fps(&self) -> f64 {
self.frame_rate.as_f64()
}
#[must_use]
#[inline]
pub const fn duration(&self) -> Option<Duration> {
self.duration
}
#[must_use]
#[inline]
pub const fn bitrate(&self) -> Option<u64> {
self.bitrate
}
#[must_use]
#[inline]
pub const fn frame_count(&self) -> Option<u64> {
self.frame_count
}
#[must_use]
#[inline]
pub const fn color_space(&self) -> ColorSpace {
self.color_space
}
#[must_use]
#[inline]
pub const fn color_range(&self) -> ColorRange {
self.color_range
}
#[must_use]
#[inline]
pub const fn color_primaries(&self) -> ColorPrimaries {
self.color_primaries
}
#[must_use]
#[inline]
pub fn aspect_ratio(&self) -> f64 {
if self.height == 0 {
log::warn!(
"aspect_ratio unavailable, height is 0, returning 0.0 \
width={} height=0 fallback=0.0",
self.width
);
0.0
} else {
f64::from(self.width) / f64::from(self.height)
}
}
#[must_use]
#[inline]
pub const fn is_hd(&self) -> bool {
self.height >= 720
}
#[must_use]
#[inline]
pub const fn is_full_hd(&self) -> bool {
self.height >= 1080
}
#[must_use]
#[inline]
pub const fn is_4k(&self) -> bool {
self.height >= 2160
}
#[must_use]
#[inline]
pub fn is_hdr(&self) -> bool {
self.color_primaries.is_wide_gamut() && self.pixel_format.is_high_bit_depth()
}
}
impl Default for VideoStreamInfo {
fn default() -> Self {
Self {
index: 0,
codec: VideoCodec::default(),
codec_name: String::new(),
width: 0,
height: 0,
pixel_format: PixelFormat::default(),
frame_rate: Rational::new(30, 1),
duration: None,
bitrate: None,
frame_count: None,
color_space: ColorSpace::default(),
color_range: ColorRange::default(),
color_primaries: ColorPrimaries::default(),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct VideoStreamInfoBuilder {
index: u32,
codec: VideoCodec,
codec_name: String,
width: u32,
height: u32,
pixel_format: PixelFormat,
frame_rate: Rational,
duration: Option<Duration>,
bitrate: Option<u64>,
frame_count: Option<u64>,
color_space: ColorSpace,
color_range: ColorRange,
color_primaries: ColorPrimaries,
}
impl VideoStreamInfoBuilder {
#[must_use]
pub fn index(mut self, index: u32) -> Self {
self.index = index;
self
}
#[must_use]
pub fn codec(mut self, codec: VideoCodec) -> Self {
self.codec = codec;
self
}
#[must_use]
pub fn codec_name(mut self, name: impl Into<String>) -> Self {
self.codec_name = name.into();
self
}
#[must_use]
pub fn width(mut self, width: u32) -> Self {
self.width = width;
self
}
#[must_use]
pub fn height(mut self, height: u32) -> Self {
self.height = height;
self
}
#[must_use]
pub fn pixel_format(mut self, format: PixelFormat) -> Self {
self.pixel_format = format;
self
}
#[must_use]
pub fn frame_rate(mut self, rate: Rational) -> Self {
self.frame_rate = rate;
self
}
#[must_use]
pub fn duration(mut self, duration: Duration) -> Self {
self.duration = Some(duration);
self
}
#[must_use]
pub fn bitrate(mut self, bitrate: u64) -> Self {
self.bitrate = Some(bitrate);
self
}
#[must_use]
pub fn frame_count(mut self, count: u64) -> Self {
self.frame_count = Some(count);
self
}
#[must_use]
pub fn color_space(mut self, space: ColorSpace) -> Self {
self.color_space = space;
self
}
#[must_use]
pub fn color_range(mut self, range: ColorRange) -> Self {
self.color_range = range;
self
}
#[must_use]
pub fn color_primaries(mut self, primaries: ColorPrimaries) -> Self {
self.color_primaries = primaries;
self
}
#[must_use]
pub fn build(self) -> VideoStreamInfo {
VideoStreamInfo {
index: self.index,
codec: self.codec,
codec_name: self.codec_name,
width: self.width,
height: self.height,
pixel_format: self.pixel_format,
frame_rate: self.frame_rate,
duration: self.duration,
bitrate: self.bitrate,
frame_count: self.frame_count,
color_space: self.color_space,
color_range: self.color_range,
color_primaries: self.color_primaries,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_builder_basic() {
let info = VideoStreamInfo::builder()
.index(0)
.codec(VideoCodec::H264)
.codec_name("h264")
.width(1920)
.height(1080)
.frame_rate(Rational::new(30, 1))
.pixel_format(PixelFormat::Yuv420p)
.build();
assert_eq!(info.index(), 0);
assert_eq!(info.codec(), VideoCodec::H264);
assert_eq!(info.codec_name(), "h264");
assert_eq!(info.width(), 1920);
assert_eq!(info.height(), 1080);
assert!((info.fps() - 30.0).abs() < 0.001);
assert_eq!(info.pixel_format(), PixelFormat::Yuv420p);
}
#[test]
fn test_builder_full() {
let info = VideoStreamInfo::builder()
.index(0)
.codec(VideoCodec::H265)
.codec_name("hevc")
.width(3840)
.height(2160)
.frame_rate(Rational::new(60, 1))
.pixel_format(PixelFormat::Yuv420p10le)
.duration(Duration::from_secs(120))
.bitrate(50_000_000)
.frame_count(7200)
.color_space(ColorSpace::Bt2020)
.color_range(ColorRange::Full)
.color_primaries(ColorPrimaries::Bt2020)
.build();
assert_eq!(info.codec(), VideoCodec::H265);
assert_eq!(info.width(), 3840);
assert_eq!(info.height(), 2160);
assert_eq!(info.duration(), Some(Duration::from_secs(120)));
assert_eq!(info.bitrate(), Some(50_000_000));
assert_eq!(info.frame_count(), Some(7200));
assert_eq!(info.color_space(), ColorSpace::Bt2020);
assert_eq!(info.color_range(), ColorRange::Full);
assert_eq!(info.color_primaries(), ColorPrimaries::Bt2020);
}
#[test]
fn test_default() {
let info = VideoStreamInfo::default();
assert_eq!(info.index(), 0);
assert_eq!(info.codec(), VideoCodec::default());
assert_eq!(info.width(), 0);
assert_eq!(info.height(), 0);
assert!(info.duration().is_none());
}
#[test]
fn test_aspect_ratio() {
let info = VideoStreamInfo::builder().width(1920).height(1080).build();
assert!((info.aspect_ratio() - (16.0 / 9.0)).abs() < 0.01);
let info = VideoStreamInfo::builder().width(1280).height(720).build();
assert!((info.aspect_ratio() - (16.0 / 9.0)).abs() < 0.01);
let info = VideoStreamInfo::builder().width(1920).height(0).build();
assert_eq!(info.aspect_ratio(), 0.0);
}
#[test]
fn test_resolution_checks() {
let sd = VideoStreamInfo::builder().width(720).height(480).build();
assert!(!sd.is_hd());
assert!(!sd.is_full_hd());
assert!(!sd.is_4k());
let hd = VideoStreamInfo::builder().width(1280).height(720).build();
assert!(hd.is_hd());
assert!(!hd.is_full_hd());
assert!(!hd.is_4k());
let fhd = VideoStreamInfo::builder().width(1920).height(1080).build();
assert!(fhd.is_hd());
assert!(fhd.is_full_hd());
assert!(!fhd.is_4k());
let uhd = VideoStreamInfo::builder().width(3840).height(2160).build();
assert!(uhd.is_hd());
assert!(uhd.is_full_hd());
assert!(uhd.is_4k());
}
#[test]
fn test_is_hdr() {
let hdr = VideoStreamInfo::builder()
.width(3840)
.height(2160)
.color_primaries(ColorPrimaries::Bt2020)
.pixel_format(PixelFormat::Yuv420p10le)
.build();
assert!(hdr.is_hdr());
let hdr_p010 = VideoStreamInfo::builder()
.width(3840)
.height(2160)
.color_primaries(ColorPrimaries::Bt2020)
.pixel_format(PixelFormat::P010le)
.build();
assert!(hdr_p010.is_hdr());
let sdr_hd = VideoStreamInfo::builder()
.width(1920)
.height(1080)
.color_primaries(ColorPrimaries::Bt709)
.pixel_format(PixelFormat::Yuv420p)
.build();
assert!(!sdr_hd.is_hdr());
let wide_gamut_8bit = VideoStreamInfo::builder()
.width(3840)
.height(2160)
.color_primaries(ColorPrimaries::Bt2020)
.pixel_format(PixelFormat::Yuv420p) .build();
assert!(!wide_gamut_8bit.is_hdr());
let hd_10bit = VideoStreamInfo::builder()
.width(1920)
.height(1080)
.color_primaries(ColorPrimaries::Bt709)
.pixel_format(PixelFormat::Yuv420p10le)
.build();
assert!(!hd_10bit.is_hdr());
let default = VideoStreamInfo::default();
assert!(!default.is_hdr());
}
#[test]
fn test_debug() {
let info = VideoStreamInfo::builder()
.index(0)
.codec(VideoCodec::H264)
.width(1920)
.height(1080)
.build();
let debug = format!("{info:?}");
assert!(debug.contains("VideoStreamInfo"));
assert!(debug.contains("1920"));
assert!(debug.contains("1080"));
}
#[test]
fn test_clone() {
let info = VideoStreamInfo::builder()
.index(0)
.codec(VideoCodec::H264)
.codec_name("h264")
.width(1920)
.height(1080)
.build();
let cloned = info.clone();
assert_eq!(info.width(), cloned.width());
assert_eq!(info.height(), cloned.height());
assert_eq!(info.codec_name(), cloned.codec_name());
}
}