1use crate::exif::ExifReader;
11use crate::raw::{utils, RawFormatHandler};
12use crate::types::{ExifError, Result, TagSourceInfo, TagValue};
13use std::collections::HashMap;
14
15pub struct KyoceraRawHandler {
18 tag_definitions: HashMap<u16, KyoceraTagDef>,
21}
22
23#[derive(Debug, Clone)]
26struct KyoceraTagDef {
27 #[allow(dead_code)] name: String,
31 offset: usize,
34 format: KyoceraFormat,
37 conversion: Option<KyoceraConversion>,
40}
41
42#[derive(Debug, Clone)]
45enum KyoceraFormat {
46 String(usize),
49 Int32u,
52 Int32uArray(usize),
55}
56
57#[derive(Debug, Clone)]
60enum KyoceraConversion {
61 ReverseString,
64 ExposureTime,
67 FNumber,
70 MaxAperture,
73 DateTime,
76 FocalLength,
79 IsoLookup,
82}
83
84impl Default for KyoceraRawHandler {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90impl KyoceraRawHandler {
91 pub fn new() -> Self {
94 let mut tag_definitions = HashMap::new();
95
96 tag_definitions.insert(
102 0x01,
103 KyoceraTagDef {
104 name: "FirmwareVersion".to_string(),
105 offset: 0x01,
106 format: KyoceraFormat::String(10),
107 conversion: Some(KyoceraConversion::ReverseString),
108 },
109 );
110
111 tag_definitions.insert(
114 0x0c,
115 KyoceraTagDef {
116 name: "Model".to_string(),
117 offset: 0x0c,
118 format: KyoceraFormat::String(12),
119 conversion: Some(KyoceraConversion::ReverseString),
120 },
121 );
122
123 tag_definitions.insert(
126 0x19,
127 KyoceraTagDef {
128 name: "Make".to_string(),
129 offset: 0x19,
130 format: KyoceraFormat::String(7),
131 conversion: Some(KyoceraConversion::ReverseString),
132 },
133 );
134
135 tag_definitions.insert(
138 0x21,
139 KyoceraTagDef {
140 name: "DateTimeOriginal".to_string(),
141 offset: 0x21,
142 format: KyoceraFormat::String(20),
143 conversion: Some(KyoceraConversion::DateTime),
144 },
145 );
146
147 tag_definitions.insert(
150 0x34,
151 KyoceraTagDef {
152 name: "ISO".to_string(),
153 offset: 0x34,
154 format: KyoceraFormat::Int32u,
155 conversion: Some(KyoceraConversion::IsoLookup),
156 },
157 );
158
159 tag_definitions.insert(
162 0x38,
163 KyoceraTagDef {
164 name: "ExposureTime".to_string(),
165 offset: 0x38,
166 format: KyoceraFormat::Int32u,
167 conversion: Some(KyoceraConversion::ExposureTime),
168 },
169 );
170
171 tag_definitions.insert(
174 0x3c,
175 KyoceraTagDef {
176 name: "WB_RGGBLevels".to_string(),
177 offset: 0x3c,
178 format: KyoceraFormat::Int32uArray(4),
179 conversion: None,
180 },
181 );
182
183 tag_definitions.insert(
186 0x58,
187 KyoceraTagDef {
188 name: "FNumber".to_string(),
189 offset: 0x58,
190 format: KyoceraFormat::Int32u,
191 conversion: Some(KyoceraConversion::FNumber),
192 },
193 );
194
195 tag_definitions.insert(
198 0x68,
199 KyoceraTagDef {
200 name: "MaxAperture".to_string(),
201 offset: 0x68,
202 format: KyoceraFormat::Int32u,
203 conversion: Some(KyoceraConversion::MaxAperture),
204 },
205 );
206
207 tag_definitions.insert(
210 0x70,
211 KyoceraTagDef {
212 name: "FocalLength".to_string(),
213 offset: 0x70,
214 format: KyoceraFormat::Int32u,
215 conversion: Some(KyoceraConversion::FocalLength),
216 },
217 );
218
219 tag_definitions.insert(
222 0x7c,
223 KyoceraTagDef {
224 name: "Lens".to_string(),
225 offset: 0x7c,
226 format: KyoceraFormat::String(32),
227 conversion: None,
228 },
229 );
230
231 Self { tag_definitions }
232 }
233
234 fn extract_value(
237 &self,
238 data: &[u8],
239 offset: usize,
240 format: &KyoceraFormat,
241 ) -> Result<TagValue> {
242 match format {
243 KyoceraFormat::String(length) => {
244 if offset + length > data.len() {
245 return Err(ExifError::ParseError(format!(
246 "String at offset {offset:#x} extends beyond data bounds"
247 )));
248 }
249 let string_bytes = &data[offset..offset + length];
250 let string_value = String::from_utf8_lossy(string_bytes).to_string();
251 Ok(TagValue::String(string_value))
252 }
253 KyoceraFormat::Int32u => {
254 if offset + 4 > data.len() {
255 return Err(ExifError::ParseError(format!(
256 "Int32u at offset {offset:#x} extends beyond data bounds"
257 )));
258 }
259 let value = u32::from_be_bytes([
262 data[offset],
263 data[offset + 1],
264 data[offset + 2],
265 data[offset + 3],
266 ]);
267 Ok(TagValue::U32(value))
268 }
269 KyoceraFormat::Int32uArray(count) => {
270 let total_bytes = count * 4;
271 if offset + total_bytes > data.len() {
272 return Err(ExifError::ParseError(format!(
273 "Int32u array at offset {offset:#x} extends beyond data bounds"
274 )));
275 }
276
277 let mut values = Vec::new();
278 for i in 0..*count {
279 let item_offset = offset + i * 4;
280 let value = u32::from_be_bytes([
281 data[item_offset],
282 data[item_offset + 1],
283 data[item_offset + 2],
284 data[item_offset + 3],
285 ]);
286 values.push(value);
287 }
288 Ok(TagValue::U32Array(values))
289 }
290 }
291 }
292
293 fn apply_conversion(&self, value: &TagValue, conversion: &KyoceraConversion) -> TagValue {
296 match conversion {
297 KyoceraConversion::ReverseString => {
298 if let TagValue::String(s) = value {
299 TagValue::String(utils::reverse_string(s.as_bytes()))
300 } else {
301 value.clone()
302 }
303 }
304 KyoceraConversion::ExposureTime => {
305 if let TagValue::U32(val) = value {
306 let exposure_time = utils::kyocera_exposure_time(*val);
307 TagValue::F64(exposure_time)
308 } else {
309 value.clone()
310 }
311 }
312 KyoceraConversion::FNumber | KyoceraConversion::MaxAperture => {
313 if let TagValue::U32(val) = value {
314 let fnumber = utils::kyocera_fnumber(*val);
315 TagValue::F64(fnumber)
316 } else {
317 value.clone()
318 }
319 }
320 KyoceraConversion::DateTime => {
321 if let TagValue::String(s) = value {
322 let reversed = utils::reverse_string(s.as_bytes());
324 TagValue::String(reversed)
327 } else {
328 value.clone()
329 }
330 }
331 KyoceraConversion::FocalLength => {
332 if let TagValue::U32(val) = value {
333 TagValue::String(format!("{val} mm"))
334 } else {
335 value.clone()
336 }
337 }
338 KyoceraConversion::IsoLookup => {
339 if let TagValue::U32(val) = value {
340 if let Some(iso_speed) = utils::kyocera_iso_lookup(*val) {
341 TagValue::U32(iso_speed)
342 } else {
343 value.clone() }
345 } else {
346 value.clone()
347 }
348 }
349 }
350 }
351}
352
353impl RawFormatHandler for KyoceraRawHandler {
354 fn process_raw(&self, reader: &mut ExifReader, data: &[u8]) -> Result<()> {
357 if data.len() < 156 {
360 return Err(ExifError::ParseError(format!(
361 "Kyocera RAW data too short: {} bytes (expected 156)",
362 data.len()
363 )));
364 }
365
366 for (&tag_id, tag_def) in &self.tag_definitions {
368 let raw_value = self.extract_value(data, tag_def.offset, &tag_def.format)?;
370
371 let final_value = if let Some(conversion) = &tag_def.conversion {
373 self.apply_conversion(&raw_value, conversion)
374 } else {
375 raw_value
376 };
377
378 let group2 = match tag_def.name.as_str() {
382 "DateTimeOriginal" => "Time",
383 "ISO" | "ExposureTime" | "FNumber" | "MaxAperture" | "FocalLength"
384 | "WB_RGGBLevels" => "Image",
385 _ => "Camera", };
387
388 let source_info = TagSourceInfo::new(
389 "MakerNotes".to_string(),
390 "KyoceraRaw".to_string(),
391 group2.to_string(),
392 );
393
394 reader.extracted_tags.insert(tag_id, final_value);
395 reader.tag_sources.insert(tag_id, source_info);
396 }
397
398 Ok(())
399 }
400
401 fn name(&self) -> &'static str {
402 "KyoceraRaw"
403 }
404
405 fn validate_format(&self, data: &[u8]) -> bool {
406 super::super::detector::validate_kyocera_magic(data)
409 }
410}
411
412pub fn get_kyocera_tag_name(tag_id: u16) -> Option<&'static str> {
415 match tag_id {
416 0x01 => Some("FirmwareVersion"),
417 0x0c => Some("Model"),
418 0x19 => Some("Make"),
419 0x21 => Some("DateTimeOriginal"),
420 0x34 => Some("ISO"),
421 0x38 => Some("ExposureTime"),
422 0x3c => Some("WB_RGGBLevels"),
423 0x58 => Some("FNumber"),
424 0x68 => Some("MaxAperture"),
425 0x70 => Some("FocalLength"),
426 0x7c => Some("Lens"),
427 _ => None,
428 }
429}
430
431#[cfg(test)]
432mod tests {
433 use super::*;
434
435 #[test]
436 fn test_kyocera_handler_creation() {
437 let handler = KyoceraRawHandler::new();
438 assert_eq!(handler.name(), "KyoceraRaw");
439
440 assert_eq!(handler.tag_definitions.len(), 11);
442
443 assert!(handler.tag_definitions.contains_key(&0x19)); assert!(handler.tag_definitions.contains_key(&0x34)); assert!(handler.tag_definitions.contains_key(&0x38)); }
448
449 #[test]
450 fn test_get_kyocera_tag_name() {
451 assert_eq!(get_kyocera_tag_name(0x19), Some("Make"));
453 assert_eq!(get_kyocera_tag_name(0x34), Some("ISO"));
454 assert_eq!(get_kyocera_tag_name(0x38), Some("ExposureTime"));
455 assert_eq!(get_kyocera_tag_name(0x01), Some("FirmwareVersion"));
456 assert_eq!(get_kyocera_tag_name(0x0c), Some("Model"));
457
458 assert_eq!(get_kyocera_tag_name(0x99), None);
460 }
461
462 #[test]
463 fn test_value_extraction() {
464 let handler = KyoceraRawHandler::new();
465
466 let mut data = vec![0u8; 20];
468 data[10..17].copy_from_slice(b"ARECOYK");
469
470 let result = handler
471 .extract_value(&data, 10, &KyoceraFormat::String(7))
472 .unwrap();
473 if let TagValue::String(s) = result {
474 assert!(s.starts_with("ARECOYK"));
475 } else {
476 panic!("Expected String value");
477 }
478
479 let mut data = vec![0u8; 10];
481 data[4..8].copy_from_slice(&[0x00, 0x00, 0x01, 0x00]); let result = handler
484 .extract_value(&data, 4, &KyoceraFormat::Int32u)
485 .unwrap();
486 if let TagValue::U32(val) = result {
487 assert_eq!(val, 256);
488 } else {
489 panic!("Expected U32 value");
490 }
491 }
492
493 #[test]
494 fn test_conversions() {
495 let handler = KyoceraRawHandler::new();
496
497 let input = TagValue::String("KYOCERA\0".to_string());
499 let result = handler.apply_conversion(&input, &KyoceraConversion::ReverseString);
500 if let TagValue::String(s) = result {
501 assert!(s.starts_with("ARECOYK"));
502 } else {
503 panic!("Expected String value");
504 }
505
506 let input = TagValue::U32(13); let result = handler.apply_conversion(&input, &KyoceraConversion::IsoLookup);
509 if let TagValue::U32(iso) = result {
510 assert_eq!(iso, 100);
511 } else {
512 panic!("Expected U32 value");
513 }
514
515 let input = TagValue::U32(50);
517 let result = handler.apply_conversion(&input, &KyoceraConversion::FocalLength);
518 if let TagValue::String(s) = result {
519 assert_eq!(s, "50 mm");
520 } else {
521 panic!("Expected String value");
522 }
523 }
524
525 #[test]
526 fn test_format_validation() {
527 let handler = KyoceraRawHandler::new();
528
529 let mut valid_data = vec![0u8; 156];
531 valid_data[0x19..0x19 + 7].copy_from_slice(b"ARECOYK");
532
533 assert!(handler.validate_format(&valid_data));
534
535 let invalid_data = vec![0u8; 100]; assert!(!handler.validate_format(&invalid_data));
538
539 let mut wrong_magic = vec![0u8; 156];
540 wrong_magic[0x19..0x19 + 7].copy_from_slice(b"WRONGXY");
541 assert!(!handler.validate_format(&wrong_magic));
542 }
543}