Skip to content

Commit ddbbca1

Browse files
rpuneetclaude
andauthored
feat(canon): add MakerNote value decoding (#41)
Decodes Canon MakerNote compound tags into human-readable values: CameraSettings array decoded fields: - Quality (RAW, Fine, Normal, etc.) - FlashMode (Off, Auto, On, Red-eye reduction) - DriveMode (Single, Continuous, etc.) - FocusMode (One-shot AF, AI Servo AF, Manual Focus, etc.) - ImageSize (Large, Medium, Small) - EasyMode (Full auto, Manual, Portrait, Landscape, etc.) - MeteringMode (Evaluative, Spot, Center-weighted, etc.) - ExposureMode (Program AE, Aperture-priority, Manual, etc.) - ImageStabilization (On, Off, Shoot Only) ShotInfo array decoded fields: - WhiteBalance (Auto, Daylight, Cloudy, Flash, etc.) ModelID decoded to camera name: - Maps 80+ Canon camera model IDs to names (EOS 5D, EOS R5, etc.) Closes #36 Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent 9338d33 commit ddbbca1

File tree

3 files changed

+758
-11
lines changed

3 files changed

+758
-11
lines changed

internal/parser/tiff/makernote/canon/canon.go

Lines changed: 100 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,19 +114,21 @@ func (h *Handler) Parse(r io.ReaderAt, makerNoteOffset, exifBase int64, cfg *mak
114114
return nil, parseErr
115115
}
116116

117-
tags := make([]parser.Tag, 0, entryCount)
117+
tags := make([]parser.Tag, 0, entryCount*2) // Extra capacity for decoded sub-tags
118118

119119
// Parse each IFD entry
120120
entryOffset := ifdOffset + ifdEntryCountSize
121121
for i := uint16(0); i < entryCount; i++ {
122-
tag, err := h.parseEntry(reader, entryOffset, exifBase)
122+
tag, subTags, err := h.parseEntry(reader, entryOffset, exifBase)
123123
if err != nil {
124124
// Continue parsing other entries
125125
entryOffset += ifdEntrySize
126126
continue
127127
}
128128
if tag != nil {
129129
tags = append(tags, *tag)
130+
// Add decoded sub-tags (for CameraSettings, ShotInfo, etc.)
131+
tags = append(tags, subTags...)
130132
}
131133
entryOffset += ifdEntrySize
132134
}
@@ -136,32 +138,33 @@ func (h *Handler) Parse(r io.ReaderAt, makerNoteOffset, exifBase int64, cfg *mak
136138

137139
// parseEntry parses a single IFD entry.
138140
// For Canon, offsets are absolute (relative to EXIF TIFF header).
139-
func (h *Handler) parseEntry(r *imxbin.Reader, entryOffset, exifBase int64) (*parser.Tag, error) {
141+
// Returns the main tag plus any decoded sub-tags (for compound tags like CameraSettings).
142+
func (h *Handler) parseEntry(r *imxbin.Reader, entryOffset, exifBase int64) (*parser.Tag, []parser.Tag, error) {
140143
// Read entry fields
141144
tagID, err := r.ReadUint16(entryOffset)
142145
if err != nil {
143-
return nil, err
146+
return nil, nil, err
144147
}
145148

146149
tagType, err := r.ReadUint16(entryOffset + 2)
147150
if err != nil {
148-
return nil, err
151+
return nil, nil, err
149152
}
150153

151154
count, err := r.ReadUint32(entryOffset + 4)
152155
if err != nil {
153-
return nil, err
156+
return nil, nil, err
154157
}
155158

156159
valueOffset, err := r.ReadUint32(entryOffset + 8)
157160
if err != nil {
158-
return nil, err
161+
return nil, nil, err
159162
}
160163

161164
// Calculate data size
162165
typeSize := getTypeSize(tagType)
163166
if typeSize == 0 {
164-
return nil, fmt.Errorf("unknown type: %d", tagType)
167+
return nil, nil, fmt.Errorf("unknown type: %d", tagType)
165168
}
166169

167170
totalSize := int64(count) * int64(typeSize)
@@ -178,15 +181,101 @@ func (h *Handler) parseEntry(r *imxbin.Reader, entryOffset, exifBase int64) (*pa
178181
// Read and parse value
179182
value, err := h.readValue(r, tagType, count, dataOffset, valueOffset)
180183
if err != nil {
181-
return nil, err
184+
return nil, nil, err
182185
}
183186

184-
return &parser.Tag{
187+
mainTag := &parser.Tag{
185188
ID: parser.TagID(fmt.Sprintf("Canon:0x%04X", tagID)),
186189
Name: h.TagName(tagID),
187190
Value: value,
188191
DataType: getTypeName(tagType),
189-
}, nil
192+
}
193+
194+
// Decode compound tags into sub-tags
195+
var subTags []parser.Tag
196+
switch tagID {
197+
case 0x0001: // CameraSettings
198+
subTags = h.decodeCameraSettings(value)
199+
case 0x0004: // ShotInfo
200+
subTags = h.decodeShotInfo(value)
201+
case 0x0010: // ModelID
202+
if decoded := h.decodeModelIDTag(value); decoded != nil {
203+
subTags = append(subTags, *decoded)
204+
}
205+
}
206+
207+
return mainTag, subTags, nil
208+
}
209+
210+
// decodeCameraSettings extracts individual settings from CameraSettings array
211+
func (h *Handler) decodeCameraSettings(value any) []parser.Tag {
212+
shorts, ok := value.([]uint16)
213+
if !ok {
214+
return nil
215+
}
216+
217+
var tags []parser.Tag
218+
for i, v := range shorts {
219+
name, decoded := decodeCameraSettingsValue(i, v)
220+
if name != "" && decoded != "" {
221+
tags = append(tags, parser.Tag{
222+
ID: parser.TagID("Canon:CameraSettings:" + name),
223+
Name: name,
224+
Value: decoded,
225+
DataType: "decoded",
226+
})
227+
}
228+
}
229+
return tags
230+
}
231+
232+
// decodeShotInfo extracts individual settings from ShotInfo array
233+
func (h *Handler) decodeShotInfo(value any) []parser.Tag {
234+
shorts, ok := value.([]uint16)
235+
if !ok {
236+
return nil
237+
}
238+
239+
var tags []parser.Tag
240+
for i, v := range shorts {
241+
name, decoded := decodeShotInfoValue(i, v)
242+
if name != "" && decoded != "" {
243+
tags = append(tags, parser.Tag{
244+
ID: parser.TagID("Canon:ShotInfo:" + name),
245+
Name: name,
246+
Value: decoded,
247+
DataType: "decoded",
248+
})
249+
}
250+
}
251+
return tags
252+
}
253+
254+
// decodeModelIDTag decodes the ModelID to camera model name
255+
func (h *Handler) decodeModelIDTag(value any) *parser.Tag {
256+
var modelID uint32
257+
switch v := value.(type) {
258+
case uint32:
259+
modelID = v
260+
case []uint32:
261+
if len(v) > 0 {
262+
modelID = v[0]
263+
}
264+
default:
265+
return nil
266+
}
267+
268+
decoded := decodeModelID(modelID)
269+
if decoded == "" {
270+
return nil
271+
}
272+
273+
return &parser.Tag{
274+
ID: parser.TagID("Canon:ModelName"),
275+
Name: "ModelName",
276+
Value: decoded,
277+
DataType: "decoded",
278+
}
190279
}
191280

192281
// readValue reads a tag value based on its type.

0 commit comments

Comments
 (0)