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 ContainerMetadata {
12 pub color_matrix1: [f32; 9],
13 pub color_matrix2: [f32; 9],
14 pub forward_matrix1: [f32; 9],
15 pub forward_matrix2: [f32; 9],
16 pub calibration_matrix1: [f32; 9],
17 pub calibration_matrix2: [f32; 9],
18 pub calibration_illuminant1: i32,
19 pub calibration_illuminant2: i32,
20 pub has_calibration_illuminants: bool,
21 pub white_level: f64,
22 pub black_level: [f64; 4],
23 pub black_level_count: i32,
24 pub audio_sample_rate_hz: i32,
25 pub num_audio_channels: i32,
26}
27
28#[derive(Debug, Clone)]
29pub struct FrameMetadata {
30 pub width: u32,
31 pub height: u32,
32 pub timestamp_ns: i64,
33 pub as_shot_neutral: [f32; 3],
34 pub exposure_time: f64,
35 pub iso: f32,
36 pub focal_length: f32,
37 pub aperture: f32,
38}
39
40fn json_to_matrix9(val: &Value, key: &str) -> [f32; 9] {
41 let mut result = [0.0f32; 9];
42 if let Some(arr) = val.get(key).and_then(|v| v.as_array()) {
43 for (i, item) in arr.iter().enumerate().take(9) {
44 if let Some(n) = item.as_f64() {
45 result[i] = n as f32;
46 }
47 }
48 }
49 result
50}
51
52fn json_to_black_level(val: &Value) -> ([f64; 4], i32) {
53 let mut result = [0.0f64; 4];
54 let mut count = 0i32;
55 if let Some(arr) = val.get("blackLevel").and_then(|v| v.as_array()) {
56 for (i, item) in arr.iter().enumerate().take(4) {
57 if let Some(n) = item.as_f64() {
58 result[i] = n;
59 count = (i + 1) as i32;
60 }
61 }
62 }
63 (result, count)
64}
65
66fn json_to_as_shot_neutral(val: &Value) -> [f32; 3] {
67 let mut result = [1.0f32; 3];
68 if let Some(arr) = val.get("asShotNeutral").and_then(|v| v.as_array()) {
69 for (i, item) in arr.iter().enumerate().take(3) {
70 if let Some(n) = item.as_f64() {
71 result[i] = n as f32;
72 }
73 }
74 }
75 result
76}
77
78impl Decoder {
79 pub fn new<P: AsRef<Path>>(path: P) -> Result<Self> {
80 let path_str = path.as_ref().to_string_lossy().to_string();
81 tracing::debug!("decoder::new: {}", path_str);
82 let inner = motioncam_decoder::Decoder::from_path(path)
83 .map_err(|e| {
84 tracing::error!("decoder failed to open {}: {}", path_str, e);
85 anyhow!("Failed to open decoder: {}", e)
86 })?;
87 tracing::debug!("decoder opened successfully: {}", path_str);
88 Ok(Self { inner })
89 }
90
91 pub fn container_metadata(&self) -> Result<ContainerMetadata> {
92 tracing::debug!("decoder::container_metadata");
93 let meta = self.inner.container_metadata();
94
95 let color_matrix1 = json_to_matrix9(meta, "colorMatrix1");
96 let color_matrix2 = json_to_matrix9(meta, "colorMatrix2");
97 let forward_matrix1 = json_to_matrix9(meta, "forwardMatrix1");
98 let forward_matrix2 = json_to_matrix9(meta, "forwardMatrix2");
99 let calibration_matrix1 = json_to_matrix9(meta, "calibrationMatrix1");
100 let calibration_matrix2 = json_to_matrix9(meta, "calibrationMatrix2");
101
102 let illuminant1 = meta.get("calibrationIlluminant1").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
103 let illuminant2 = meta.get("calibrationIlluminant2").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
104
105 let white_level = meta.get("whiteLevel").and_then(|v| v.as_f64()).unwrap_or(16383.0);
106
107 let (black_level, black_level_count) = json_to_black_level(meta);
108
109 let audio_sample_rate = meta
110 .get("extraData")
111 .and_then(|e| e.get("audioSampleRate"))
112 .and_then(|v| v.as_i64())
113 .unwrap_or(0) as i32;
114 let audio_channels = meta
115 .get("extraData")
116 .and_then(|e| e.get("audioChannels"))
117 .and_then(|v| v.as_i64())
118 .unwrap_or(0) as i32;
119
120 Ok(ContainerMetadata {
121 color_matrix1,
122 color_matrix2,
123 forward_matrix1,
124 forward_matrix2,
125 calibration_matrix1,
126 calibration_matrix2,
127 calibration_illuminant1: illuminant1,
128 calibration_illuminant2: illuminant2,
129 has_calibration_illuminants: illuminant1 != 0 || illuminant2 != 0,
130 white_level,
131 black_level,
132 black_level_count,
133 audio_sample_rate_hz: audio_sample_rate,
134 num_audio_channels: audio_channels,
135 })
136 }
137
138 pub fn timestamps(&self) -> Result<Vec<i64>> {
139 let ts = self.inner.frame_timestamps().collect::<Vec<_>>();
140 tracing::debug!("decoder::timestamps: {} frames", ts.len());
141 Ok(ts)
142 }
143
144 pub fn prefetch(&self, timestamp_ns: i64) {
147 self.inner.prefetch(timestamp_ns);
148 }
149
150 pub fn load_frame(&self, timestamp_ns: i64) -> Result<(Vec<u16>, FrameMetadata)> {
151 let (pixels, meta) = self.inner.load_frame(timestamp_ns)
152 .map_err(|e| {
153 tracing::error!("failed to decode frame at ns {}: {}", timestamp_ns, e);
154 anyhow!("Failed to decode frame at ns {}: {}", timestamp_ns, e)
155 })?;
156
157 let width = meta.get("width").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
158 let height = meta.get("height").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
159 let as_shot_neutral = json_to_as_shot_neutral(&meta);
160 let exposure_time = meta.get("exposureTime").and_then(|v| v.as_f64()).unwrap_or(0.0);
161 let iso = meta.get("iso").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
162 let focal_length = meta.get("focalLength").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
163 let aperture = meta.get("aperture").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
164
165 Ok((pixels, FrameMetadata {
166 width,
167 height,
168 timestamp_ns,
169 as_shot_neutral,
170 exposure_time,
171 iso,
172 focal_length,
173 aperture,
174 }))
175 }
176
177 pub fn load_frame_into(&self, timestamp_ns: i64, out: &mut [u16]) -> Result<[f32; 3]> {
181 self.inner.load_frame_into(timestamp_ns, out)
182 .map_err(|e| {
183 tracing::error!("failed to decode frame at ns {}: {}", timestamp_ns, e);
184 anyhow!("Failed to decode frame at ns {}: {}", timestamp_ns, e)
185 })
186 }
187
188 pub fn load_frame_metadata(&self, timestamp_ns: i64) -> Result<FrameMetadata> {
189 let meta = self.inner.load_frame_metadata(timestamp_ns)
190 .map_err(|e| anyhow!("Failed to get metadata for frame at ns {}: {}", timestamp_ns, e))?;
191
192 let width = meta.get("width").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
193 let height = meta.get("height").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
194 let as_shot_neutral = json_to_as_shot_neutral(&meta);
195 let exposure_time = meta.get("exposureTime").and_then(|v| v.as_f64()).unwrap_or(0.0);
196 let iso = meta.get("iso").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
197 let focal_length = meta.get("focalLength").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
198 let aperture = meta.get("aperture").and_then(|v| v.as_f64()).unwrap_or(0.0) as f32;
199
200 Ok(FrameMetadata {
201 width,
202 height,
203 timestamp_ns,
204 as_shot_neutral,
205 exposure_time,
206 iso,
207 focal_length,
208 aperture,
209 })
210 }
211
212 pub fn write_audio_to<W: Write>(&self, writer: &mut W) -> Result<()> {
217 let chunks = self.inner.load_audio()
218 .map_err(|e| anyhow!("Failed to load audio: {}", e))?;
219 for chunk in chunks {
220 for &sample in &chunk.samples {
221 writer.write_all(&sample.to_le_bytes())?;
222 }
223 }
224 Ok(())
225 }
226}