oximedia_clips/
camera_metadata.rs1#![allow(dead_code)]
9
10use crate::clip::ClipId;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct CameraMetadata {
17 pub lens: Option<String>,
19
20 pub iso: Option<u32>,
22
23 pub aperture: Option<f32>,
25
26 pub shutter_speed: Option<String>,
29
30 pub focal_length_mm: Option<f32>,
32
33 pub camera_make: Option<String>,
35
36 pub camera_model: Option<String>,
38}
39
40impl CameraMetadata {
41 #[must_use]
43 pub fn new() -> Self {
44 Self {
45 lens: None,
46 iso: None,
47 aperture: None,
48 shutter_speed: None,
49 focal_length_mm: None,
50 camera_make: None,
51 camera_model: None,
52 }
53 }
54
55 #[must_use]
57 pub fn with_lens(mut self, lens: impl Into<String>) -> Self {
58 self.lens = Some(lens.into());
59 self
60 }
61
62 #[must_use]
64 pub fn with_iso(mut self, iso: u32) -> Self {
65 self.iso = Some(iso);
66 self
67 }
68
69 #[must_use]
71 pub fn with_aperture(mut self, aperture: f32) -> Self {
72 self.aperture = Some(aperture);
73 self
74 }
75
76 #[must_use]
78 pub fn with_shutter_speed(mut self, speed: impl Into<String>) -> Self {
79 self.shutter_speed = Some(speed.into());
80 self
81 }
82
83 #[must_use]
85 pub fn with_focal_length_mm(mut self, mm: f32) -> Self {
86 self.focal_length_mm = Some(mm);
87 self
88 }
89
90 #[must_use]
92 pub fn with_camera_make(mut self, make: impl Into<String>) -> Self {
93 self.camera_make = Some(make.into());
94 self
95 }
96
97 #[must_use]
99 pub fn with_camera_model(mut self, model: impl Into<String>) -> Self {
100 self.camera_model = Some(model.into());
101 self
102 }
103
104 #[must_use]
106 pub fn is_empty(&self) -> bool {
107 self.lens.is_none()
108 && self.iso.is_none()
109 && self.aperture.is_none()
110 && self.shutter_speed.is_none()
111 && self.focal_length_mm.is_none()
112 && self.camera_make.is_none()
113 && self.camera_model.is_none()
114 }
115
116 #[must_use]
121 pub fn approximate_ev(&self) -> Option<f32> {
122 let iso = self.iso? as f32;
123 let ap = self.aperture?;
124 if iso <= 0.0 || ap <= 0.0 {
126 return None;
127 }
128 let ev = (ap * ap / (iso / 100.0)).log2();
129 Some(ev)
130 }
131}
132
133impl Default for CameraMetadata {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139pub trait CameraMetadataExt {
144 fn set_camera_metadata(&mut self, clip_id: ClipId, meta: CameraMetadata);
146
147 fn camera_metadata(&self, clip_id: &ClipId) -> Option<&CameraMetadata>;
149
150 fn remove_camera_metadata(&mut self, clip_id: &ClipId) -> Option<CameraMetadata>;
152}
153
154#[derive(Debug, Default)]
156pub struct CameraMetadataStore {
157 inner: HashMap<ClipId, CameraMetadata>,
158}
159
160impl CameraMetadataStore {
161 #[must_use]
163 pub fn new() -> Self {
164 Self::default()
165 }
166
167 #[must_use]
169 pub fn len(&self) -> usize {
170 self.inner.len()
171 }
172
173 #[must_use]
175 pub fn is_empty(&self) -> bool {
176 self.inner.is_empty()
177 }
178
179 pub fn iter(&self) -> impl Iterator<Item = (&ClipId, &CameraMetadata)> {
181 self.inner.iter()
182 }
183}
184
185impl CameraMetadataExt for CameraMetadataStore {
186 fn set_camera_metadata(&mut self, clip_id: ClipId, meta: CameraMetadata) {
187 self.inner.insert(clip_id, meta);
188 }
189
190 fn camera_metadata(&self, clip_id: &ClipId) -> Option<&CameraMetadata> {
191 self.inner.get(clip_id)
192 }
193
194 fn remove_camera_metadata(&mut self, clip_id: &ClipId) -> Option<CameraMetadata> {
195 self.inner.remove(clip_id)
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::clip::ClipId;
203
204 #[test]
205 fn test_camera_metadata_default_is_empty() {
206 let meta = CameraMetadata::new();
207 assert!(meta.is_empty());
208 }
209
210 #[test]
211 fn test_camera_metadata_builder() {
212 let meta = CameraMetadata::new()
213 .with_lens("Zeiss 50mm T1.5")
214 .with_iso(800)
215 .with_aperture(2.8)
216 .with_shutter_speed("1/48")
217 .with_focal_length_mm(50.0)
218 .with_camera_make("ARRI")
219 .with_camera_model("ALEXA Mini LF");
220
221 assert_eq!(meta.lens.as_deref(), Some("Zeiss 50mm T1.5"));
222 assert_eq!(meta.iso, Some(800));
223 assert!((meta.aperture.expect("aperture should be set") - 2.8).abs() < 1e-5);
224 assert_eq!(meta.shutter_speed.as_deref(), Some("1/48"));
225 assert!((meta.focal_length_mm.expect("focal_length_mm should be set") - 50.0).abs() < 1e-5);
226 assert_eq!(meta.camera_make.as_deref(), Some("ARRI"));
227 assert_eq!(meta.camera_model.as_deref(), Some("ALEXA Mini LF"));
228 assert!(!meta.is_empty());
229 }
230
231 #[test]
232 fn test_camera_metadata_approximate_ev_some() {
233 let meta = CameraMetadata::new().with_aperture(2.8).with_iso(800);
235 let ev = meta.approximate_ev();
236 assert!(ev.is_some());
237 let ev_val = ev.expect("EV should be computed when aperture and ISO are set");
238 assert!(ev_val > -1.0 && ev_val < 1.0);
239 }
240
241 #[test]
242 fn test_camera_metadata_approximate_ev_none_when_missing_fields() {
243 let meta_no_iso = CameraMetadata::new().with_aperture(2.8);
244 assert!(meta_no_iso.approximate_ev().is_none());
245
246 let meta_no_ap = CameraMetadata::new().with_iso(800);
247 assert!(meta_no_ap.approximate_ev().is_none());
248 }
249
250 #[test]
251 fn test_camera_metadata_store_set_get() {
252 let mut store = CameraMetadataStore::new();
253 let id = ClipId::new();
254 let meta = CameraMetadata::new().with_iso(400);
255
256 store.set_camera_metadata(id, meta.clone());
257 assert_eq!(store.len(), 1);
258
259 let retrieved = store.camera_metadata(&id).expect("should be present");
260 assert_eq!(retrieved.iso, Some(400));
261 }
262
263 #[test]
264 fn test_camera_metadata_store_remove() {
265 let mut store = CameraMetadataStore::new();
266 let id = ClipId::new();
267 store.set_camera_metadata(id, CameraMetadata::new().with_iso(100));
268
269 let removed = store.remove_camera_metadata(&id);
270 assert!(removed.is_some());
271 assert!(store.is_empty());
272 }
273
274 #[test]
275 fn test_camera_metadata_store_missing_key() {
276 let store = CameraMetadataStore::new();
277 let id = ClipId::new();
278 assert!(store.camera_metadata(&id).is_none());
279 }
280
281 #[test]
282 fn test_camera_metadata_overwrite() {
283 let mut store = CameraMetadataStore::new();
284 let id = ClipId::new();
285
286 store.set_camera_metadata(id, CameraMetadata::new().with_iso(200));
287 store.set_camera_metadata(id, CameraMetadata::new().with_iso(3200));
288
289 assert_eq!(store.len(), 1);
290 assert_eq!(
291 store
292 .camera_metadata(&id)
293 .expect("overwritten metadata should be present")
294 .iso,
295 Some(3200)
296 );
297 }
298}