use std::{fmt, str::FromStr};
use serde::{Deserialize, Serialize};
use crate::Error;
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct H265 {
pub in_band: bool,
pub profile_space: u8,
pub profile_idc: u8,
pub profile_compatibility_flags: [u8; 4],
pub tier_flag: bool,
pub level_idc: u8,
pub constraint_flags: [u8; 6],
}
impl fmt::Display for H265 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let compatibility = self
.profile_compatibility_flags
.iter()
.rev()
.skip_while(|b| **b == 0)
.map(|b| format!("{b:X}"))
.collect::<Vec<_>>()
.join("");
let skip = self.constraint_flags.iter().rev().skip_while(|b| **b == 0).count();
let constraints = self
.constraint_flags
.iter()
.take(skip)
.map(|b| format!("{b:X}"))
.collect::<Vec<_>>()
.join(".");
write!(
f,
"{}.{}{}.{}.{}{}.{}",
match self.in_band {
true => "hev1",
false => "hvc1",
},
match self.profile_space {
0 => "".to_string(),
n => (b'A'.saturating_add(n).saturating_sub(1) as char).to_string(),
},
self.profile_idc,
compatibility,
match self.tier_flag {
true => "H",
false => "L",
},
self.level_idc,
constraints,
)
}
}
impl FromStr for H265 {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut parts = s.split('.');
let in_band = match parts.next() {
Some("hev1") => true,
Some("hvc1") => false,
_ => return Err(Error::InvalidCodec),
};
let profile = parts.next().ok_or(Error::InvalidCodec)?;
let profile_space = match profile.as_bytes().first().ok_or(Error::InvalidCodec)? {
b'A'..=b'Z' => 1 + profile.as_bytes()[0] - b'A',
_ => 0,
};
let profile_idc = (if profile_space > 0 { &profile[1..] } else { profile }).parse::<u8>()?;
let compatibility = parts.next().ok_or(Error::InvalidCodec)?;
let profile_compatibility_flags = u32::from_str_radix(compatibility, 16)?.to_le_bytes();
let level = parts.next().ok_or(Error::InvalidCodec)?;
let tier_flag = match level.as_bytes().first() {
Some(b'H') => true,
Some(b'L') => false,
_ => return Err(Error::InvalidCodec),
};
let level_idc = level[1..].parse::<u8>()?;
let mut constraint_flags = [0u8; 6];
let parts = parts.enumerate();
for (i, constraint) in parts {
if i >= 6 {
return Err(Error::InvalidCodec);
}
constraint_flags[i] = u8::from_str_radix(constraint, 16)?;
}
Ok(Self {
in_band,
profile_space,
profile_idc,
profile_compatibility_flags,
tier_flag,
level_idc,
constraint_flags,
})
}
}
#[cfg(test)]
mod tests {
use crate::catalog::VideoCodec;
use super::*;
#[test]
fn test_h265() {
let encoded = "hev1.1.6.L93.B0";
let decoded = H265 {
in_band: true,
profile_space: 0,
profile_idc: 1,
profile_compatibility_flags: [0x6, 0, 0, 0],
tier_flag: false,
level_idc: 93,
constraint_flags: [0xB0, 0, 0, 0, 0, 0],
}
.into();
let output = VideoCodec::from_str(encoded).expect("failed to parse");
assert_eq!(output, decoded);
let output = decoded.to_string();
assert_eq!(output, encoded);
}
#[test]
fn test_h265_long() {
let encoded = "hev1.A4.41.H120.B0.23";
let decoded = H265 {
in_band: true,
profile_space: 1,
profile_idc: 4,
profile_compatibility_flags: [0x41, 0, 0, 0],
tier_flag: true,
level_idc: 120,
constraint_flags: [0xB0, 0x23, 0, 0, 0, 0],
};
let output = H265::from_str(encoded).expect("failed to parse");
assert_eq!(output, decoded);
let output = decoded.to_string();
assert_eq!(output, encoded);
}
#[test]
fn test_h265_out_of_band() {
let encoded = "hvc1.A1.60.H93.B0";
let output = H265::from_str(encoded).unwrap();
assert!(!output.in_band);
let output = output.to_string();
assert_eq!(output, encoded);
}
}