use crate::error::{Error, Result};
use crate::tag::{Tag, TagGroup, TagId};
use crate::value::Value;
pub fn read_mie(data: &[u8]) -> Result<Vec<Tag>> {
if data.len() < 4 || data[0] != b'~' {
return Err(Error::InvalidData("not a MIE file".into()));
}
let mut tags = Vec::new();
let mut pos = 0;
parse_mie_group(data, &mut pos, "MIE-Top", &mut tags)?;
Ok(tags)
}
fn parse_mie_group(
data: &[u8],
pos: &mut usize,
group_name: &str,
tags: &mut Vec<Tag>,
) -> Result<()> {
while *pos + 4 <= data.len() {
let sync = data[*pos];
if sync != b'~' {
break;
}
let format = data[*pos + 1];
let tag_len = data[*pos + 2] as usize;
let mut val_len = data[*pos + 3] as usize;
*pos += 4;
if tag_len > 0 && *pos + tag_len > data.len() {
break;
}
let tag_name = if tag_len > 0 {
let name =
crate::encoding::decode_utf8_or_latin1(&data[*pos..*pos + tag_len]).to_string();
*pos += tag_len;
name
} else {
String::new()
};
if val_len > 252 {
let n = 1usize << (256 - val_len);
if *pos + n > data.len() {
break;
}
val_len = match n {
1 => data[*pos] as usize,
2 => u16::from_be_bytes([data[*pos], data[*pos + 1]]) as usize,
4 => {
u32::from_be_bytes([data[*pos], data[*pos + 1], data[*pos + 2], data[*pos + 3]])
as usize
}
_ => break,
};
*pos += n;
}
if tag_name.is_empty() {
*pos += val_len; break;
}
if *pos + val_len > data.len() {
break;
}
let format_type = format & 0xfb; let _is_compressed = format & 0x04 != 0;
if format_type == 0x10 || format_type == 0x18 {
let child_group = resolve_subdir_group(group_name, &tag_name);
if val_len > 0 {
let value_data = &data[*pos..*pos + val_len];
match tag_name.as_str() {
"EXIF" => {
if let Ok(exif_tags) = crate::metadata::ExifReader::read(value_data) {
tags.extend(exif_tags);
}
}
"XMP" => {
if let Ok(xmp_tags) = crate::metadata::XmpReader::read(value_data) {
tags.extend(xmp_tags);
}
}
"IPTC" => {
if let Ok(iptc_tags) = crate::metadata::IptcReader::read(value_data) {
tags.extend(iptc_tags);
}
}
"ICCProfile" => {
if let Ok(icc_tags) = crate::formats::icc::read_icc(value_data) {
tags.extend(icc_tags);
}
}
_ => {
let mut sub_pos = 0;
let _ = parse_mie_group(value_data, &mut sub_pos, &child_group, tags);
}
}
*pos += val_len;
} else {
let _ = parse_mie_group(data, pos, &child_group, tags);
}
} else if format_type == 0x80 {
*pos += val_len;
} else {
let value_data = &data[*pos..*pos + val_len];
*pos += val_len;
let clean_tag = if let Some(paren) = tag_name.find('(') {
&tag_name[..paren]
} else {
&tag_name
};
let (resolved_name, family2) = resolve_tag_name(group_name, clean_tag);
let value = parse_mie_value(format_type, value_data);
tags.push(Tag {
id: TagId::Text(resolved_name.clone()),
name: resolved_name.clone(),
description: resolved_name.clone(),
group: TagGroup {
family0: "MIE".into(),
family1: group_name.into(),
family2,
},
print_value: value.to_display_string(),
raw_value: value,
priority: 0,
});
}
}
Ok(())
}
fn parse_mie_value(format_type: u8, data: &[u8]) -> Value {
match format_type {
0x20 | 0x28 => {
Value::String(
crate::encoding::decode_utf8_or_latin1(data)
.trim_end_matches('\0')
.to_string(),
)
}
0x30 | 0x38 => {
let s = crate::encoding::decode_utf8_or_latin1(data)
.trim_end_matches('\0')
.to_string();
Value::String(s.replace('\0', ", "))
}
0x40 => {
if data.len() == 1 {
Value::U8(data[0])
} else {
Value::Binary(data.to_vec())
}
}
0x41 => {
if data.len() >= 2 {
let mut vals = Vec::new();
let mut i = 0;
while i + 2 <= data.len() {
vals.push(u16::from_be_bytes([data[i], data[i + 1]]));
i += 2;
}
if vals.len() == 1 {
Value::U16(vals[0])
} else {
Value::String(
vals.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(" "),
)
}
} else {
Value::Binary(data.to_vec())
}
}
0x42 => {
if data.len() >= 4 {
Value::U32(u32::from_be_bytes([data[0], data[1], data[2], data[3]]))
} else {
Value::Binary(data.to_vec())
}
}
0x48 => {
if data.len() == 1 {
Value::I16(data[0] as i8 as i16)
} else {
Value::Binary(data.to_vec())
}
}
0x49 => {
if data.len() >= 2 {
Value::I16(i16::from_be_bytes([data[0], data[1]]))
} else {
Value::Binary(data.to_vec())
}
}
0x52 | 0x53 => {
if format_type == 0x53 && data.len() >= 8 {
let num = u32::from_be_bytes([data[0], data[1], data[2], data[3]]);
let den = u32::from_be_bytes([data[4], data[5], data[6], data[7]]);
Value::URational(num, den)
} else if format_type == 0x52 && data.len() >= 4 {
let num = u16::from_be_bytes([data[0], data[1]]) as u32;
let den = u16::from_be_bytes([data[2], data[3]]) as u32;
Value::URational(num, den)
} else {
Value::Binary(data.to_vec())
}
}
0x5a | 0x5b => {
if format_type == 0x5b && data.len() >= 8 {
let num = i32::from_be_bytes([data[0], data[1], data[2], data[3]]);
let den = i32::from_be_bytes([data[4], data[5], data[6], data[7]]);
Value::IRational(num, den)
} else {
Value::Binary(data.to_vec())
}
}
0x72 => {
if data.len() >= 4 {
let f = f32::from_be_bytes([data[0], data[1], data[2], data[3]]);
Value::String(format!("{}", f))
} else {
Value::Binary(data.to_vec())
}
}
0x73 => {
if data.len() >= 8 {
let f = f64::from_be_bytes([
data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7],
]);
Value::String(format!("{}", f))
} else {
Value::Binary(data.to_vec())
}
}
_ => {
if data.is_empty() {
Value::String(String::new())
} else {
Value::Binary(data.to_vec())
}
}
}
}
fn resolve_subdir_group(parent: &str, tag_name: &str) -> String {
match (parent, tag_name) {
("MIE-Top", "0MIE") => "MIE-Main".into(),
("MIE-Main", "Meta") => "MIE-Meta".into(),
("MIE-Meta", "Audio") => "MIE-Audio".into(),
("MIE-Meta", "Camera") => "MIE-Camera".into(),
("MIE-Meta", "Document") => "MIE-Doc".into(),
("MIE-Meta", "Geo") => "MIE-Geo".into(),
("MIE-Meta", "Image") => "MIE-Image".into(),
("MIE-Meta", "MakerNotes") => "MIE-MakerNotes".into(),
("MIE-Meta", "Preview") => "MIE-Preview".into(),
("MIE-Meta", "Thumbnail") => "MIE-Thumbnail".into(),
("MIE-Meta", "Video") => "MIE-Video".into(),
("MIE-Camera", "Flash") => "MIE-Flash".into(),
("MIE-Camera", "Lens") => "MIE-Lens".into(),
("MIE-Camera", "Orientation") => "MIE-Orient".into(),
("MIE-Geo", "GPS") => "MIE-GPS".into(),
("MIE-Geo", "UTM") => "MIE-UTM".into(),
("MIE-Lens", "Extender") => "MIE-Extender".into(),
("MIE-MakerNotes", "Canon") => "MIE-Canon".into(),
_ => format!("MIE-{}", tag_name),
}
}
fn resolve_tag_name(group: &str, tag: &str) -> (String, String) {
match group {
"MIE-Main" => {
let name = match tag {
"0Type" => "SubfileType",
"0Vers" => "MIEVersion",
"1Directory" => "SubfileDirectory",
"1Name" => "SubfileName",
"2MIME" => "SubfileMIMEType",
"data" => "SubfileData",
"rsrc" => "SubfileResource",
"zmd5" => "MD5Digest",
"zmie" => "TrailerSignature",
_ => tag,
};
(name.into(), "Other".into())
}
"MIE-Doc" => {
let name = match tag {
"Author" => "Author",
"Comment" => "Comment",
"Contributors" => "Contributors",
"Copyright" => "Copyright",
"CreateDate" => "CreateDate",
"EMail" => "Email",
"Keywords" => "Keywords",
"ModifyDate" => "ModifyDate",
"OriginalDate" => "DateTimeOriginal",
"Phone" => "PhoneNumber",
"References" => "References",
"Software" => "Software",
"Title" => "Title",
"URL" => "URL",
_ => tag,
};
(name.into(), "Document".into())
}
"MIE-Geo" => (tag.into(), "Location".into()),
"MIE-GPS" => {
let name = match tag {
"Altitude" => "GPSAltitude",
"Bearing" => "GPSDestBearing",
"Datum" => "GPSMapDatum",
"Differential" => "GPSDifferential",
"Distance" => "GPSDestDistance",
"Heading" => "GPSTrack",
"Latitude" => "GPSLatitude",
"Longitude" => "GPSLongitude",
"MeasureMode" => "GPSMeasureMode",
"Satellites" => "GPSSatellites",
"Speed" => "GPSSpeed",
"DateTime" => "GPSDateTime",
_ => tag,
};
(name.into(), "Location".into())
}
"MIE-Image" => {
let name = match tag {
"0Type" => "FullSizeImageType",
"1Name" => "FullSizeImageName",
"BitDepth" => "BitDepth",
"ColorSpace" => "ColorSpace",
"Components" => "ComponentsConfiguration",
"Compression" => "CompressionRatio",
"ImageSize" => "ImageSize",
"Resolution" => "Resolution",
"data" => "FullSizeImage",
_ => tag,
};
(name.into(), "Image".into())
}
"MIE-Preview" => {
let name = match tag {
"0Type" => "PreviewImageType",
"1Name" => "PreviewImageName",
"ImageSize" => "PreviewImageSize",
"data" => "PreviewImage",
_ => tag,
};
(name.into(), "Image".into())
}
"MIE-Thumbnail" => {
let name = match tag {
"0Type" => "ThumbnailImageType",
"1Name" => "ThumbnailImageName",
"ImageSize" => "ThumbnailImageSize",
"data" => "ThumbnailImage",
_ => tag,
};
(name.into(), "Image".into())
}
"MIE-Camera" => {
let name = match tag {
"Brightness" => "Brightness",
"ColorTemperature" => "ColorTemperature",
"Contrast" => "Contrast",
"DigitalZoom" => "DigitalZoom",
"ExposureComp" => "ExposureCompensation",
"ExposureMode" => "ExposureMode",
"ExposureTime" => "ExposureTime",
"FirmwareVersion" => "FirmwareVersion",
"FocusMode" => "FocusMode",
"ISO" => "ISO",
"ISOSetting" => "ISOSetting",
"ImageNumber" => "ImageNumber",
"ImageQuality" => "ImageQuality",
"ImageStabilization" => "ImageStabilization",
"Make" => "Make",
"MeasuredEV" => "MeasuredEV",
"Model" => "Model",
"OwnerName" => "OwnerName",
"Saturation" => "Saturation",
"SerialNumber" => "SerialNumber",
"Sharpness" => "Sharpness",
"ShootingMode" => "ShootingMode",
_ => tag,
};
(name.into(), "Camera".into())
}
"MIE-Lens" => {
let name = match tag {
"FNumber" => "FNumber",
"FocalLength" => "FocalLength",
"FocusDistance" => "FocusDistance",
"Make" => "LensMake",
"MaxAperture" => "MaxAperture",
"MaxApertureAtMaxFocal" => "MaxApertureAtMaxFocal",
"MaxFocalLength" => "MaxFocalLength",
"MinAperture" => "MinAperture",
"MinFocalLength" => "MinFocalLength",
"Model" => "LensModel",
"OpticalZoom" => "OpticalZoom",
"SerialNumber" => "LensSerialNumber",
_ => tag,
};
(name.into(), "Camera".into())
}
"MIE-Flash" => {
let name = match tag {
"ExposureComp" => "FlashExposureComp",
"Fired" => "FlashFired",
"GuideNumber" => "FlashGuideNumber",
"Make" => "FlashMake",
"Mode" => "FlashMode",
"Model" => "FlashModel",
"SerialNumber" => "FlashSerialNumber",
"Type" => "FlashType",
_ => tag,
};
(name.into(), "Camera".into())
}
"MIE-Orient" => (tag.into(), "Camera".into()),
"MIE-Audio" => {
let name = match tag {
"0Type" => "RelatedAudioFileType",
"1Name" => "RelatedAudioFileName",
"SampleBits" => "SampleBits",
"Channels" => "Channels",
"Compression" => "AudioCompression",
"Duration" => "Duration",
"SampleRate" => "SampleRate",
"data" => "RelatedAudioFile",
_ => tag,
};
(name.into(), "Audio".into())
}
"MIE-Video" => {
let name = match tag {
"0Type" => "RelatedVideoFileType",
"1Name" => "RelatedVideoFileName",
"Codec" => "Codec",
"Duration" => "Duration",
"data" => "RelatedVideoFile",
_ => tag,
};
(name.into(), "Video".into())
}
_ => (tag.into(), "Other".into()),
}
}