use crate::error::{Error, Result};
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: "Red".into(),
family1: "Red".into(),
family2: "Camera".into(),
},
raw_value: value,
print_value: pv,
priority: 0,
}
}
fn mk_print(name: &str, value: Value, print: String) -> Tag {
Tag {
id: TagId::Text(name.to_string()),
name: name.to_string(),
description: name.to_string(),
group: TagGroup {
family0: "Red".into(),
family1: "Red".into(),
family2: "Camera".into(),
},
raw_value: value,
print_value: print,
priority: 0,
}
}
fn read_u16_be(data: &[u8], off: usize) -> u16 {
if off + 2 > data.len() {
return 0;
}
u16::from_be_bytes([data[off], data[off + 1]])
}
fn read_u32_be(data: &[u8], off: usize) -> u32 {
if off + 4 > data.len() {
return 0;
}
u32::from_be_bytes([data[off], data[off + 1], data[off + 2], data[off + 3]])
}
fn read_i32_be(data: &[u8], off: usize) -> i32 {
read_u32_be(data, off) as i32
}
fn read_f32_be(data: &[u8], off: usize) -> f32 {
if off + 4 > data.len() {
return 0.0;
}
f32::from_bits(u32::from_be_bytes([
data[off],
data[off + 1],
data[off + 2],
data[off + 3],
]))
}
fn read_str(data: &[u8], off: usize, len: usize) -> String {
if off + len > data.len() {
return String::new();
}
let s = &data[off..off + len];
let end = s.iter().position(|&b| b == 0).unwrap_or(len);
crate::encoding::decode_utf8_or_latin1(&s[..end])
.trim()
.to_string()
}
fn convert_date(val: &str) -> String {
let v = val.replace('_', " ");
if v.len() >= 8 {
format!("{}:{}:{}", &v[0..4], &v[4..6], &v[6..8])
} else if v.len() >= 6 {
format!("{}:{}", &v[0..4], &v[4..6])
} else {
v
}
}
fn convert_time(val: &str) -> String {
if val.len() >= 6 {
format!("{}:{}:{}", &val[0..2], &val[2..4], &val[4..6])
} else if val.len() >= 4 {
format!("{}:{}", &val[0..2], &val[2..4])
} else {
val.to_string()
}
}
fn convert_datetime(val: &str) -> String {
if val.len() >= 14 {
format!(
"{}:{}:{} {}:{}:{}",
&val[0..4],
&val[4..6],
&val[6..8],
&val[8..10],
&val[10..12],
&val[12..14]
)
} else {
val.to_string()
}
}
fn parse_red_dir(buff: &[u8], dir_start: usize, dir_end: usize, tags: &mut Vec<Tag>) {
let mut pos = dir_start;
while pos + 4 <= dir_end {
let len = read_u16_be(buff, pos) as usize;
if len < 4 || pos + len > dir_end {
break;
}
let tag = read_u16_be(buff, pos + 2) as u32;
let fmt_code = tag >> 12;
let data = &buff[pos + 4..pos + len];
match fmt_code {
1 => {
let s = {
let end = data.iter().position(|&b| b == 0).unwrap_or(data.len());
crate::encoding::decode_utf8_or_latin1(&data[..end])
.trim()
.to_string()
};
if !s.is_empty() {
match tag {
0x1000 => tags.push(mk("StartEdgeCode", Value::String(s))),
0x1001 => tags.push(mk("StartTimecode", Value::String(s))),
0x1005 => {
let dt = convert_datetime(&s);
tags.push(mk("DateTimeOriginal", Value::String(dt)));
}
0x1006 => tags.push(mk("SerialNumber", Value::String(s))),
0x1019 => tags.push(mk("CameraType", Value::String(s))),
0x101a => tags.push(mk("ReelNumber", Value::String(s))),
0x101b => tags.push(mk("Take", Value::String(s))),
0x1023 => {
let d = convert_date(&s);
tags.push(mk("DateCreated", Value::String(d)));
}
0x1024 => {
let t = convert_time(&s);
tags.push(mk("TimeCreated", Value::String(t)));
}
0x1025 => tags.push(mk("FirmwareVersion", Value::String(s))),
0x1029 => tags.push(mk("ReelTimecode", Value::String(s))),
0x102a => tags.push(mk("StorageType", Value::String(s))),
0x1030 => {
let d = convert_date(&s);
tags.push(mk("StorageFormatDate", Value::String(d)));
}
0x1031 => {
let t = convert_time(&s);
tags.push(mk("StorageFormatTime", Value::String(t)));
}
0x1032 => tags.push(mk("StorageSerialNumber", Value::String(s))),
0x1033 => tags.push(mk("StorageModel", Value::String(s))),
0x1036 => tags.push(mk("AspectRatio", Value::String(s))),
0x1056 => tags.push(mk("OriginalFileName", Value::String(s))),
0x106e => tags.push(mk("LensMake", Value::String(s))),
0x106f => tags.push(mk("LensNumber", Value::String(s))),
0x1070 => tags.push(mk("LensModel", Value::String(s))),
0x1071 => tags.push(mk("Model", Value::String(s))),
0x107c => tags.push(mk("CameraOperator", Value::String(s))),
0x1086 => tags.push(mk("VideoFormat", Value::String(s))),
0x1096 => tags.push(mk("Filter", Value::String(s))),
0x10a0 => tags.push(mk("Brain", Value::String(s))),
0x10a1 => tags.push(mk("Sensor", Value::String(s))),
0x10be => tags.push(mk("Quality", Value::String(s))),
_ => {}
}
}
}
2 => {
if data.len() >= 4 {
let f = read_f32_be(data, 0);
match tag {
0x200d => tags.push(mk("ColorTemperature", Value::F64(f as f64))),
0x204b => {
let count = data.len() / 4;
let mut vals = Vec::new();
for i in 0..count {
let v = read_f32_be(data, i * 4);
vals.push(format!("{}", v));
}
tags.push(mk("RGBCurves", Value::String(vals.join(" "))));
}
0x2066 => {
let print = format!("{}", (f * 1000.0 + 0.5) as u32 as f32 / 1000.0);
tags.push(mk_print("OriginalFrameRate", Value::F64(f as f64), print));
}
_ => {}
}
}
}
4 => {
if data.len() >= 2 {
match tag {
0x4037 => {
if data.len() >= 8 {
let a = read_u16_be(data, 0);
let b = read_u16_be(data, 2);
let c = read_u16_be(data, 4);
let d2 = read_u16_be(data, 6);
tags.push(mk(
"CropArea",
Value::String(format!("{} {} {} {}", a, b, c, d2)),
));
}
}
0x403b => {
let v = read_u16_be(data, 0);
tags.push(mk("ISO", Value::U16(v)));
}
0x406a => {
let v = read_u16_be(data, 0);
let f = v as f64 / 10.0;
tags.push(mk_print("FNumber", Value::F64(f), format!("{:.1}", f)));
}
0x406b => {
let v = read_u16_be(data, 0);
tags.push(mk("FocalLength", Value::U16(v)));
}
_ => {}
}
}
}
6 => {
if data.len() >= 4 && tag == 0x606c {
let v = read_i32_be(data, 0);
let meters = v as f64 / 1000.0;
tags.push(mk_print(
"FocusDistance",
Value::F64(meters),
format!("{} m", meters),
));
}
}
_ => {}
}
pos += len;
}
}
pub fn read_r3d(data: &[u8]) -> Result<Vec<Tag>> {
if data.len() < 8 {
return Err(Error::InvalidData("file too small for R3D".into()));
}
if data[0] != 0 || data[1] != 0 {
return Err(Error::InvalidData("not an R3D file".into()));
}
let ver = match &data[4..8] {
b"RED1" => 1u8,
b"RED2" => 2u8,
_ => return Err(Error::InvalidData("not an R3D file".into())),
};
let mut tags = Vec::new();
let block_size = read_u32_be(data, 0) as usize;
if block_size < 8 || block_size > data.len() {
return Err(Error::InvalidData("invalid R3D block size".into()));
}
match ver {
1 => {
if block_size >= 0x08 {
if 0x07 < block_size {
let rv = format!("{}", data[0x07] as char);
tags.push(mk("RedcodeVersion", Value::String(rv)));
}
if 0x36 + 2 <= block_size {
let w = read_u16_be(data, 0x36);
tags.push(mk("ImageWidth", Value::U16(w)));
}
if 0x3a + 2 <= block_size {
let h = read_u16_be(data, 0x3a);
tags.push(mk("ImageHeight", Value::U16(h)));
}
if 0x42 + 4 <= block_size {
let num = read_u32_be(data, 0x3e);
let den = read_u32_be(data, 0x42);
if den > 0 {
let fr = num as f64 / den as f64;
let print = format!("{:.3}", fr);
let print = print
.trim_end_matches('0')
.trim_end_matches('.')
.to_string();
tags.push(mk_print("FrameRate", Value::F64(fr), print));
}
}
if 0x43 + 32 <= block_size {
let fn_str = read_str(data, 0x43, 32);
if !fn_str.is_empty() {
tags.push(mk("OriginalFileName", Value::String(fn_str)));
}
}
}
let next_block_start = block_size;
if next_block_start + 0x22 <= data.len() {
let next_size = read_u32_be(data, next_block_start) as usize;
if next_size >= 8 && next_block_start + next_size <= data.len() {
let buff = &data[next_block_start..next_block_start + next_size];
let dir_start = 0x22;
if dir_start + 2 <= buff.len() {
let dir_len = read_u16_be(buff, dir_start) as usize;
let dir_content_start = dir_start + 2;
let dir_end = dir_content_start + dir_len;
let dir_end = dir_end.min(buff.len());
parse_red_dir(buff, dir_content_start, dir_end, &mut tags);
}
}
}
}
2 => {
if 0x07 < block_size {
let rv = format!("{}", data[0x07] as char);
tags.push(mk("RedcodeVersion", Value::String(rv)));
}
let rdi_count = if 0x40 < block_size {
data[0x40] as usize
} else {
0
};
let rda_count = if 0x41 < block_size {
data[0x41] as usize
} else {
0
};
let rdx_count = if 0x42 < block_size {
data[0x42] as usize
} else {
0
};
let first_rdi = 0x44;
if first_rdi + 0x18 <= block_size {
let w = read_u32_be(data, 0x4c);
tags.push(mk("ImageWidth", Value::U32(w)));
let h = read_u32_be(data, 0x50);
tags.push(mk("ImageHeight", Value::U32(h)));
if 0x56 + 6 <= block_size {
let a0 = read_u16_be(data, 0x56) as u32;
let a1 = read_u16_be(data, 0x58) as u32;
let a2 = read_u16_be(data, 0x5a) as u32;
if a0 > 0 {
let fr = (a1 * 0x10000 + a2) as f64 / a0 as f64;
let print = format!("{:.3}", fr);
let print = print
.trim_end_matches('0')
.trim_end_matches('.')
.to_string();
tags.push(mk_print("FrameRate", Value::F64(fr), print));
}
}
}
let dir_start = 0x44 + rdi_count * 0x18 + rda_count * 0x14 + rdx_count * 0x10;
if dir_start + 2 <= block_size {
let dir_len = read_u16_be(data, dir_start) as usize;
let dir_content_start = dir_start + 2;
let dir_end = dir_content_start + dir_len;
let dir_end = dir_end.min(block_size);
parse_red_dir(data, dir_content_start, dir_end, &mut tags);
}
}
_ => {}
}
Ok(tags)
}