exif_oxide/processor_registry/processors/
canon.rs1use super::super::{
12 BinaryDataProcessor, ProcessorCapability, ProcessorContext, ProcessorMetadata, ProcessorResult,
13};
14use crate::implementations::canon;
15use crate::types::{Result, TagValue};
16use std::collections::HashMap;
17use tracing::debug;
18
19pub struct CanonSerialDataProcessor;
29
30impl BinaryDataProcessor for CanonSerialDataProcessor {
31 fn can_process(&self, context: &ProcessorContext) -> ProcessorCapability {
32 if context.is_manufacturer("Canon") && context.table_name.contains("SerialData") {
34 return ProcessorCapability::Perfect;
35 }
36
37 if context.is_manufacturer("Canon") && context.table_name.contains("Binary") {
39 return ProcessorCapability::Good;
40 }
41
42 ProcessorCapability::Incompatible
44 }
45
46 fn process_data(&self, data: &[u8], context: &ProcessorContext) -> Result<ProcessorResult> {
47 debug!(
48 "Processing Canon SerialData with {} bytes for table: {}",
49 data.len(),
50 context.table_name
51 );
52
53 let mut result = ProcessorResult::new();
54
55 let byte_order = context
58 .byte_order
59 .unwrap_or(crate::tiff_types::ByteOrder::LittleEndian);
60 let camera_settings = canon::binary_data::extract_camera_settings(
61 data,
62 context.data_offset,
63 data.len(),
64 byte_order,
65 );
66
67 match camera_settings {
68 Ok(tags) => {
69 for (tag_name, tag_value) in tags {
71 result.add_tag(tag_name, tag_value);
72 }
73
74 debug!(
75 "Canon SerialData processor extracted {} tags",
76 result.extracted_tags.len()
77 );
78 }
79 Err(e) => {
80 let warning = format!("Canon SerialData processing failed: {e}");
81 result.add_warning(warning);
82 debug!("Canon SerialData processing error: {}", e);
83 }
84 }
85
86 Ok(result)
87 }
88
89 fn get_metadata(&self) -> ProcessorMetadata {
90 ProcessorMetadata::new(
91 "Canon Serial Data Processor".to_string(),
92 "Processes Canon camera serial data using existing implementation".to_string(),
93 )
94 .with_manufacturer("Canon".to_string())
95 .with_required_context("manufacturer".to_string())
96 .with_example_condition(
97 "manufacturer == 'Canon' && table.contains('SerialData')".to_string(),
98 )
99 }
100}
101
102pub struct CanonCameraSettingsProcessor;
111
112impl BinaryDataProcessor for CanonCameraSettingsProcessor {
113 fn can_process(&self, context: &ProcessorContext) -> ProcessorCapability {
114 if !context.is_manufacturer("Canon") {
117 return ProcessorCapability::Incompatible;
118 }
119
120 if context.table_name == "Canon::CameraSettings" {
122 return ProcessorCapability::Perfect;
123 }
124
125 if context.table_name.starts_with("Canon::") && context.table_name.contains("Settings") {
127 return ProcessorCapability::Good;
128 }
129
130 ProcessorCapability::Incompatible
132 }
133
134 fn process_data(&self, data: &[u8], context: &ProcessorContext) -> Result<ProcessorResult> {
135 debug!(
136 "Processing Canon CameraSettings with {} bytes for table: {}",
137 data.len(),
138 context.table_name
139 );
140
141 let mut result = ProcessorResult::new();
142
143 let table = canon::binary_data::create_canon_camera_settings_table();
145
146 let extracted_tags = extract_binary_data_using_table(data, context, &table)?;
149
150 for (tag_name, tag_value) in extracted_tags {
151 result.add_tag(tag_name, tag_value);
152 }
153
154 if result.extracted_tags.is_empty() {
155 result.add_warning("No Canon CameraSettings tags extracted".to_string());
156 } else {
157 debug!(
158 "Canon CameraSettings processor extracted {} tags",
159 result.extracted_tags.len()
160 );
161 }
162
163 Ok(result)
164 }
165
166 fn get_metadata(&self) -> ProcessorMetadata {
167 ProcessorMetadata::new(
168 "Canon Camera Settings Processor".to_string(),
169 "Processes Canon CameraSettings binary data table".to_string(),
170 )
171 .with_manufacturer("Canon".to_string())
172 .with_required_context("manufacturer".to_string())
173 .with_example_condition(
174 "manufacturer == 'Canon' && table.contains('CameraSettings')".to_string(),
175 )
176 }
177}
178
179pub struct CanonSerialDataMkIIProcessor;
188
189impl BinaryDataProcessor for CanonSerialDataMkIIProcessor {
190 fn can_process(&self, context: &ProcessorContext) -> ProcessorCapability {
191 if context.is_manufacturer("Canon") {
193 if let Some(model) = &context.model {
194 if (model.contains("EOS R5")
195 || model.contains("EOS R6")
196 || model.contains("EOS R3"))
197 && context.table_name.contains("SerialData")
198 {
199 return ProcessorCapability::Perfect;
200 }
201 }
202 }
203
204 ProcessorCapability::Incompatible
205 }
206
207 fn process_data(&self, data: &[u8], context: &ProcessorContext) -> Result<ProcessorResult> {
208 debug!(
209 "Processing Canon MkII SerialData with {} bytes for model: {:?}",
210 data.len(),
211 context.model
212 );
213
214 let mut result = ProcessorResult::new();
215
216 if data.len() < 12 {
218 result.add_warning("Canon MkII serial data too short".to_string());
219 return Ok(result);
220 }
221
222 let serial_bytes = &data[0..8];
227 let serial_number = String::from_utf8_lossy(serial_bytes)
228 .trim_end_matches('\0')
229 .to_string();
230 if !serial_number.is_empty() {
231 result.add_tag("SerialNumber".to_string(), TagValue::String(serial_number));
232 }
233
234 if data.len() >= 12 {
236 let firmware_major = data[8];
237 let firmware_minor = data[9];
238 let firmware_patch = u16::from_le_bytes([data[10], data[11]]);
239
240 let firmware_version = format!("{firmware_major}.{firmware_minor}.{firmware_patch}");
241 result.add_tag(
242 "FirmwareVersion".to_string(),
243 TagValue::String(firmware_version),
244 );
245 }
246
247 if let Some(model) = &context.model {
249 result.add_tag("CameraModel".to_string(), TagValue::String(model.clone()));
250 }
251
252 debug!(
253 "Canon MkII SerialData processor extracted {} tags",
254 result.extracted_tags.len()
255 );
256
257 Ok(result)
258 }
259
260 fn get_metadata(&self) -> ProcessorMetadata {
261 ProcessorMetadata::new(
262 "Canon Serial Data MkII".to_string(),
263 "Enhanced serial data processing for newer Canon models (R5, R6, R3)".to_string(),
264 )
265 .with_manufacturer("Canon".to_string())
266 .with_required_context("manufacturer".to_string())
267 .with_required_context("model".to_string())
268 .with_example_condition(
269 "manufacturer == 'Canon' && (model.contains('EOS R5') || model.contains('EOS R6'))"
270 .to_string(),
271 )
272 }
273}
274
275fn extract_binary_data_using_table(
280 data: &[u8],
281 context: &ProcessorContext,
282 table: &crate::types::BinaryDataTable,
283) -> Result<HashMap<String, TagValue>> {
284 let mut extracted = HashMap::new();
285
286 for (&index, tag_def) in &table.tags {
288 let first_entry = table.first_entry.unwrap_or(0);
290 if index < first_entry {
291 continue;
292 }
293
294 let entry_offset = (index - first_entry) as usize * table.default_format.byte_size();
295 if entry_offset + table.default_format.byte_size() > data.len() {
296 debug!("Tag {} at index {} beyond data bounds", tag_def.name, index);
297 continue;
298 }
299
300 let data_offset = context.data_offset + entry_offset;
301
302 let format = tag_def.format.unwrap_or(table.default_format);
304 let byte_order = context
305 .byte_order
306 .unwrap_or(crate::tiff_types::ByteOrder::LittleEndian);
307 let raw_value = extract_binary_value_direct(data, data_offset, format, byte_order)?;
308
309 let final_value = if let Some(print_conv) = &tag_def.print_conv {
311 apply_print_conv(&raw_value, print_conv)
312 } else {
313 raw_value
314 };
315
316 let group_0 = table
318 .groups
319 .get(&0)
320 .cloned()
321 .unwrap_or_else(|| "Unknown".to_string());
322
323 let tag_name = format!("{}:{}", group_0, tag_def.name);
324
325 debug!(
326 "Extracted Canon binary tag {} (index {}) = {:?}",
327 tag_def.name, index, final_value
328 );
329
330 extracted.insert(tag_name, final_value);
331 }
332
333 Ok(extracted)
334}
335
336fn extract_binary_value_direct(
341 data: &[u8],
342 offset: usize,
343 format: crate::types::BinaryDataFormat,
344 byte_order: crate::tiff_types::ByteOrder,
345) -> Result<TagValue> {
346 use crate::types::{BinaryDataFormat, ExifError};
347
348 match format {
349 BinaryDataFormat::Int8u => {
350 if offset >= data.len() {
351 return Err(ExifError::ParseError(
352 "Offset beyond data bounds".to_string(),
353 ));
354 }
355 Ok(TagValue::U8(data[offset]))
356 }
357 BinaryDataFormat::Int16s => {
358 if offset + 2 > data.len() {
359 return Err(ExifError::ParseError(
360 "Offset beyond data bounds for int16s".to_string(),
361 ));
362 }
363 let value = byte_order.read_u16(data, offset)? as i16;
364 Ok(TagValue::I16(value))
365 }
366 BinaryDataFormat::Int16u => {
367 if offset + 2 > data.len() {
368 return Err(ExifError::ParseError(
369 "Offset beyond data bounds for int16u".to_string(),
370 ));
371 }
372 let value = byte_order.read_u16(data, offset)?;
373 Ok(TagValue::U16(value))
374 }
375 BinaryDataFormat::Int32u => {
376 if offset + 4 > data.len() {
377 return Err(ExifError::ParseError(
378 "Offset beyond data bounds for int32u".to_string(),
379 ));
380 }
381 let value = byte_order.read_u32(data, offset)?;
382 Ok(TagValue::U32(value))
383 }
384 BinaryDataFormat::String => {
385 if offset >= data.len() {
386 return Err(ExifError::ParseError(
387 "Offset beyond data bounds for string".to_string(),
388 ));
389 }
390
391 let mut end = offset;
392 while end < data.len() && data[end] != 0 {
393 end += 1;
394 }
395
396 let string_bytes = &data[offset..end];
397 let string_value = String::from_utf8_lossy(string_bytes).to_string();
398 Ok(TagValue::String(string_value))
399 }
400 _ => Err(ExifError::ParseError(format!(
401 "Binary format {format:?} not yet implemented"
402 ))),
403 }
404}
405
406fn apply_print_conv(raw_value: &TagValue, print_conv: &HashMap<u32, String>) -> TagValue {
408 match raw_value {
409 TagValue::I16(val) => {
410 if let Some(converted) = print_conv.get(&(*val as u32)) {
411 TagValue::String(converted.clone())
412 } else {
413 raw_value.clone()
414 }
415 }
416 TagValue::U16(val) => {
417 if let Some(converted) = print_conv.get(&(*val as u32)) {
418 TagValue::String(converted.clone())
419 } else {
420 raw_value.clone()
421 }
422 }
423 TagValue::U32(val) => {
424 if let Some(converted) = print_conv.get(val) {
425 TagValue::String(converted.clone())
426 } else {
427 raw_value.clone()
428 }
429 }
430 _ => raw_value.clone(),
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437 use crate::formats::FileFormat;
438 use crate::processor_registry::ProcessorContext;
439
440 #[test]
441 fn test_canon_serial_data_processor_capability() {
442 let processor = CanonSerialDataProcessor;
443
444 let context = ProcessorContext::new(FileFormat::Jpeg, "Canon::SerialData".to_string())
446 .with_manufacturer("Canon".to_string());
447
448 assert_eq!(
449 processor.can_process(&context),
450 ProcessorCapability::Perfect
451 );
452
453 let context = ProcessorContext::new(FileFormat::Jpeg, "Canon::SerialData".to_string())
455 .with_manufacturer("Nikon".to_string());
456
457 assert_eq!(
458 processor.can_process(&context),
459 ProcessorCapability::Incompatible
460 );
461 }
462
463 #[test]
464 fn test_canon_mk2_processor_capability() {
465 let processor = CanonSerialDataMkIIProcessor;
466
467 let context = ProcessorContext::new(FileFormat::Jpeg, "Canon::SerialData".to_string())
469 .with_manufacturer("Canon".to_string())
470 .with_model("Canon EOS R5".to_string());
471
472 assert_eq!(
473 processor.can_process(&context),
474 ProcessorCapability::Perfect
475 );
476
477 let context = ProcessorContext::new(FileFormat::Jpeg, "Canon::SerialData".to_string())
479 .with_manufacturer("Canon".to_string())
480 .with_model("Canon EOS 5D Mark IV".to_string());
481
482 assert_eq!(
483 processor.can_process(&context),
484 ProcessorCapability::Incompatible
485 );
486 }
487
488 #[test]
489 fn test_canon_processor_metadata() {
490 let processor = CanonSerialDataProcessor;
491 let metadata = processor.get_metadata();
492
493 assert_eq!(metadata.name, "Canon Serial Data Processor");
494 assert!(metadata
495 .supported_manufacturers
496 .contains(&"Canon".to_string()));
497 assert!(metadata
498 .required_context
499 .contains(&"manufacturer".to_string()));
500 }
501}