use anyhow::{anyhow, Result};
use serde_json::Value;
use std::io::Write;
use std::path::Path;
pub struct Decoder {
inner: motioncam_decoder::Decoder,
}
#[derive(Debug, Clone)]
pub struct ContainerMetadata {
pub color_matrix1: [f32; 9],
pub color_matrix2: [f32; 9],
pub forward_matrix1: [f32; 9],
pub forward_matrix2: [f32; 9],
pub calibration_matrix1: [f32; 9],
pub calibration_matrix2: [f32; 9],
pub calibration_illuminant1: i32,
pub calibration_illuminant2: i32,
pub has_calibration_illuminants: bool,
pub white_level: f64,
pub black_level: [f64; 4],
pub black_level_count: i32,
pub audio_sample_rate_hz: i32,
pub num_audio_channels: i32,
}
#[derive(Debug, Clone)]
pub struct FrameMetadata {
pub width: u32,
pub height: u32,
pub timestamp_ns: i64,
pub as_shot_neutral: [f32; 3],
pub exposure_time: f64,
pub iso: f32,
pub focal_length: f32,
pub aperture: f32,
}
fn json_to_matrix9(val: &Value, key: &str) -> [f32; 9] {
let mut result = [0.0f32; 9];
if let Some(arr) = val.get(key).and_then(|v| v.as_array()) {
for (i, item) in arr.iter().enumerate().take(9) {
if let Some(n) = item.as_f64() {
result[i] = n as f32;
}
}
}
result
}
fn json_to_black_level(val: &Value) -> ([f64; 4], i32) {
let mut result = [0.0f64; 4];
let mut count = 0i32;
if let Some(arr) = val.get("blackLevel").and_then(|v| v.as_array()) {
for (i, item) in arr.iter().enumerate().take(4) {
if let Some(n) = item.as_f64() {
result[i] = n;
count = (i + 1) as i32;
}
}
}
(result, count)
}
fn json_to_as_shot_neutral(val: &Value) -> [f32; 3] {
let mut result = [1.0f32; 3];
if let Some(arr) = val.get("asShotNeutral").and_then(|v| v.as_array()) {
for (i, item) in arr.iter().enumerate().take(3) {
if let Some(n) = item.as_f64() {
result[i] = n as f32;
}
}
}
result
}
impl Decoder {
pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_str = path.as_ref().to_string_lossy().to_string();
tracing::debug!("decoder::new: {}", path_str);
let inner = motioncam_decoder::Decoder::from_path(path)
.map_err(|e| {
tracing::error!("decoder failed to open {}: {}", path_str, e);
anyhow!("Failed to open decoder: {}", e)
})?;
tracing::debug!("decoder opened successfully: {}", path_str);
Ok(Self { inner })
}
pub fn container_metadata(&self) -> Result<ContainerMetadata> {
tracing::debug!("decoder::container_metadata");
let meta = self.inner.container_metadata();
let color_matrix1 = json_to_matrix9(meta, "colorMatrix1");
let color_matrix2 = json_to_matrix9(meta, "colorMatrix2");
let forward_matrix1 = json_to_matrix9(meta, "forwardMatrix1");
let forward_matrix2 = json_to_matrix9(meta, "forwardMatrix2");
let calibration_matrix1 = json_to_matrix9(meta, "calibrationMatrix1");
let calibration_matrix2 = json_to_matrix9(meta, "calibrationMatrix2");
let illuminant1 = meta.get("calibrationIlluminant1").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
let illuminant2 = meta.get("calibrationIlluminant2").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
let white_level = meta.get("whiteLevel").and_then(|v| v.as_f64()).unwrap_or(16383.0);
let (black_level, black_level_count) = json_to_black_level(meta);
let audio_sample_rate = meta
.get("extraData")
.and_then(|e| e.get("audioSampleRate"))
.and_then(|v| v.as_i64())
.unwrap_or(0) as i32;
let audio_channels = meta
.get("extraData")
.and_then(|e| e.get("audioChannels"))
.and_then(|v| v.as_i64())
.unwrap_or(0) as i32;
Ok(ContainerMetadata {
color_matrix1,
color_matrix2,
forward_matrix1,
forward_matrix2,
calibration_matrix1,
calibration_matrix2,
calibration_illuminant1: illuminant1,
calibration_illuminant2: illuminant2,
has_calibration_illuminants: illuminant1 != 0 || illuminant2 != 0,
white_level,
black_level,
black_level_count,
audio_sample_rate_hz: audio_sample_rate,
num_audio_channels: audio_channels,
})
}
pub fn timestamps(&self) -> Result<Vec<i64>> {
let ts = self.inner.frame_timestamps().collect::<Vec<_>>();
tracing::debug!("decoder::timestamps: {} frames", ts.len());
Ok(ts)
}
pub fn prefetch(&self, timestamp_ns: i64) {
self.inner.prefetch(timestamp_ns);
}
pub fn load_frame(&self, timestamp_ns: i64) -> Result<(Vec<u16>, FrameMetadata)> {
let (pixels, meta) = self.inner.load_frame(timestamp_ns)
.map_err(|e| {
tracing::error!("failed to decode frame at ns {}: {}", timestamp_ns, e);
anyhow!("Failed to decode frame at ns {}: {}", timestamp_ns, e)
})?;
let width = meta.get("width").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
let height = meta.get("height").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
let as_shot_neutral = json_to_as_shot_neutral(&meta);
let exposure_time = meta.get("exposureTime").and_then(|v| v.as_f64()).unwrap_or(0.0);
let iso = meta.get("iso").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
let focal_length = meta.get("focalLength").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
let aperture = meta.get("aperture").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
Ok((pixels, FrameMetadata {
width,
height,
timestamp_ns,
as_shot_neutral,
exposure_time,
iso,
focal_length,
aperture,
}))
}
pub fn load_frame_into(&self, timestamp_ns: i64, out: &mut [u16]) -> Result<[f32; 3]> {
self.inner.load_frame_into(timestamp_ns, out)
.map_err(|e| {
tracing::error!("failed to decode frame at ns {}: {}", timestamp_ns, e);
anyhow!("Failed to decode frame at ns {}: {}", timestamp_ns, e)
})
}
pub fn load_frame_metadata(&self, timestamp_ns: i64) -> Result<FrameMetadata> {
let meta = self.inner.load_frame_metadata(timestamp_ns)
.map_err(|e| anyhow!("Failed to get metadata for frame at ns {}: {}", timestamp_ns, e))?;
let width = meta.get("width").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
let height = meta.get("height").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
let as_shot_neutral = json_to_as_shot_neutral(&meta);
let exposure_time = meta.get("exposureTime").and_then(|v| v.as_f64()).unwrap_or(0.0);
let iso = meta.get("iso").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
let focal_length = meta.get("focalLength").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
let aperture = meta.get("aperture").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
Ok(FrameMetadata {
width,
height,
timestamp_ns,
as_shot_neutral,
exposure_time,
iso,
focal_length,
aperture,
})
}
pub fn write_audio_to<W: Write>(&self, writer: &mut W) -> Result<()> {
let chunks = self.inner.load_audio()
.map_err(|e| anyhow!("Failed to load audio: {}", e))?;
for chunk in chunks {
for &sample in &chunk.samples {
writer.write_all(&sample.to_le_bytes())?;
}
}
Ok(())
}
}