Skip to main content

exiftool_rs_wrapper/
types.rs

1//! 核心类型定义
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::fmt;
6
7/// 标签标识符 - 提供类型安全的标签访问
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct TagId(&'static str);
10
11impl TagId {
12    /// 创建新的标签标识符
13    pub const fn new(name: &'static str) -> Self {
14        Self(name)
15    }
16
17    /// 获取标签名称
18    pub fn name(&self) -> &str {
19        self.0
20    }
21
22    // === 常用 EXIF 标签 ===
23    pub const MAKE: Self = Self("Make");
24    pub const MODEL: Self = Self("Model");
25    pub const DATE_TIME_ORIGINAL: Self = Self("DateTimeOriginal");
26    pub const CREATE_DATE: Self = Self("CreateDate");
27    pub const MODIFY_DATE: Self = Self("ModifyDate");
28    pub const IMAGE_WIDTH: Self = Self("ImageWidth");
29    pub const IMAGE_HEIGHT: Self = Self("ImageHeight");
30    pub const ORIENTATION: Self = Self("Orientation");
31    pub const X_RESOLUTION: Self = Self("XResolution");
32    pub const Y_RESOLUTION: Self = Self("YResolution");
33    pub const RESOLUTION_UNIT: Self = Self("ResolutionUnit");
34    pub const SOFTWARE: Self = Self("Software");
35    pub const COPYRIGHT: Self = Self("Copyright");
36    pub const ARTIST: Self = Self("Artist");
37    pub const IMAGE_DESCRIPTION: Self = Self("ImageDescription");
38
39    // === 相机设置标签 ===
40    pub const EXPOSURE_TIME: Self = Self("ExposureTime");
41    pub const F_NUMBER: Self = Self("FNumber");
42    pub const EXPOSURE_PROGRAM: Self = Self("ExposureProgram");
43    pub const ISO: Self = Self("ISO");
44    pub const SENSITIVITY_TYPE: Self = Self("SensitivityType");
45    pub const RECOMMENDED_EXPOSURE_INDEX: Self = Self("RecommendedExposureIndex");
46    pub const EXIF_VERSION: Self = Self("ExifVersion");
47    pub const DATE_TIME_DIGITIZED: Self = Self("DateTimeDigitized");
48    pub const COMPONENT_CONFIGURATION: Self = Self("ComponentConfiguration");
49    pub const SHUTTER_SPEED_VALUE: Self = Self("ShutterSpeedValue");
50    pub const APERTURE_VALUE: Self = Self("ApertureValue");
51    pub const BRIGHTNESS_VALUE: Self = Self("BrightnessValue");
52    pub const EXPOSURE_COMPENSATION: Self = Self("ExposureCompensation");
53    pub const MAX_APERTURE_VALUE: Self = Self("MaxApertureValue");
54    pub const SUBJECT_DISTANCE: Self = Self("SubjectDistance");
55    pub const METERING_MODE: Self = Self("MeteringMode");
56    pub const LIGHT_SOURCE: Self = Self("LightSource");
57    pub const FLASH: Self = Self("Flash");
58    pub const FOCAL_LENGTH: Self = Self("FocalLength");
59    pub const FOCAL_LENGTH_IN_35MM_FORMAT: Self = Self("FocalLengthIn35mmFormat");
60    pub const FLASH_ENERGY: Self = Self("FlashEnergy");
61    pub const SPATIAL_FREQUENCY_RESPONSE: Self = Self("SpatialFrequencyResponse");
62    pub const FOCAL_PLANE_X_RESOLUTION: Self = Self("FocalPlaneXResolution");
63    pub const FOCAL_PLANE_Y_RESOLUTION: Self = Self("FocalPlaneYResolution");
64    pub const FOCAL_PLANE_RESOLUTION_UNIT: Self = Self("FocalPlaneResolutionUnit");
65    pub const SUBJECT_LOCATION: Self = Self("SubjectLocation");
66    pub const EXPOSURE_INDEX: Self = Self("ExposureIndex");
67    pub const SENSING_METHOD: Self = Self("SensingMethod");
68    pub const FILE_SOURCE: Self = Self("FileSource");
69    pub const SCENE_TYPE: Self = Self("SceneType");
70    pub const CFA_PATTERN: Self = Self("CFAPattern");
71    pub const CUSTOM_RENDERED: Self = Self("CustomRendered");
72    pub const EXPOSURE_MODE: Self = Self("ExposureMode");
73    pub const WHITE_BALANCE: Self = Self("WhiteBalance");
74    pub const DIGITAL_ZOOM_RATIO: Self = Self("DigitalZoomRatio");
75    pub const FOCAL_LENGTH_35EFL: Self = Self("FocalLength35efl");
76    pub const SCENE_CAPTURE_TYPE: Self = Self("SceneCaptureType");
77    pub const GAIN_CONTROL: Self = Self("GainControl");
78    pub const CONTRAST: Self = Self("Contrast");
79    pub const SATURATION: Self = Self("Saturation");
80    pub const SHARPNESS: Self = Self("Sharpness");
81    pub const DEVICE_SETTING_DESCRIPTION: Self = Self("DeviceSettingDescription");
82    pub const SUBJECT_DISTANCE_RANGE: Self = Self("SubjectDistanceRange");
83
84    // === GPS 标签 ===
85    pub const GPS_LATITUDE_REF: Self = Self("GPSLatitudeRef");
86    pub const GPS_LATITUDE: Self = Self("GPSLatitude");
87    pub const GPS_LONGITUDE_REF: Self = Self("GPSLongitudeRef");
88    pub const GPS_LONGITUDE: Self = Self("GPSLongitude");
89    pub const GPS_ALTITUDE_REF: Self = Self("GPSAltitudeRef");
90    pub const GPS_ALTITUDE: Self = Self("GPSAltitude");
91    pub const GPS_TIMESTAMP: Self = Self("GPSTimeStamp");
92    pub const GPS_SATELLITES: Self = Self("GPSSatellites");
93    pub const GPS_STATUS: Self = Self("GPSStatus");
94    pub const GPS_MEASURE_MODE: Self = Self("GPSMeasureMode");
95    pub const GPS_DOP: Self = Self("GPSDOP");
96    pub const GPS_SPEED_REF: Self = Self("GPSSpeedRef");
97    pub const GPS_SPEED: Self = Self("GPSSpeed");
98    pub const GPS_TRACK_REF: Self = Self("GPSTrackRef");
99    pub const GPS_TRACK: Self = Self("GPSTrack");
100    pub const GPS_IMG_DIRECTION_REF: Self = Self("GPSImgDirectionRef");
101    pub const GPS_IMG_DIRECTION: Self = Self("GPSImgDirection");
102    pub const GPS_MAP_DATUM: Self = Self("GPSMapDatum");
103    pub const GPS_DEST_LATITUDE_REF: Self = Self("GPSDestLatitudeRef");
104    pub const GPS_DEST_LATITUDE: Self = Self("GPSDestLatitude");
105    pub const GPS_DEST_LONGITUDE_REF: Self = Self("GPSDestLongitudeRef");
106    pub const GPS_DEST_LONGITUDE: Self = Self("GPSDestLongitude");
107    pub const GPS_DEST_BEARING_REF: Self = Self("GPSDestBearingRef");
108    pub const GPS_DEST_BEARING: Self = Self("GPSDestBearing");
109    pub const GPS_DEST_DISTANCE_REF: Self = Self("GPSDestDistanceRef");
110    pub const GPS_DEST_DISTANCE: Self = Self("GPSDestDistance");
111    pub const GPS_PROCESSING_METHOD: Self = Self("GPSProcessingMethod");
112    pub const GPS_AREA_INFORMATION: Self = Self("GPSAreaInformation");
113    pub const GPS_DATE_STAMP: Self = Self("GPSDateStamp");
114    pub const GPS_DIFFERENTIAL: Self = Self("GPSDifferential");
115    pub const GPS_H_POSITIONING_ERROR: Self = Self("GPSHPositioningError");
116
117    // === 文件信息标签 ===
118    pub const FILE_NAME: Self = Self("FileName");
119    pub const DIRECTORY: Self = Self("Directory");
120    pub const FILE_SIZE: Self = Self("FileSize");
121    pub const FILE_MODIFY_DATE: Self = Self("FileModifyDate");
122    pub const FILE_ACCESS_DATE: Self = Self("FileAccessDate");
123    pub const FILE_INODE_CHANGE_DATE: Self = Self("FileInodeChangeDate");
124    pub const FILE_PERMISSIONS: Self = Self("FilePermissions");
125    pub const FILE_TYPE: Self = Self("FileType");
126    pub const FILE_TYPE_EXTENSION: Self = Self("FileTypeExtension");
127    pub const MIME_TYPE: Self = Self("MIMEType");
128    pub const EXIF_BYTE_ORDER: Self = Self("ExifByteOrder");
129    pub const CURRENT_ICC_PROFILE: Self = Self("CurrentICCProfile");
130    pub const PROFILE_DATE_TIME: Self = Self("ProfileDateTime");
131    pub const PROFILE_FILE_SIGNATURE: Self = Self("ProfileFileSignature");
132    pub const PRIMARY_PLATFORM: Self = Self("PrimaryPlatform");
133    pub const CMM_TYPE: Self = Self("CMMType");
134    pub const PROFILE_VERSION: Self = Self("ProfileVersion");
135    pub const PROFILE_CLASS: Self = Self("ProfileClass");
136    pub const COLOR_SPACE_DATA: Self = Self("ColorSpaceData");
137    pub const PROFILE_CONNECTION_SPACE: Self = Self("ProfileConnectionSpace");
138    pub const PROFILE_CONNECTION_SPACE_ILLUMINANT: Self = Self("ProfileConnectionSpaceIlluminant");
139    pub const ICC_PROFILE_CREATOR: Self = Self("ICCProfileCreator");
140    pub const ICC_PROFILE_DESCRIPTION: Self = Self("ICCProfileDescription");
141    pub const ICC_VIEWING_CONDITIONS_DESCRIPTION: Self = Self("ICCViewingConditionsDescription");
142    pub const ICC_DEVICE_MODEL: Self = Self("ICCDeviceModel");
143    pub const ICC_DEVICE_MANUFACTURER: Self = Self("ICCDeviceManufacturer");
144
145    // === IPTC 标签 ===
146    pub const IPTC_OBJECT_NAME: Self = Self("ObjectName");
147    pub const IPTC_EDIT_STATUS: Self = Self("EditStatus");
148    pub const IPTC_EDITORIAL_UPDATE: Self = Self("EditorialUpdate");
149    pub const IPTC_URGENCY: Self = Self("Urgency");
150    pub const IPTC_SUBJECT_REFERENCE: Self = Self("SubjectReference");
151    pub const IPTC_CATEGORY: Self = Self("Category");
152    pub const IPTC_SUPPLEMENTAL_CATEGORY: Self = Self("SupplementalCategory");
153    pub const IPTC_FIXTURE_IDENTIFIER: Self = Self("FixtureIdentifier");
154    pub const IPTC_KEYWORDS: Self = Self("Keywords");
155    pub const IPTC_CONTENT_LOCATION_CODE: Self = Self("ContentLocationCode");
156    pub const IPTC_CONTENT_LOCATION_NAME: Self = Self("ContentLocationName");
157    pub const IPTC_RELEASE_DATE: Self = Self("ReleaseDate");
158    pub const IPTC_RELEASE_TIME: Self = Self("ReleaseTime");
159    pub const IPTC_EXPIRATION_DATE: Self = Self("ExpirationDate");
160    pub const IPTC_EXPIRATION_TIME: Self = Self("ExpirationTime");
161    pub const IPTC_SPECIAL_INSTRUCTIONS: Self = Self("SpecialInstructions");
162    pub const IPTC_ACTION_ADVISED: Self = Self("ActionAdvised");
163    pub const IPTC_REFERENCE_SERVICE: Self = Self("ReferenceService");
164    pub const IPTC_REFERENCE_DATE: Self = Self("ReferenceDate");
165    pub const IPTC_REFERENCE_NUMBER: Self = Self("ReferenceNumber");
166    pub const IPTC_DATE_CREATED: Self = Self("DateCreated");
167    pub const IPTC_TIME_CREATED: Self = Self("TimeCreated");
168    pub const IPTC_DIGITAL_CREATION_DATE: Self = Self("DigitalCreationDate");
169    pub const IPTC_DIGITAL_CREATION_TIME: Self = Self("DigitalCreationTime");
170    pub const IPTC_ORIGINATING_PROGRAM: Self = Self("OriginatingProgram");
171    pub const IPTC_PROGRAM_VERSION: Self = Self("ProgramVersion");
172    pub const IPTC_OBJECT_CYCLE: Self = Self("ObjectCycle");
173    pub const IPTC_BY_LINE: Self = Self("By-line");
174    pub const IPTC_BY_LINE_TITLE: Self = Self("By-lineTitle");
175    pub const IPTC_CITY: Self = Self("City");
176    pub const IPTC_SUB_LOCATION: Self = Self("Sub-location");
177    pub const IPTC_PROVINCE_STATE: Self = Self("Province-State");
178    pub const IPTC_COUNTRY_PRIMARY_LOCATION_CODE: Self = Self("Country-PrimaryLocationCode");
179    pub const IPTC_COUNTRY_PRIMARY_LOCATION_NAME: Self = Self("Country-PrimaryLocationName");
180    pub const IPTC_ORIGINAL_TRANSMISSION_REFERENCE: Self = Self("OriginalTransmissionReference");
181    pub const IPTC_HEADLINE: Self = Self("Headline");
182    pub const IPTC_CREDIT: Self = Self("Credit");
183    pub const IPTC_SOURCE: Self = Self("Source");
184    pub const IPTC_COPYRIGHT_NOTICE: Self = Self("CopyrightNotice");
185    pub const IPTC_CONTACT: Self = Self("Contact");
186    pub const IPTC_CAPTION_ABSTRACT: Self = Self("Caption-Abstract");
187    pub const IPTC_WRITER_EDITOR: Self = Self("Writer-Editor");
188    pub const IPTC_IMAGE_TYPE: Self = Self("ImageType");
189    pub const IPTC_IMAGE_ORIENTATION: Self = Self("ImageOrientation");
190    pub const IPTC_LANGUAGE_IDENTIFIER: Self = Self("LanguageIdentifier");
191
192    // === XMP 标签 ( Dublin Core ) ===
193    pub const XMP_DC_TITLE: Self = Self("Title");
194    pub const XMP_DC_CREATOR: Self = Self("Creator");
195    pub const XMP_DC_SUBJECT: Self = Self("Subject");
196    pub const XMP_DC_DESCRIPTION: Self = Self("Description");
197    pub const XMP_DC_PUBLISHER: Self = Self("Publisher");
198    pub const XMP_DC_CONTRIBUTOR: Self = Self("Contributor");
199    pub const XMP_DC_DATE: Self = Self("Date");
200    pub const XMP_DC_TYPE: Self = Self("Type");
201    pub const XMP_DC_FORMAT: Self = Self("Format");
202    pub const XMP_DC_IDENTIFIER: Self = Self("Identifier");
203    pub const XMP_DC_SOURCE: Self = Self("Source");
204    pub const XMP_DC_LANGUAGE: Self = Self("Language");
205    pub const XMP_DC_RELATION: Self = Self("Relation");
206    pub const XMP_DC_COVERAGE: Self = Self("Coverage");
207    pub const XMP_DC_RIGHTS: Self = Self("Rights");
208
209    // === XMP 标签 ( XMP Rights ) ===
210    pub const XMP_XMP_RIGHTS_MANAGED: Self = Self("RightsManaged");
211    pub const XMP_XMP_RIGHTS_MARKED: Self = Self("RightsMarked");
212    pub const XMP_XMP_RIGHTS_WEB_STATEMENT: Self = Self("WebStatement");
213    pub const XMP_XMP_RIGHTS_USAGE_TERMS: Self = Self("UsageTerms");
214
215    // === 图像尺寸标签 ===
216    pub const IMAGE_SIZE: Self = Self("ImageSize");
217    pub const MEGAPIXELS: Self = Self("Megapixels");
218    pub const QUALITY: Self = Self("Quality");
219    pub const BITS_PER_SAMPLE: Self = Self("BitsPerSample");
220    pub const COLOR_COMPONENTS: Self = Self("ColorComponents");
221    pub const Y_CB_CR_SUB_SAMPLING: Self = Self("YCbCrSubSampling");
222    pub const Y_CB_CR_POSITIONING: Self = Self("YCbCrPositioning");
223
224    // === 缩略图标签 ===
225    pub const THUMBNAIL_IMAGE: Self = Self("ThumbnailImage");
226    pub const THUMBNAIL_LENGTH: Self = Self("ThumbnailLength");
227    pub const THUMBNAIL_OFFSET: Self = Self("ThumbnailOffset");
228    pub const PREVIEW_IMAGE: Self = Self("PreviewImage");
229    pub const PREVIEW_IMAGE_TYPE: Self = Self("PreviewImageType");
230    pub const JPG_FROM_RAW: Self = Self("JpgFromRaw");
231    pub const OTHER_IMAGE: Self = Self("OtherImage");
232
233    // === 色彩空间标签 ===
234    pub const COLOR_SPACE: Self = Self("ColorSpace");
235    pub const GAMMA: Self = Self("Gamma");
236}
237
238impl fmt::Display for TagId {
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        write!(f, "{}", self.0)
241    }
242}
243
244impl From<&'static str> for TagId {
245    fn from(name: &'static str) -> Self {
246        Self(name)
247    }
248}
249
250impl AsRef<str> for TagId {
251    fn as_ref(&self) -> &str {
252        self.0
253    }
254}
255
256/// 标签值类型 - 支持 ExifTool 返回的所有数据类型
257#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
258#[serde(untagged)]
259pub enum TagValue {
260    /// 字符串值
261    String(String),
262
263    /// 整数值
264    Integer(i64),
265
266    /// 浮点数值
267    Float(f64),
268
269    /// 布尔值
270    Boolean(bool),
271
272    /// 数组值
273    Array(Vec<TagValue>),
274
275    /// 二进制数据(Base64 编码)
276    Binary(String),
277
278    /// 空值
279    Null,
280}
281
282impl TagValue {
283    /// 尝试获取字符串值
284    pub fn as_string(&self) -> Option<&String> {
285        match self {
286            Self::String(s) => Some(s),
287            _ => None,
288        }
289    }
290
291    /// 尝试获取整数值
292    pub fn as_integer(&self) -> Option<i64> {
293        match self {
294            Self::Integer(i) => Some(*i),
295            Self::Float(f) => Some(*f as i64),
296            Self::String(s) => s.parse().ok(),
297            _ => None,
298        }
299    }
300
301    /// 尝试获取浮点数值
302    pub fn as_float(&self) -> Option<f64> {
303        match self {
304            Self::Float(f) => Some(*f),
305            Self::Integer(i) => Some(*i as f64),
306            Self::String(s) => s.parse().ok(),
307            _ => None,
308        }
309    }
310
311    /// 尝试获取布尔值
312    pub fn as_bool(&self) -> Option<bool> {
313        match self {
314            Self::Boolean(b) => Some(*b),
315            Self::Integer(0) => Some(false),
316            Self::Integer(_) => Some(true),
317            Self::String(s) => match s.to_lowercase().as_str() {
318                "true" | "yes" | "1" | "on" => Some(true),
319                "false" | "no" | "0" | "off" => Some(false),
320                _ => None,
321            },
322            _ => None,
323        }
324    }
325
326    /// 尝试获取数组
327    pub fn as_array(&self) -> Option<&Vec<TagValue>> {
328        match self {
329            Self::Array(arr) => Some(arr),
330            _ => None,
331        }
332    }
333
334    /// 转换为字符串表示
335    pub fn to_string_lossy(&self) -> String {
336        match self {
337            Self::String(s) => s.clone(),
338            Self::Integer(i) => i.to_string(),
339            Self::Float(f) => f.to_string(),
340            Self::Boolean(b) => b.to_string(),
341            Self::Array(arr) => {
342                let items: Vec<String> = arr.iter().map(|v| v.to_string_lossy()).collect();
343                format!("[{}]", items.join(", "))
344            }
345            Self::Binary(b) => format!("[binary: {} bytes]", b.len()),
346            Self::Null => "null".to_string(),
347        }
348    }
349
350    /// 检查是否为空
351    pub fn is_null(&self) -> bool {
352        matches!(self, Self::Null)
353    }
354}
355
356impl fmt::Display for TagValue {
357    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358        write!(f, "{}", self.to_string_lossy())
359    }
360}
361
362/// 元数据结构
363#[derive(Debug, Clone, Default, Serialize, Deserialize)]
364pub struct Metadata {
365    /// 顶层标签
366    #[serde(flatten)]
367    tags: HashMap<String, TagValue>,
368
369    /// 分组标签(如 EXIF、IPTC、XMP 等)
370    #[serde(skip)]
371    groups: HashMap<String, Metadata>,
372}
373
374impl Metadata {
375    /// 创建空的元数据
376    pub fn new() -> Self {
377        Self::default()
378    }
379
380    /// 获取标签值
381    pub fn get(&self, tag: &str) -> Option<&TagValue> {
382        self.tags.get(tag)
383    }
384
385    /// 获取标签值(使用 TagId)
386    pub fn get_tag(&self, tag: TagId) -> Option<&TagValue> {
387        self.get(tag.name())
388    }
389
390    /// 设置标签值
391    pub fn set(&mut self, tag: impl Into<String>, value: impl Into<TagValue>) {
392        self.tags.insert(tag.into(), value.into());
393    }
394
395    /// 设置标签值(使用 TagId)
396    pub fn set_tag(&mut self, tag: TagId, value: impl Into<TagValue>) {
397        self.set(tag.name(), value);
398    }
399
400    /// 获取所有标签
401    pub fn tags(&self) -> &HashMap<String, TagValue> {
402        &self.tags
403    }
404
405    /// 获取所有标签(可变)
406    pub fn tags_mut(&mut self) -> &mut HashMap<String, TagValue> {
407        &mut self.tags
408    }
409
410    /// 获取分组
411    pub fn group(&self, name: &str) -> Option<&Metadata> {
412        self.groups.get(name)
413    }
414
415    /// 设置分组
416    pub fn set_group(&mut self, name: impl Into<String>, metadata: Metadata) {
417        self.groups.insert(name.into(), metadata);
418    }
419
420    /// 获取所有分组
421    pub fn groups(&self) -> &HashMap<String, Metadata> {
422        &self.groups
423    }
424
425    /// 检查是否包含标签
426    pub fn contains(&self, tag: &str) -> bool {
427        self.tags.contains_key(tag)
428    }
429
430    /// 检查是否包含标签(使用 TagId)
431    pub fn contains_tag(&self, tag: TagId) -> bool {
432        self.contains(tag.name())
433    }
434
435    /// 获取标签数量
436    pub fn len(&self) -> usize {
437        self.tags.len()
438    }
439
440    /// 检查是否为空
441    pub fn is_empty(&self) -> bool {
442        self.tags.is_empty()
443    }
444
445    /// 合并另一个元数据
446    pub fn merge(&mut self, other: Metadata) {
447        self.tags.extend(other.tags);
448        self.groups.extend(other.groups);
449    }
450
451    /// 遍历所有标签
452    pub fn iter(&self) -> impl Iterator<Item = (&String, &TagValue)> {
453        self.tags.iter()
454    }
455}
456
457impl IntoIterator for Metadata {
458    type Item = (String, TagValue);
459    type IntoIter = std::collections::hash_map::IntoIter<String, TagValue>;
460
461    fn into_iter(self) -> Self::IntoIter {
462        self.tags.into_iter()
463    }
464}
465
466impl<'a> IntoIterator for &'a Metadata {
467    type Item = (&'a String, &'a TagValue);
468    type IntoIter = std::collections::hash_map::Iter<'a, String, TagValue>;
469
470    fn into_iter(self) -> Self::IntoIter {
471        self.tags.iter()
472    }
473}
474
475// 类型转换实现
476impl From<String> for TagValue {
477    fn from(s: String) -> Self {
478        Self::String(s)
479    }
480}
481
482impl From<&str> for TagValue {
483    fn from(s: &str) -> Self {
484        Self::String(s.to_string())
485    }
486}
487
488impl From<i64> for TagValue {
489    fn from(i: i64) -> Self {
490        Self::Integer(i)
491    }
492}
493
494impl From<i32> for TagValue {
495    fn from(i: i32) -> Self {
496        Self::Integer(i as i64)
497    }
498}
499
500impl From<f64> for TagValue {
501    fn from(f: f64) -> Self {
502        Self::Float(f)
503    }
504}
505
506impl From<f32> for TagValue {
507    fn from(f: f32) -> Self {
508        Self::Float(f as f64)
509    }
510}
511
512impl From<bool> for TagValue {
513    fn from(b: bool) -> Self {
514        Self::Boolean(b)
515    }
516}
517
518impl From<Vec<TagValue>> for TagValue {
519    fn from(arr: Vec<TagValue>) -> Self {
520        Self::Array(arr)
521    }
522}
523
524impl<T: Into<TagValue>> From<Option<T>> for TagValue {
525    fn from(opt: Option<T>) -> Self {
526        match opt {
527            Some(v) => v.into(),
528            None => Self::Null,
529        }
530    }
531}
532
533#[cfg(test)]
534mod tests {
535    use super::*;
536
537    #[test]
538    fn test_tag_id() {
539        assert_eq!(TagId::MAKE.name(), "Make");
540        assert_eq!(TagId::MODEL.name(), "Model");
541    }
542
543    #[test]
544    fn test_tag_value_conversions() {
545        let str_val: TagValue = "test".into();
546        assert_eq!(str_val.as_string(), Some(&"test".to_string()));
547
548        let int_val: TagValue = 42i64.into();
549        assert_eq!(int_val.as_integer(), Some(42));
550
551        let float_val: TagValue = std::f64::consts::PI.into();
552        assert_eq!(float_val.as_float(), Some(std::f64::consts::PI));
553
554        let bool_val: TagValue = true.into();
555        assert_eq!(bool_val.as_bool(), Some(true));
556    }
557
558    #[test]
559    fn test_metadata() {
560        let mut meta = Metadata::new();
561        meta.set("Make", "Canon");
562        meta.set("Model", "EOS 5D");
563
564        assert_eq!(meta.len(), 2);
565        assert!(meta.contains("Make"));
566        assert_eq!(
567            meta.get("Make"),
568            Some(&TagValue::String("Canon".to_string()))
569        );
570    }
571
572    #[test]
573    fn test_metadata_iteration() {
574        let mut meta = Metadata::new();
575        meta.set("A", 1);
576        meta.set("B", 2);
577
578        let mut count = 0;
579        for (key, _value) in &meta {
580            count += 1;
581            assert!(key == "A" || key == "B");
582        }
583        assert_eq!(count, 2);
584    }
585}