1use anyhow::{anyhow, Result};
2use serde_json::Value;
3use std::io::Write;
4use std::path::Path;
5
6pub struct Decoder {
7 inner: motioncam_decoder::Decoder,
8}
9
10#[derive(Debug, Clone)]
11pub struct LensShadingMap {
12 pub channels: Vec<Vec<f32>>,
13 pub width: u32,
14 pub height: u32,
15}
16
17#[derive(Debug, Clone)]
18pub struct ContainerMetadata {
19 pub color_matrix1: [f32; 9],
20 pub color_matrix2: [f32; 9],
21 pub forward_matrix1: [f32; 9],
22 pub forward_matrix2: [f32; 9],
23 pub calibration_matrix1: [f32; 9],
24 pub calibration_matrix2: [f32; 9],
25 pub calibration_illuminant1: i32,
26 pub calibration_illuminant2: i32,
27 pub has_calibration_illuminants: bool,
28 pub white_level: f64,
29 pub black_level: [f64; 4],
30 pub black_level_count: i32,
31 pub audio_sample_rate_hz: i32,
32 pub num_audio_channels: i32,
33 pub lens_shading_map: Option<LensShadingMap>,
34}
35
36#[derive(Debug, Clone)]
37pub struct FrameMetadata {
38 pub width: u32,
39 pub height: u32,
40 pub timestamp_ns: i64,
41 pub as_shot_neutral: [f32; 3],
42 pub exposure_time: f64,
43 pub iso: f32,
44 pub focal_length: f32,
45 pub aperture: f32,
46 pub dynamic_black_level: Option<[f32; 4]>,
47 pub dynamic_white_level: Option<f32>,
48 pub lens_shading_map: Option<LensShadingMap>,
49}
50
51fn json_to_matrix9(val: &Value, key: &str) -> [f32; 9] {
52 let mut result = [0.0f32; 9];
53 if let Some(arr) = val.get(key).and_then(|v| v.as_array()) {
54 for (i, item) in arr.iter().enumerate().take(9) {
55 if let Some(n) = item.as_f64() {
56 result[i] = n as f32;
57 }
58 }
59 }
60 result
61}
62
63fn json_to_black_level(val: &Value) -> ([f64; 4], i32) {
64 let mut result = [0.0f64; 4];
65 let mut count = 0i32;
66 if let Some(arr) = val.get("blackLevel").and_then(|v| v.as_array()) {
67 for (i, item) in arr.iter().enumerate().take(4) {
68 if let Some(n) = item.as_f64() {
69 result[i] = n;
70 count = (i + 1) as i32;
71 }
72 }
73 }
74 (result, count)
75}
76
77fn json_to_as_shot_neutral(val: &Value) -> [f32; 3] {
78 let mut result = [1.0f32; 3];
79 if let Some(arr) = val.get("asShotNeutral").and_then(|v| v.as_array()) {
80 for (i, item) in arr.iter().enumerate().take(3) {
81 if let Some(n) = item.as_f64() {
82 result[i] = n as f32;
83 }
84 }
85 }
86 result
87}
88
89fn json_to_lens_shading_map(val: &Value) -> Option<LensShadingMap> {
90 let map_arr = val.get("lensShadingMap").and_then(|v| v.as_array())?;
91 if map_arr.len() < 4 {
92 return None;
93 }
94 let width = val.get("lensShadingMapWidth").and_then(|v| v.as_u64())? as u32;
95 let height = val.get("lensShadingMapHeight").and_then(|v| v.as_u64())? as u32;
96 let expected_len = (width * height) as usize;
97 let channels: Vec<Vec<f32>> = map_arr.iter().take(4).filter_map(|ch| {
98 let arr = ch.as_array()?;
99 if arr.len() < expected_len {
100 return None;
101 }
102 Some(arr.iter().filter_map(|v| v.as_f64().map(|x| x as f32)).collect::<Vec<f32>>())
103 }).collect();
104 if channels.len() < 4 {
105 return None;
106 }
107 Some(LensShadingMap { channels, width, height })
108}
109
110fn json_to_dynamic_black_level(val: &Value) -> Option<[f32; 4]> {
111 let arr = val.get("dynamicBlackLevel").and_then(|v| v.as_array())?;
112 if arr.len() < 4 {
113 return None;
114 }
115 let mut result = [0.0f32; 4];
116 for (i, item) in arr.iter().enumerate().take(4) {
117 result[i] = item.as_f64()? as f32;
118 }
119 Some(result)
120}
121
122fn json_to_dynamic_white_level(val: &Value) -> Option<f32> {
123 val.get("dynamicWhiteLevel").and_then(|v| v.as_f64()).map(|v| v as f32)
124}
125
126impl Decoder {
127 pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
128 let path_str = path.as_ref().to_string_lossy().to_string();
129 tracing::debug!("decoder::new: {}", path_str);
130 let inner = motioncam_decoder::Decoder::from_path(path)
131 .map_err(|e| {
132 tracing::error!("decoder failed to open {}: {}", path_str, e);
133 anyhow!("Failed to open decoder: {}", e)
134 })?;
135 tracing::debug!("decoder opened successfully: {}", path_str);
136 Ok(Self { inner })
137 }
138
139 pub fn container_metadata(&self) -> Result<ContainerMetadata> {
140 tracing::debug!("decoder::container_metadata");
141 let meta = self.inner.container_metadata();
142
143 let color_matrix1 = json_to_matrix9(meta, "colorMatrix1");
144 let color_matrix2 = json_to_matrix9(meta, "colorMatrix2");
145 let forward_matrix1 = json_to_matrix9(meta, "forwardMatrix1");
146 let forward_matrix2 = json_to_matrix9(meta, "forwardMatrix2");
147 let calibration_matrix1 = json_to_matrix9(meta, "calibrationMatrix1");
148 let calibration_matrix2 = json_to_matrix9(meta, "calibrationMatrix2");
149
150 let illuminant1 = meta.get("calibrationIlluminant1").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
151 let illuminant2 = meta.get("calibrationIlluminant2").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
152
153 let white_level = meta.get("whiteLevel").and_then(|v| v.as_f64()).unwrap_or(16383.0);
154
155 let (black_level, black_level_count) = json_to_black_level(meta);
156
157 let audio_sample_rate = meta
158 .get("extraData")
159 .and_then(|e| e.get("audioSampleRate"))
160 .and_then(|v| v.as_i64())
161 .unwrap_or(0) as i32;
162 let audio_channels = meta
163 .get("extraData")
164 .and_then(|e| e.get("audioChannels"))
165 .and_then(|v| v.as_i64())
166 .unwrap_or(0) as i32;
167
168 let lens_shading_map = json_to_lens_shading_map(meta);
169
170 Ok(ContainerMetadata {
171 color_matrix1,
172 color_matrix2,
173 forward_matrix1,
174 forward_matrix2,
175 calibration_matrix1,
176 calibration_matrix2,
177 calibration_illuminant1: illuminant1,
178 calibration_illuminant2: illuminant2,
179 has_calibration_illuminants: illuminant1 != 0 || illuminant2 != 0,
180 white_level,
181 black_level,
182 black_level_count,
183 audio_sample_rate_hz: audio_sample_rate,
184 num_audio_channels: audio_channels,
185 lens_shading_map,
186 })
187 }
188
189 pub fn timestamps(&self) -> Result<Vec<i64>> {
190 let ts = self.inner.frame_timestamps().collect::<Vec<_>>();
191 tracing::debug!("decoder::timestamps: {} frames", ts.len());
192 Ok(ts)
193 }
194
195 pub fn prefetch(&self, timestamp_ns: i64) {
198 self.inner.prefetch(timestamp_ns);
199 }
200
201 pub fn load_frame(&self, timestamp_ns: i64) -> Result<(Vec<u16>, FrameMetadata)> {
202 let (pixels, meta) = self.inner.load_frame(timestamp_ns)
203 .map_err(|e| {
204 tracing::error!("failed to decode frame at ns {}: {}", timestamp_ns, e);
205 anyhow!("Failed to decode frame at ns {}: {}", timestamp_ns, e)
206 })?;
207
208 let width = meta.get("width").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
209 let height = meta.get("height").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
210 let as_shot_neutral = json_to_as_shot_neutral(&meta);
211 let exposure_time = meta.get("exposureTime").and_then(|v| v.as_f64()).unwrap_or(0.0);
212 let iso = meta.get("iso").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
213 let focal_length = meta.get("focalLength").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
214 let aperture = meta.get("aperture").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
215
216 Ok((pixels, FrameMetadata {
217 width,
218 height,
219 timestamp_ns,
220 as_shot_neutral,
221 exposure_time,
222 iso,
223 focal_length,
224 aperture,
225 dynamic_black_level: json_to_dynamic_black_level(&meta),
226 dynamic_white_level: json_to_dynamic_white_level(&meta),
227 lens_shading_map: json_to_lens_shading_map(&meta),
228 }))
229 }
230
231 pub fn load_frame_into(&self, timestamp_ns: i64, out: &mut [u16]) -> Result<[f32; 3]> {
235 self.inner.load_frame_into(timestamp_ns, out)
236 .map_err(|e| {
237 tracing::error!("failed to decode frame at ns {}: {}", timestamp_ns, e);
238 anyhow!("Failed to decode frame at ns {}: {}", timestamp_ns, e)
239 })
240 }
241
242 pub fn load_frame_metadata(&self, timestamp_ns: i64) -> Result<FrameMetadata> {
243 let meta = self.inner.load_frame_metadata(timestamp_ns)
244 .map_err(|e| anyhow!("Failed to get metadata for frame at ns {}: {}", timestamp_ns, e))?;
245
246 let width = meta.get("width").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
247 let height = meta.get("height").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
248 let as_shot_neutral = json_to_as_shot_neutral(&meta);
249 let exposure_time = meta.get("exposureTime").and_then(|v| v.as_f64()).unwrap_or(0.0);
250 let iso = meta.get("iso").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
251 let focal_length = meta.get("focalLength").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
252 let aperture = meta.get("aperture").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
253
254 Ok(FrameMetadata {
255 width,
256 height,
257 timestamp_ns,
258 as_shot_neutral,
259 exposure_time,
260 iso,
261 focal_length,
262 aperture,
263 dynamic_black_level: json_to_dynamic_black_level(&meta),
264 dynamic_white_level: json_to_dynamic_white_level(&meta),
265 lens_shading_map: json_to_lens_shading_map(&meta),
266 })
267 }
268
269 pub fn write_audio_to<W: Write>(&self, writer: &mut W) -> Result<()> {
274 let chunks = self.inner.load_audio()
275 .map_err(|e| anyhow!("Failed to load audio: {}", e))?;
276 for chunk in chunks {
277 for &sample in &chunk.samples {
278 writer.write_all(&sample.to_le_bytes())?;
279 }
280 }
281 Ok(())
282 }
283}