use std::io::{self, Read};
use crate::wind_map::GridLayout;
use crate::{TimedWindMap, WeatherRow, WindMap};
use super::ivf::IvfReader;
use super::rav1d_wrap::{Decoder, DecoderError};
use super::{
HEADER_BYTES_V1, HEADER_BYTES_V2, MAGIC, UNKNOWN_TIME_SENTINEL, dequantize, uv_to_sample,
};
#[derive(Debug)]
#[non_exhaustive]
pub enum DecodeError {
BadMagic,
UnsupportedVersion(u32),
BadDimensions {
nx: u32,
ny: u32,
frame_count: u32,
},
BadStep {
step_lon: f32,
step_lat: f32,
},
Truncated {
expected: u32,
got: u32,
},
DimensionMismatch {
header_nx: usize,
header_ny: usize,
pic_w: usize,
pic_h: usize,
},
Av1(DecoderError),
Io(io::Error),
}
impl std::fmt::Display for DecodeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BadMagic => f.write_str("not a wind_av1 file (bad magic)"),
Self::UnsupportedVersion(v) => write!(f, "unsupported wind_av1 version: {v}"),
Self::BadDimensions {
nx,
ny,
frame_count,
} => {
write!(
f,
"invalid grid dimensions: nx={nx}, ny={ny}, frame_count={frame_count}"
)
}
Self::BadStep { step_lon, step_lat } => {
write!(
f,
"invalid grid step: step_lon={step_lon}, step_lat={step_lat}"
)
}
Self::Truncated { expected, got } => {
write!(
f,
"IVF stream truncated: expected {expected} frames, decoded {got}"
)
}
Self::DimensionMismatch {
header_nx,
header_ny,
pic_w,
pic_h,
} => {
write!(
f,
"AV1 picture {pic_w}x{pic_h} doesn't match header grid {header_nx}x{header_ny}",
)
}
Self::Av1(e) => write!(f, "AV1 decode: {e}"),
Self::Io(e) => write!(f, "I/O error: {e}"),
}
}
}
impl std::error::Error for DecodeError {}
impl From<io::Error> for DecodeError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
impl From<DecoderError> for DecodeError {
fn from(e: DecoderError) -> Self {
Self::Av1(e)
}
}
pub fn decode<R: Read>(reader: R) -> Result<TimedWindMap, DecodeError> {
let mut reader = reader;
let header = read_header(&mut reader)?;
let layout = GridLayout {
origin_x: header.origin_lon,
origin_y: header.origin_lat,
step_x: header.step_lon,
step_y: header.step_lat,
nx: header.nx as usize,
ny: header.ny as usize,
};
let mut ivf = IvfReader::open(reader)?;
let mut av1 = Decoder::new()?;
let mut frames: Vec<WindMap> = Vec::with_capacity(header.frame_count as usize);
while let Some(packet) = ivf.read_frame()? {
let mut accepted = av1.feed(&packet)?;
while !accepted {
match av1.next_picture()? {
Some(pic) => collect_picture(&pic, &layout, &mut frames)?,
None => break,
}
accepted = av1.feed(&packet)?;
}
while let Some(pic) = av1.next_picture()? {
collect_picture(&pic, &layout, &mut frames)?;
}
}
while let Some(pic) = av1.next_picture()? {
collect_picture(&pic, &layout, &mut frames)?;
}
if frames.len() as u32 != header.frame_count {
return Err(DecodeError::Truncated {
expected: header.frame_count,
got: frames.len() as u32,
});
}
let mut map = TimedWindMap::new(frames, header.step_seconds);
if let Some((s, e)) = header.time_range {
map = map.with_time_range(s, e);
}
Ok(map)
}
struct Header {
origin_lon: f32,
origin_lat: f32,
step_lon: f32,
step_lat: f32,
nx: u32,
ny: u32,
frame_count: u32,
step_seconds: f32,
time_range: Option<(chrono::DateTime<chrono::Utc>, chrono::DateTime<chrono::Utc>)>,
}
fn read_u32_le(buf: &[u8], offset: usize) -> u32 {
u32::from_le_bytes(
buf[offset..offset + 4]
.try_into()
.expect("slice is exactly 4 bytes"),
)
}
fn read_f32_le(buf: &[u8], offset: usize) -> f32 {
f32::from_le_bytes(
buf[offset..offset + 4]
.try_into()
.expect("slice is exactly 4 bytes"),
)
}
fn read_i64_le(buf: &[u8], offset: usize) -> i64 {
i64::from_le_bytes(
buf[offset..offset + 8]
.try_into()
.expect("slice is exactly 8 bytes"),
)
}
fn read_header<R: Read>(reader: &mut R) -> Result<Header, DecodeError> {
let mut buf = [0u8; HEADER_BYTES_V2];
reader.read_exact(&mut buf[..HEADER_BYTES_V1])?;
if buf[0..8] != MAGIC {
return Err(DecodeError::BadMagic);
}
let version = read_u32_le(&buf, 8);
let time_range = match version {
1 => None,
2 => {
reader.read_exact(&mut buf[HEADER_BYTES_V1..HEADER_BYTES_V2])?;
let start_unix = read_i64_le(&buf, 44);
let end_unix = read_i64_le(&buf, 52);
decode_time_range(start_unix, end_unix)
}
_ => return Err(DecodeError::UnsupportedVersion(version)),
};
let origin_lon = read_f32_le(&buf, 12);
let origin_lat = read_f32_le(&buf, 16);
let step_lon = read_f32_le(&buf, 20);
let step_lat = read_f32_le(&buf, 24);
let nx = read_u32_le(&buf, 28);
let ny = read_u32_le(&buf, 32);
let frame_count = read_u32_le(&buf, 36);
let step_seconds = read_f32_le(&buf, 40);
if nx < 2 || ny < 2 || frame_count == 0 {
return Err(DecodeError::BadDimensions {
nx,
ny,
frame_count,
});
}
if !step_lon.is_finite() || !step_lat.is_finite() || step_lon <= 0.0 || step_lat <= 0.0 {
return Err(DecodeError::BadStep { step_lon, step_lat });
}
Ok(Header {
origin_lon,
origin_lat,
step_lon,
step_lat,
nx,
ny,
frame_count,
step_seconds,
time_range,
})
}
fn decode_time_range(
start_unix: i64,
end_unix: i64,
) -> Option<(chrono::DateTime<chrono::Utc>, chrono::DateTime<chrono::Utc>)> {
if start_unix == UNKNOWN_TIME_SENTINEL || end_unix == UNKNOWN_TIME_SENTINEL {
return None;
}
let start = chrono::DateTime::<chrono::Utc>::from_timestamp(start_unix, 0)?;
let end = chrono::DateTime::<chrono::Utc>::from_timestamp(end_unix, 0)?;
Some((start, end))
}
fn collect_picture(
pic: &super::rav1d_wrap::DecodedPicture,
layout: &GridLayout,
frames: &mut Vec<WindMap>,
) -> Result<(), DecodeError> {
pic.check_format()?;
let w = pic.width();
let h = pic.height();
if w != layout.nx || h != layout.ny {
return Err(DecodeError::DimensionMismatch {
header_nx: layout.nx,
header_ny: layout.ny,
pic_w: w,
pic_h: h,
});
}
let mut rows = Vec::with_capacity(w * h);
for i in 0..layout.nx {
let lon = layout.origin_x + (i as f32) * layout.step_x;
for j in 0..layout.ny {
let lat = layout.origin_y + (j as f32) * layout.step_y;
rows.push(WeatherRow {
lon,
lat,
sample: crate::WindSample {
speed: 0.0,
direction: 0.0,
},
});
}
}
for j_screen in 0..h {
let j_src = layout.ny - 1 - j_screen;
let y_row = pic.plane_row(0, j_screen);
let cb_row = pic.plane_row(1, j_screen);
for i in 0..w {
let u = dequantize(y_row[i]);
let v = dequantize(cb_row[i]);
let cell_idx = i * layout.ny + j_src;
rows[cell_idx].sample = uv_to_sample(u, v);
}
}
frames.push(WindMap::from_grid(rows, *layout));
Ok(())
}