use crate::tag::{Tag, TagGroup, TagId};
use crate::value::Value;
fn mk(name: &str, value: Value) -> Tag {
let pv = value.to_display_string();
Tag {
id: TagId::Text(name.to_string()),
name: name.to_string(),
description: name.to_string(),
group: TagGroup {
family0: "ISO".into(),
family1: "ISO".into(),
family2: "Other".into(),
},
raw_value: value,
print_value: pv,
priority: 0,
}
}
fn mk_with_print(name: &str, raw: Value, print: String) -> Tag {
Tag {
id: TagId::Text(name.to_string()),
name: name.to_string(),
description: name.to_string(),
group: TagGroup {
family0: "ISO".into(),
family1: "ISO".into(),
family2: "Other".into(),
},
raw_value: raw,
print_value: print,
priority: 0,
}
}
fn trim_spaces(s: &str) -> &str {
s.trim_end_matches(' ')
}
fn parse_iso_datetime(data: &[u8]) -> Option<String> {
if data.len() < 17 {
return None;
}
if data[..16].iter().all(|&b| b == b'0' || b == 0 || b == b' ') {
return None;
}
let s = std::str::from_utf8(&data[..16]).ok()?;
if s.chars().all(|c| c == '0' || c == ' ') {
return None;
}
let year = &s[0..4];
let month = &s[4..6];
let day = &s[6..8];
let hour = &s[8..10];
let min = &s[10..12];
let sec = &s[12..14];
let csec = &s[14..16];
let tz_byte = data[16] as i8;
let tz_mins = tz_byte as i32 * 15;
let tz_str = if tz_mins == 0 {
"+00:00".to_string()
} else {
let sign = if tz_mins >= 0 { "+" } else { "-" };
let abs = tz_mins.abs();
format!("{}{:02}:{:02}", sign, abs / 60, abs % 60)
};
Some(format!(
"{}:{}:{} {}:{}:{}.{}{}",
year, month, day, hour, min, sec, csec, tz_str
))
}
fn parse_short_datetime(data: &[u8]) -> Option<String> {
if data.len() < 7 {
return None;
}
let year = data[0] as u32 + 1900;
let month = data[1];
let day = data[2];
let hour = data[3];
let min = data[4];
let sec = data[5];
let tz_byte = data[6] as i8;
let tz_mins = tz_byte as i32 * 15;
let tz_str = if tz_mins == 0 {
"+00:00".to_string()
} else {
let sign = if tz_mins >= 0 { "+" } else { "-" };
let abs = tz_mins.abs();
format!("{}{:02}:{:02}", sign, abs / 60, abs % 60)
};
Some(format!(
"{:04}:{:02}:{:02} {:02}:{:02}:{:02}{}",
year, month, day, hour, min, sec, tz_str
))
}
fn format_file_size(bytes: u64) -> String {
if bytes >= 1_073_741_824 {
format!("{:.0} GB", bytes as f64 / 1_073_741_824.0)
} else if bytes >= 1_048_576 {
format!("{:.0} MB", bytes as f64 / 1_048_576.0)
} else if bytes >= 1024 {
format!("{:.1} kB", bytes as f64 / 1024.0)
} else {
format!("{} bytes", bytes)
}
}
fn read_le_u32(data: &[u8], off: usize) -> u32 {
if off + 4 > data.len() {
return 0;
}
u32::from_le_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]])
}
fn read_le_u16(data: &[u8], off: usize) -> u16 {
if off + 2 > data.len() {
return 0;
}
u16::from_le_bytes([data[off], data[off + 1]])
}
pub fn read_iso(data: &[u8]) -> crate::error::Result<Vec<Tag>> {
if data.len() < 32768 + 2048 {
return Ok(Vec::new());
}
let mut tags = Vec::new();
let mut offset = 32768usize;
while offset + 2048 <= data.len() {
let sector = &data[offset..offset + 2048];
let vol_type = sector[0];
if §or[1..6] != b"CD001" {
break;
}
match vol_type {
0 => {
let boot_system = trim_spaces(std::str::from_utf8(§or[7..39]).unwrap_or(""));
tags.push(mk("BootSystem", Value::String(boot_system.to_string())));
}
1 => {
let vol_name = trim_spaces(
std::str::from_utf8(§or[40..72])
.unwrap_or("")
.trim_end_matches('\0'),
);
if !vol_name.is_empty() {
tags.push(mk("VolumeName", Value::String(vol_name.to_string())));
}
let block_count = read_le_u32(sector, 80);
tags.push(mk("VolumeBlockCount", Value::U32(block_count)));
let block_size = read_le_u16(sector, 128);
tags.push(mk("VolumeBlockSize", Value::U16(block_size)));
if sector.len() > 181 {
if let Some(dt) = parse_short_datetime(§or[174..181]) {
tags.push(mk("RootDirectoryCreateDate", Value::String(dt)));
}
}
let set_name = trim_spaces(
std::str::from_utf8(§or[190..318])
.unwrap_or("")
.trim_end_matches('\0'),
);
if !set_name.is_empty() {
tags.push(mk("VolumeSetName", Value::String(set_name.to_string())));
}
let publisher = trim_spaces(
std::str::from_utf8(§or[318..446])
.unwrap_or("")
.trim_end_matches('\0'),
);
if !publisher.is_empty() {
tags.push(mk("Publisher", Value::String(publisher.to_string())));
}
let preparer = trim_spaces(
std::str::from_utf8(§or[446..574])
.unwrap_or("")
.trim_end_matches('\0'),
);
if !preparer.is_empty() {
tags.push(mk("DataPreparer", Value::String(preparer.to_string())));
}
let software = trim_spaces(
std::str::from_utf8(§or[574..702])
.unwrap_or("")
.trim_end_matches('\0'),
);
if !software.is_empty() {
tags.push(mk("Software", Value::String(software.to_string())));
}
let copyright_fn = trim_spaces(
std::str::from_utf8(§or[702..740])
.unwrap_or("")
.trim_end_matches('\0'),
);
if !copyright_fn.is_empty() {
tags.push(mk(
"CopyrightFileName",
Value::String(copyright_fn.to_string()),
));
}
let abstract_fn = trim_spaces(
std::str::from_utf8(§or[740..776])
.unwrap_or("")
.trim_end_matches('\0'),
);
if !abstract_fn.is_empty() {
tags.push(mk(
"AbstractFileName",
Value::String(abstract_fn.to_string()),
));
}
let biblio_fn = trim_spaces(
std::str::from_utf8(§or[776..813])
.unwrap_or("")
.trim_end_matches('\0'),
);
if !biblio_fn.is_empty() {
tags.push(mk(
"BibligraphicFileName",
Value::String(biblio_fn.to_string()),
));
}
if sector.len() > 830 {
if let Some(dt) = parse_iso_datetime(§or[813..830]) {
tags.push(mk("VolumeCreateDate", Value::String(dt)));
}
}
if sector.len() > 847 {
if let Some(dt) = parse_iso_datetime(§or[830..847]) {
tags.push(mk("VolumeModifyDate", Value::String(dt)));
}
}
let total_bytes = block_count as u64 * block_size as u64;
tags.push(mk_with_print(
"VolumeSize",
Value::String(total_bytes.to_string()),
format_file_size(total_bytes),
));
}
255 => {
break;
}
_ => {}
}
offset += 2048;
}
Ok(tags)
}