#![allow(dead_code)]
use crate::clip::ClipId;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CameraMetadata {
pub lens: Option<String>,
pub iso: Option<u32>,
pub aperture: Option<f32>,
pub shutter_speed: Option<String>,
pub focal_length_mm: Option<f32>,
pub camera_make: Option<String>,
pub camera_model: Option<String>,
}
impl CameraMetadata {
#[must_use]
pub fn new() -> Self {
Self {
lens: None,
iso: None,
aperture: None,
shutter_speed: None,
focal_length_mm: None,
camera_make: None,
camera_model: None,
}
}
#[must_use]
pub fn with_lens(mut self, lens: impl Into<String>) -> Self {
self.lens = Some(lens.into());
self
}
#[must_use]
pub fn with_iso(mut self, iso: u32) -> Self {
self.iso = Some(iso);
self
}
#[must_use]
pub fn with_aperture(mut self, aperture: f32) -> Self {
self.aperture = Some(aperture);
self
}
#[must_use]
pub fn with_shutter_speed(mut self, speed: impl Into<String>) -> Self {
self.shutter_speed = Some(speed.into());
self
}
#[must_use]
pub fn with_focal_length_mm(mut self, mm: f32) -> Self {
self.focal_length_mm = Some(mm);
self
}
#[must_use]
pub fn with_camera_make(mut self, make: impl Into<String>) -> Self {
self.camera_make = Some(make.into());
self
}
#[must_use]
pub fn with_camera_model(mut self, model: impl Into<String>) -> Self {
self.camera_model = Some(model.into());
self
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.lens.is_none()
&& self.iso.is_none()
&& self.aperture.is_none()
&& self.shutter_speed.is_none()
&& self.focal_length_mm.is_none()
&& self.camera_make.is_none()
&& self.camera_model.is_none()
}
#[must_use]
pub fn approximate_ev(&self) -> Option<f32> {
let iso = self.iso? as f32;
let ap = self.aperture?;
if iso <= 0.0 || ap <= 0.0 {
return None;
}
let ev = (ap * ap / (iso / 100.0)).log2();
Some(ev)
}
}
impl Default for CameraMetadata {
fn default() -> Self {
Self::new()
}
}
pub trait CameraMetadataExt {
fn set_camera_metadata(&mut self, clip_id: ClipId, meta: CameraMetadata);
fn camera_metadata(&self, clip_id: &ClipId) -> Option<&CameraMetadata>;
fn remove_camera_metadata(&mut self, clip_id: &ClipId) -> Option<CameraMetadata>;
}
#[derive(Debug, Default)]
pub struct CameraMetadataStore {
inner: HashMap<ClipId, CameraMetadata>,
}
impl CameraMetadataStore {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn len(&self) -> usize {
self.inner.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (&ClipId, &CameraMetadata)> {
self.inner.iter()
}
}
impl CameraMetadataExt for CameraMetadataStore {
fn set_camera_metadata(&mut self, clip_id: ClipId, meta: CameraMetadata) {
self.inner.insert(clip_id, meta);
}
fn camera_metadata(&self, clip_id: &ClipId) -> Option<&CameraMetadata> {
self.inner.get(clip_id)
}
fn remove_camera_metadata(&mut self, clip_id: &ClipId) -> Option<CameraMetadata> {
self.inner.remove(clip_id)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::clip::ClipId;
#[test]
fn test_camera_metadata_default_is_empty() {
let meta = CameraMetadata::new();
assert!(meta.is_empty());
}
#[test]
fn test_camera_metadata_builder() {
let meta = CameraMetadata::new()
.with_lens("Zeiss 50mm T1.5")
.with_iso(800)
.with_aperture(2.8)
.with_shutter_speed("1/48")
.with_focal_length_mm(50.0)
.with_camera_make("ARRI")
.with_camera_model("ALEXA Mini LF");
assert_eq!(meta.lens.as_deref(), Some("Zeiss 50mm T1.5"));
assert_eq!(meta.iso, Some(800));
assert!((meta.aperture.expect("aperture should be set") - 2.8).abs() < 1e-5);
assert_eq!(meta.shutter_speed.as_deref(), Some("1/48"));
assert!((meta.focal_length_mm.expect("focal_length_mm should be set") - 50.0).abs() < 1e-5);
assert_eq!(meta.camera_make.as_deref(), Some("ARRI"));
assert_eq!(meta.camera_model.as_deref(), Some("ALEXA Mini LF"));
assert!(!meta.is_empty());
}
#[test]
fn test_camera_metadata_approximate_ev_some() {
let meta = CameraMetadata::new().with_aperture(2.8).with_iso(800);
let ev = meta.approximate_ev();
assert!(ev.is_some());
let ev_val = ev.expect("EV should be computed when aperture and ISO are set");
assert!(ev_val > -1.0 && ev_val < 1.0);
}
#[test]
fn test_camera_metadata_approximate_ev_none_when_missing_fields() {
let meta_no_iso = CameraMetadata::new().with_aperture(2.8);
assert!(meta_no_iso.approximate_ev().is_none());
let meta_no_ap = CameraMetadata::new().with_iso(800);
assert!(meta_no_ap.approximate_ev().is_none());
}
#[test]
fn test_camera_metadata_store_set_get() {
let mut store = CameraMetadataStore::new();
let id = ClipId::new();
let meta = CameraMetadata::new().with_iso(400);
store.set_camera_metadata(id, meta.clone());
assert_eq!(store.len(), 1);
let retrieved = store.camera_metadata(&id).expect("should be present");
assert_eq!(retrieved.iso, Some(400));
}
#[test]
fn test_camera_metadata_store_remove() {
let mut store = CameraMetadataStore::new();
let id = ClipId::new();
store.set_camera_metadata(id, CameraMetadata::new().with_iso(100));
let removed = store.remove_camera_metadata(&id);
assert!(removed.is_some());
assert!(store.is_empty());
}
#[test]
fn test_camera_metadata_store_missing_key() {
let store = CameraMetadataStore::new();
let id = ClipId::new();
assert!(store.camera_metadata(&id).is_none());
}
#[test]
fn test_camera_metadata_overwrite() {
let mut store = CameraMetadataStore::new();
let id = ClipId::new();
store.set_camera_metadata(id, CameraMetadata::new().with_iso(200));
store.set_camera_metadata(id, CameraMetadata::new().with_iso(3200));
assert_eq!(store.len(), 1);
assert_eq!(
store
.camera_metadata(&id)
.expect("overwritten metadata should be present")
.iso,
Some(3200)
);
}
}