From 844dfa057c5fb62615b6f9e62a3d49b80381eb44 Mon Sep 17 00:00:00 2001 From: Puneet Rai Date: Sun, 8 Feb 2026 00:38:27 +0530 Subject: [PATCH 1/2] fix(makernote): register Canon parser in TIFF parser Fixes #29 The Canon MakerNote parser was merged (PR #24) but not registered in the MakerNote registry, causing Canon metadata to not be extracted. Canon must be registered LAST as it has no header and uses IFD structure validation as fallback detection. Co-Authored-By: Claude Opus 4.5 --- internal/parser/tiff/tiff.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/parser/tiff/tiff.go b/internal/parser/tiff/tiff.go index c89b347..f938426 100644 --- a/internal/parser/tiff/tiff.go +++ b/internal/parser/tiff/tiff.go @@ -10,6 +10,7 @@ import ( "github.com/gomantics/imx/internal/parser/icc" "github.com/gomantics/imx/internal/parser/iptc" "github.com/gomantics/imx/internal/parser/tiff/makernote" + "github.com/gomantics/imx/internal/parser/tiff/makernote/canon" "github.com/gomantics/imx/internal/parser/tiff/makernote/sony" "github.com/gomantics/imx/internal/parser/xmp" ) @@ -44,8 +45,9 @@ type Parser struct { func New() *Parser { registry := makernote.NewRegistry() // Register MakerNote handlers in priority order (most specific first) - // Sony must be registered before Canon (Canon has no header, is fallback) + // Canon must be LAST (no header, validated by IFD structure, is fallback) registry.Register(sony.New()) + registry.Register(canon.New()) return &Parser{ icc: icc.New(), From 125fefedc9ef6da3819c144268380fecaa3986f5 Mon Sep 17 00:00:00 2001 From: Puneet Rai Date: Sun, 8 Feb 2026 00:48:54 +0530 Subject: [PATCH 2/2] fix(makernote): preserve raw MakerNote tag and update integration test - Always add raw MakerNote tag to ExifIFD (backward compatibility) - Add Canon directory expectation to CR2 integration test with 28 tags - Verify correct Canon tag names (CameraSettings, ShotInfo, etc.) Co-Authored-By: Claude Opus 4.5 --- api_integration_test.go | 34 ++++++++++++++++++++++++++++++++++ internal/parser/tiff/ifd.go | 8 ++++++++ 2 files changed, 42 insertions(+) diff --git a/api_integration_test.go b/api_integration_test.go index b74818a..7d745d6 100644 --- a/api_integration_test.go +++ b/api_integration_test.go @@ -305,6 +305,40 @@ func TestIntegration_CR2(t *testing.T) { {Name: "JPEGInterchangeFormatLength", Value: uint32(13120)}, }, }, + { + Name: "Canon", + ExactTagCount: 28, + Tags: []imxtest.TagExpectation{ + {Name: "CameraSettings"}, + {Name: "FocalLength"}, + {Name: "FlashInfo"}, + {Name: "ShotInfo"}, + {Name: "ImageType", Value: "Canon EOS-1Ds Mark II"}, + {Name: "FirmwareVersion", Value: "Firmware Version 1.0.2"}, + {Name: "OwnerName"}, + {Name: "SerialNumber", Value: uint32(304421)}, + {Name: "CameraInfo"}, + {Name: "CustomFunctions"}, + {Name: "ModelID", Value: uint32(2147484040)}, + {Name: "AFInfo"}, + {Name: "ThumbnailImageValidArea"}, + {Name: "SerialNumberFormat"}, + {Name: "0x0019"}, + {Name: "OriginalDecisionDataOffset"}, + {Name: "PersonalFunctions"}, + {Name: "PersonalFunctionValues"}, + {Name: "CanonFileInfo"}, + {Name: "AFPointsInFocus1D"}, + {Name: "ProcessingInfo"}, + {Name: "MeasuredColor"}, + {Name: "SensorInfo"}, + {Name: "VRDOffset"}, + {Name: "ColorData"}, + {Name: "CRWParam"}, + {Name: "ColorInfo"}, + {Name: "0x4004"}, + }, + }, }) if result.Failed() { for _, err := range result.Errors { diff --git a/internal/parser/tiff/ifd.go b/internal/parser/tiff/ifd.go index e4466c3..dacd81d 100644 --- a/internal/parser/tiff/ifd.go +++ b/internal/parser/tiff/ifd.go @@ -572,6 +572,14 @@ func (p *Parser) handleMakerNote(r *imxbin.Reader, fileReader io.ReaderAt, entry return } + // Always add raw MakerNote tag (for backward compatibility) + *dirTags = append(*dirTags, parser.Tag{ + ID: parser.TagID("ExifIFD:0x927C"), + Name: "MakerNote", + Value: data, + DataType: "UNDEFINED", + }) + // Detect manufacturer and parse // exifBase is 0 for standard TIFF files (TIFF header at file start) // TODO: For JPEG files, this would need to be the APP1 EXIF header offset