use little_exif::exif_tag::ExifTag;
#[cfg(feature = "wasm")]
use {js_sys::Array, wasm_bindgen::prelude::*, JsValue};
pub(super) struct EditableTag {
pub tag: exif::Tag,
pub constructor: fn(String) -> ExifTag,
}
pub(super) static EDITABLE_TAGS: &[EditableTag] = &[
EditableTag { tag: exif::Tag::ImageDescription, constructor: ExifTag::ImageDescription },
EditableTag { tag: exif::Tag::Make, constructor: ExifTag::Make },
EditableTag { tag: exif::Tag::Model, constructor: ExifTag::Model },
EditableTag { tag: exif::Tag::Software, constructor: ExifTag::Software },
EditableTag { tag: exif::Tag::DateTime, constructor: ExifTag::ModifyDate },
EditableTag { tag: exif::Tag::Artist, constructor: ExifTag::Artist },
EditableTag { tag: exif::Tag::Copyright, constructor: ExifTag::Copyright },
EditableTag { tag: exif::Tag::DateTimeOriginal, constructor: ExifTag::DateTimeOriginal },
EditableTag { tag: exif::Tag::DateTimeDigitized, constructor: ExifTag::CreateDate },
EditableTag { tag: exif::Tag::OffsetTime, constructor: ExifTag::OffsetTime },
EditableTag { tag: exif::Tag::OffsetTimeOriginal, constructor: ExifTag::OffsetTimeOriginal },
EditableTag { tag: exif::Tag::OffsetTimeDigitized, constructor: ExifTag::OffsetTimeDigitized },
EditableTag { tag: exif::Tag::SubSecTime, constructor: ExifTag::SubSecTime },
EditableTag { tag: exif::Tag::SubSecTimeOriginal, constructor: ExifTag::SubSecTimeOriginal },
EditableTag { tag: exif::Tag::SubSecTimeDigitized, constructor: ExifTag::SubSecTimeDigitized },
EditableTag { tag: exif::Tag::SpectralSensitivity, constructor: ExifTag::SpectralSensitivity },
EditableTag { tag: exif::Tag::RelatedSoundFile, constructor: ExifTag::RelatedSoundFile },
EditableTag { tag: exif::Tag::ImageUniqueID, constructor: ExifTag::ImageUniqueID },
EditableTag { tag: exif::Tag::CameraOwnerName, constructor: ExifTag::OwnerName },
EditableTag { tag: exif::Tag::BodySerialNumber, constructor: ExifTag::SerialNumber },
EditableTag { tag: exif::Tag::LensMake, constructor: ExifTag::LensMake },
EditableTag { tag: exif::Tag::LensModel, constructor: ExifTag::LensModel },
EditableTag { tag: exif::Tag::LensSerialNumber, constructor: ExifTag::LensSerialNumber },
];
pub(super) fn description_to_exif_tag(desc: &str, value: String) -> Option<ExifTag> {
EDITABLE_TAGS
.iter()
.find(|e| e.tag.description() == Some(desc))
.map(|e| (e.constructor)(value))
}
pub(super) struct DeletableTag {
pub tag: exif::Tag,
pub deleter: fn() -> ExifTag,
}
pub(super) static DELETABLE_TAGS: &[DeletableTag] = &[
DeletableTag { tag: exif::Tag::Orientation, deleter: || ExifTag::Orientation(vec![]) },
DeletableTag { tag: exif::Tag::ExposureTime, deleter: || ExifTag::ExposureTime(vec![]) },
DeletableTag { tag: exif::Tag::FNumber, deleter: || ExifTag::FNumber(vec![]) },
DeletableTag { tag: exif::Tag::ExposureProgram, deleter: || ExifTag::ExposureProgram(vec![]) },
DeletableTag { tag: exif::Tag::ISOSpeed, deleter: || ExifTag::ISO(vec![]) },
DeletableTag { tag: exif::Tag::ShutterSpeedValue, deleter: || ExifTag::ShutterSpeedValue(vec![]) },
DeletableTag { tag: exif::Tag::ApertureValue, deleter: || ExifTag::ApertureValue(vec![]) },
DeletableTag { tag: exif::Tag::BrightnessValue, deleter: || ExifTag::BrightnessValue(vec![]) },
DeletableTag { tag: exif::Tag::ExposureBiasValue, deleter: || ExifTag::ExposureCompensation(vec![]) },
DeletableTag { tag: exif::Tag::MaxApertureValue, deleter: || ExifTag::MaxApertureValue(vec![]) },
DeletableTag { tag: exif::Tag::SubjectDistance, deleter: || ExifTag::SubjectDistance(vec![]) },
DeletableTag { tag: exif::Tag::MeteringMode, deleter: || ExifTag::MeteringMode(vec![]) },
DeletableTag { tag: exif::Tag::LightSource, deleter: || ExifTag::LightSource(vec![]) },
DeletableTag { tag: exif::Tag::Flash, deleter: || ExifTag::Flash(vec![]) },
DeletableTag { tag: exif::Tag::FocalLength, deleter: || ExifTag::FocalLength(vec![]) },
DeletableTag { tag: exif::Tag::ColorSpace, deleter: || ExifTag::ColorSpace(vec![]) },
DeletableTag { tag: exif::Tag::ExposureMode, deleter: || ExifTag::ExposureMode(vec![]) },
DeletableTag { tag: exif::Tag::WhiteBalance, deleter: || ExifTag::WhiteBalance(vec![]) },
DeletableTag { tag: exif::Tag::DigitalZoomRatio, deleter: || ExifTag::DigitalZoomRatio(vec![]) },
DeletableTag { tag: exif::Tag::SceneCaptureType, deleter: || ExifTag::SceneCaptureType(vec![]) },
DeletableTag { tag: exif::Tag::Contrast, deleter: || ExifTag::Contrast(vec![]) },
DeletableTag { tag: exif::Tag::Saturation, deleter: || ExifTag::Saturation(vec![]) },
DeletableTag { tag: exif::Tag::Sharpness, deleter: || ExifTag::Sharpness(vec![]) },
];
pub(super) fn tag_for_remove(desc: &str) -> Option<ExifTag> {
description_to_exif_tag(desc, String::new())
.or_else(|| DELETABLE_TAGS.iter().find(|e| e.tag.description() == Some(desc)).map(|e| (e.deleter)()))
}
#[cfg(feature = "wasm")]
#[wasm_bindgen(js_name = editableFields)]
pub fn editable_fields() -> Array {
EDITABLE_TAGS
.iter()
.filter_map(|e| e.tag.description())
.map(JsValue::from_str)
.collect()
}
#[cfg(feature = "wasm")]
#[wasm_bindgen(js_name = deletableFields)]
pub fn deletable_fields() -> Array {
EDITABLE_TAGS
.iter()
.filter_map(|e| e.tag.description())
.chain(DELETABLE_TAGS.iter().filter_map(|e| e.tag.description()))
.map(JsValue::from_str)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verify_editable_tags_have_descriptions() {
for e in EDITABLE_TAGS {
assert!(
e.tag.description().is_some(),
"{:?} has no description in kamadak-exif — was it removed or renamed?",
e.tag
);
}
let descs: Vec<&str> = EDITABLE_TAGS.iter().filter_map(|e| e.tag.description()).collect();
let unique: std::collections::HashSet<&str> = descs.iter().copied().collect();
assert_eq!(descs.len(), unique.len(), "EDITABLE_TAGS contains duplicate descriptions");
assert_eq!(EDITABLE_TAGS.len(), 23, "Expected 23 editable tags — update this count if you add or remove entries");
}
#[test]
fn verify_deletable_tags() {
let editable_set: std::collections::HashSet<exif::Tag> =
EDITABLE_TAGS.iter().map(|e| e.tag).collect();
for e in DELETABLE_TAGS {
assert!(
e.tag.description().is_some(),
"{:?} has no description in kamadak-exif — was it removed or renamed?",
e.tag
);
assert!(
!editable_set.contains(&e.tag),
"{:?} is in both EDITABLE_TAGS and DELETABLE_TAGS — remove it from DELETABLE_TAGS",
e.tag
);
}
}
}