use super::stream::{StreamCollection, StreamKind};
use revelo_util::Ztring;
pub fn fill_computed_fields(sc: &mut StreamCollection) {
fill_bits_pixel_frame(sc);
fill_compression_ratio(sc);
fill_format_profile_general(sc);
fill_frame_rate_mode_original(sc);
fill_bitrate_ranges(sc);
fill_inferred_av1_level(sc);
}
fn field_val(sc: &StreamCollection, kind: StreamKind, pos: usize, key: &str) -> Option<String> {
sc.stream(kind, pos).and_then(|s| s.get(key)).map(|z| z.as_str().to_string())
}
fn fill_bits_pixel_frame(sc: &mut StreamCollection) {
let n = sc.stream_count(StreamKind::Video);
for i in 0..n {
let w: f64 = field_val(sc, StreamKind::Video, i, "Width")
.and_then(|v| v.parse().ok())
.unwrap_or(0.0);
let h: f64 = field_val(sc, StreamKind::Video, i, "Height")
.and_then(|v| v.parse().ok())
.unwrap_or(0.0);
let fr: f64 = field_val(sc, StreamKind::Video, i, "FrameRate")
.and_then(|v| v.parse().ok())
.unwrap_or(0.0);
let br: f64 = field_val(sc, StreamKind::Video, i, "BitRate")
.and_then(|v| v.parse().ok())
.unwrap_or(0.0);
if w > 0.0 && h > 0.0 && fr > 0.0 && br > 0.0 {
let bpp = br / (w * h * fr);
sc.set_field(
StreamKind::Video,
i,
"Bits_Pixel_Frame",
Ztring::from(format!("{:.3}", bpp)),
);
}
}
}
fn fill_compression_ratio(sc: &mut StreamCollection) {
for kind in &[StreamKind::Video, StreamKind::Audio] {
let n = sc.stream_count(*kind);
for i in 0..n {
let stream_size: u64 =
field_val(sc, *kind, i, "StreamSize").and_then(|v| v.parse().ok()).unwrap_or(0);
let dur_s: f64 =
field_val(sc, *kind, i, "Duration").and_then(|v| v.parse().ok()).unwrap_or(0.0);
let channels: u64 =
field_val(sc, *kind, i, "Channels").and_then(|v| v.parse().ok()).unwrap_or(1);
let bit_depth: u64 =
field_val(sc, *kind, i, "BitDepth").and_then(|v| v.parse().ok()).unwrap_or(8);
let sr: u64 =
field_val(sc, *kind, i, "SamplingRate").and_then(|v| v.parse().ok()).unwrap_or(0);
let is_audio = matches!(kind, StreamKind::Audio);
let uncompressed: u64 = if is_audio && sr > 0 && dur_s > 0.0 {
(channels * sr * bit_depth * (dur_s * 1000.0) as u64) / 8 / 1000
} else if dur_s > 0.0 {
let br: u64 =
field_val(sc, *kind, i, "BitRate").and_then(|v| v.parse().ok()).unwrap_or(0);
if br > 0 { (br * (dur_s * 1000.0) as u64) / 8 / 1000 } else { 0 }
} else {
0
};
if stream_size > 0 && uncompressed > 0 {
let ratio = uncompressed as f64 / stream_size as f64;
sc.set_field(*kind, i, "Compression_Ratio", Ztring::from(format!("{:.3}", ratio)));
}
}
}
}
fn fill_bitrate_ranges(sc: &mut StreamCollection) {
let mut overall_max: u64 = 0;
let mut overall_min: u64 = u64::MAX;
for kind in &[StreamKind::Audio, StreamKind::Video] {
let n = sc.stream_count(*kind);
for i in 0..n {
if let Some(max_str) = field_val(sc, *kind, i, "BitRate_Maximum")
&& let Ok(v) = max_str.parse::<u64>()
{
overall_max += v;
}
if let Some(br_str) = field_val(sc, *kind, i, "BitRate")
&& let Ok(v) = br_str.parse::<u64>()
&& v < overall_min
{
overall_min = v;
}
if field_val(sc, *kind, i, "BitRate_Minimum").is_none()
&& let Some(br_str) = field_val(sc, *kind, i, "BitRate")
&& let Ok(v) = br_str.parse::<u64>()
{
sc.set_field(*kind, i, "BitRate_Minimum", Ztring::from(format!("{}", v / 2)));
}
}
}
if overall_max > 0 && field_val(sc, StreamKind::General, 0, "OverallBitRate_Maximum").is_none()
{
sc.set_field(
StreamKind::General,
0,
"OverallBitRate_Maximum",
Ztring::from(format!("{}", overall_max)),
);
}
if overall_min < u64::MAX
&& field_val(sc, StreamKind::General, 0, "OverallBitRate_Minimum").is_none()
{
sc.set_field(
StreamKind::General,
0,
"OverallBitRate_Minimum",
Ztring::from(format!("{}", overall_min)),
);
}
}
fn fill_format_profile_general(sc: &mut StreamCollection) {
let format = field_val(sc, StreamKind::General, 0, "Format").unwrap_or_default();
let kind = field_val(sc, StreamKind::General, 0, "CodecID").unwrap_or_default();
if format == "MPEG-4" {
let profile = match kind.as_str() {
"mp42" => Some("Base Media / Version 2"),
"isom" => Some("Base Media / Version 1"),
"avc1" => Some("Base Media"),
_ => None,
};
if let Some(p) = profile {
sc.set_field(StreamKind::General, 0, "Format_Profile", Ztring::from(p));
}
}
}
fn fill_frame_rate_mode_original(sc: &mut StreamCollection) {
let n = sc.stream_count(StreamKind::Video);
for i in 0..n {
if let Some(mode) = field_val(sc, StreamKind::Video, i, "FrameRate_Mode") {
sc.set_field(StreamKind::Video, i, "FrameRate_Mode_Original", Ztring::from(mode));
}
}
}
fn fill_inferred_av1_level(sc: &mut StreamCollection) {
let n = sc.stream_count(StreamKind::Video);
for i in 0..n {
let format = field_val(sc, StreamKind::Video, i, "Format").unwrap_or_default();
if format != "AV1" {
continue;
}
let Some(profile_str) = field_val(sc, StreamKind::Video, i, "Format_Profile") else {
continue;
};
let width: u64 =
match field_val(sc, StreamKind::Video, i, "Width").and_then(|v| v.parse().ok()) {
Some(w) => w,
None => continue,
};
let height: u64 =
match field_val(sc, StreamKind::Video, i, "Height").and_then(|v| v.parse().ok()) {
Some(h) => h,
None => continue,
};
let frame_rate: f64 =
match field_val(sc, StreamKind::Video, i, "FrameRate").and_then(|v| v.parse().ok()) {
Some(f) => f,
None => continue,
};
let max_dim = width.max(height);
let inferred_level_idx = av1_inferred_level_idx(max_dim, frame_rate);
if let Some(inferred) = inferred_level_idx {
let reported_level_idx =
profile_str.split("@L").nth(1).and_then(|s| s.parse::<u8>().ok());
if reported_level_idx != Some(inferred) {
let level_name = av1_level_name(inferred);
sc.set_field(
StreamKind::Video,
i,
"Format_Level_Inferred",
Ztring::from(level_name),
);
}
}
}
}
fn av1_inferred_level_idx(max_dim: u64, fps: f64) -> Option<u8> {
let levels: &[(u8, u64, u64)] = &[
(0, 147_456, 4_915_200), (1, 262_144, 8_847_360), (2, 491_520, 16_515_072), (3, 1_048_576, 35_389_440), (4, 2_088_960, 69_632_000), (5, 2_088_960, 139_264_000), (6, 8_912_896, 297_096_000), (7, 8_912_896, 594_192_000), (8, 8_912_896, 1_188_384_000), (9, 8_912_896, 1_980_640_000), (10, 35_651_584, 1_188_384_000), (11, 35_651_584, 2_376_768_000), (12, 35_651_584, 4_753_536_000), (13, 35_651_584, 7_922_560_000), ];
let w16 = ((max_dim + 15) / 16) * 16;
let h16 = (((max_dim as f64 * 9.0 / 16.0).ceil() as u64 + 15) / 16) * 16;
let pic_size = w16 * h16;
let samples_per_sec = (pic_size as f64 * fps) as u64;
for &(idx, max_pic, max_sps) in levels {
if pic_size <= max_pic && samples_per_sec <= max_sps {
return Some(idx);
}
}
let pic_size = (max_dim as f64 * max_dim as f64 * 9.0 / 16.0).round() as u64;
let samples_per_sec = pic_size as f64 * fps;
for &(idx, max_pic, max_sps) in levels {
if pic_size <= max_pic && (samples_per_sec as u64) <= max_sps {
return Some(idx);
}
}
None
}
fn av1_level_name(idx: u8) -> &'static str {
match idx {
0 => "2.0",
1 => "2.1",
2 => "3.0",
3 => "3.1",
4 => "4.0",
5 => "4.1",
6 => "5.0",
7 => "5.1",
8 => "5.2",
9 => "5.3",
10 => "6.0",
11 => "6.1",
12 => "6.2",
13 => "6.3",
_ => "",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bpp() {
let mut sc = StreamCollection::new();
sc.stream_prepare(StreamKind::Video);
sc.set_field(StreamKind::Video, 0, "Width", Ztring::from("1920"));
sc.set_field(StreamKind::Video, 0, "Height", Ztring::from("1080"));
sc.set_field(StreamKind::Video, 0, "FrameRate", Ztring::from("25.000"));
sc.set_field(StreamKind::Video, 0, "BitRate", Ztring::from("5000000"));
fill_computed_fields(&mut sc);
assert_eq!(field_val(&sc, StreamKind::Video, 0, "Bits_Pixel_Frame").unwrap(), "0.096");
}
#[test]
fn test_compression_ratio() {
let mut sc = StreamCollection::new();
sc.stream_prepare(StreamKind::Audio);
sc.set_field(StreamKind::Audio, 0, "StreamSize", Ztring::from("1000"));
sc.set_field(StreamKind::Audio, 0, "Duration", Ztring::from("1.000"));
sc.set_field(StreamKind::Audio, 0, "Channels", Ztring::from("2"));
sc.set_field(StreamKind::Audio, 0, "BitDepth", Ztring::from("16"));
sc.set_field(StreamKind::Audio, 0, "SamplingRate", Ztring::from("44100"));
fill_computed_fields(&mut sc);
assert!(field_val(&sc, StreamKind::Audio, 0, "Compression_Ratio").is_some());
}
#[test]
fn test_format_profile_general() {
let mut sc = StreamCollection::new();
sc.stream_prepare(StreamKind::General);
sc.set_field(StreamKind::General, 0, "Format", Ztring::from("MPEG-4"));
sc.set_field(StreamKind::General, 0, "CodecID", Ztring::from("mp42"));
fill_computed_fields(&mut sc);
assert_eq!(
field_val(&sc, StreamKind::General, 0, "Format_Profile").unwrap(),
"Base Media / Version 2"
);
}
}