Skip to content

Commit 873cb23

Browse files
committed
Enhance Canon MakerNote parsing; add Nikon ISO2
Bump version to 0.4.25 and update CHANGES.md. Add a missing Nikon ShotInfo D300A ISO2 tag mapping (0x0265) and regenerate makernote tag names. Improve Canon MakerNote handling: include EOS 7D in the PSInfo cohort and add EOS Kiss X7i / EOS-1D X to the PSInfo2 cohort selection logic. Change logic for Canon main tag 0x0038 so short byte blobs are treated as the placeholder (ContextualName) while long byte blobs are left named as BatteryType (wire_count <= 8 is considered short). Update TIFF decode and Canon makernote code accordingly and add/adjust tests to cover the new behaviors (including helpers for long Canon tag 0x0038 blobs and cohort selection tests).
1 parent 3083c46 commit 873cb23

8 files changed

Lines changed: 134 additions & 7 deletions

File tree

CHANGES.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,27 @@
11
# OpenMeta Changes
22

3+
## 0.4.25 - 2026-05-21
4+
5+
Changes compared with `0.4.24`.
6+
7+
### Changed
8+
9+
- Improved Canon CameraInfo PictureStyle interpretation for EOS 7D,
10+
EOS Kiss X7i, and EOS-1D X model cohorts by selecting the matching
11+
PSInfo/PSInfo2 table.
12+
- Split Canon MakerNote main tag `0x0038` byte-blob naming by observed
13+
payload size, preserving the placeholder for short blobs while reporting
14+
long battery-type payloads as `BatteryType`.
15+
16+
## 0.4.24 - 2026-05-21
17+
18+
Changes compared with `0.4.23`.
19+
20+
### Added
21+
22+
- Added the missing Nikon ShotInfo D300A `ISO2` MakerNote name used by the
23+
ExifTool-compatible interpretation path.
24+
325
## 0.4.23 - 2026-05-21
426

527
Changes compared with `0.4.22`.

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.4.23
1+
0.4.25

registry/exif/makernotes/nikon.jsonl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1196,6 +1196,7 @@
11961196
{"kind":"exif.tag","ifd":"makernote:nikon:shotinfo","tag":"0x024d","name":"ShutterCount"}
11971197
{"kind":"exif.tag","ifd":"makernote:nikon:shotinfod300a","tag":"0x0000","name":"ShotInfoVersion"}
11981198
{"kind":"exif.tag","ifd":"makernote:nikon:shotinfod300a","tag":"0x025c","name":"ISO2"}
1199+
{"kind":"exif.tag","ifd":"makernote:nikon:shotinfod300a","tag":"0x0265","name":"ISO2"}
11991200
{"kind":"exif.tag","ifd":"makernote:nikon:shotinfod300a","tag":"0x0279","name":"ShutterCount"}
12001201
{"kind":"exif.tag","ifd":"makernote:nikon:shotinfod300a","tag":"0x02d1","name":"AFFineTuneAdj"}
12011202
{"kind":"exif.tag","ifd":"makernote:nikon:shotinfod300b","tag":"0x0000","name":"ShotInfoVersion"}

src/openmeta/exif_makernote_canon.cc

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ canon_camerainfo_prefers_psinfo(std::string_view model) noexcept
328328
"EOS 50D", "EOS 5D Mark II",
329329
"EOS 1000D", "EOS DIGITAL REBEL XS",
330330
"EOS Kiss F", "EOS-1D Mark III",
331-
"EOS-1Ds Mark III",
331+
"EOS-1Ds Mark III", "EOS 7D",
332332
};
333333
return canon_model_matches_any(model, kCanonModels);
334334
}
@@ -337,9 +337,7 @@ static bool
337337
canon_camerainfo_prefers_psinfo2(std::string_view model) noexcept
338338
{
339339
static constexpr std::string_view kCanonModels[] = {
340-
"EOS 60D",
341-
"EOS 6D",
342-
"EOS 5D Mark III",
340+
"EOS 60D", "EOS 6D", "EOS 5D Mark III", "EOS Kiss X7i", "EOS-1D X",
343341
};
344342
return canon_model_matches_any(model, kCanonModels);
345343
}
@@ -3824,7 +3822,7 @@ decode_canon_makernote(const TiffConfig& cfg,
38243822
abs_value_off, value_bytes,
38253823
store.arena(), options.limits,
38263824
status_out);
3827-
if (tag == 0x0038u && type == 7
3825+
if (tag == 0x0038u && type == 7 && count <= 8U
38283826
&& entry.value.kind == MetaValueKind::Bytes) {
38293827
entry.flags |= EntryFlags::ContextualName;
38303828
entry.origin.name_context_kind = EntryNameContextKind::CanonMain0038;

src/openmeta/exif_makernote_tag_names_generated.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5876,6 +5876,7 @@ static constexpr MakerNoteTagNameEntry kMakernoteNikonShotinfo[] = {
58765876
static constexpr MakerNoteTagNameEntry kMakernoteNikonShotinfod300a[] = {
58775877
{ 0x0000u, "ShotInfoVersion" },
58785878
{ 0x025Cu, "ISO2" },
5879+
{ 0x0265u, "ISO2" },
58795880
{ 0x0279u, "ShutterCount" },
58805881
{ 0x02D1u, "AFFineTuneAdj" },
58815882
};

src/openmeta/exif_tiff_decode.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -999,7 +999,8 @@ namespace {
999999
}
10001000
}
10011001
if (ifd_name == "mk_canon0" && tag == 0x0038u
1002-
&& entry->value.kind == MetaValueKind::Bytes) {
1002+
&& entry->value.kind == MetaValueKind::Bytes
1003+
&& entry->origin.wire_count <= 8U) {
10031004
entry->flags |= EntryFlags::ContextualName;
10041005
entry->origin.name_context_kind
10051006
= EntryNameContextKind::CanonMain0038;

tests/exif_tag_names_test.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ TEST(ExifTagNames, MapsCommonTags)
3131
std::string_view("NumberOfImages"));
3232

3333
EXPECT_EQ(exif_tag_name("mk_nikon0", 0x0002), std::string_view("ISO"));
34+
EXPECT_EQ(exif_tag_name("mk_nikon_shotinfod300a_0", 0x0265),
35+
std::string_view("ISO2"));
3436
EXPECT_EQ(exif_tag_name("mk_canon0", 0x0003),
3537
std::string_view("CanonFlashInfo"));
3638
EXPECT_EQ(exif_tag_name("mk_fuji0", 0x1000), std::string_view("Quality"));

tests/makernote_decode_test.cc

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,24 @@ namespace {
903903
return mn;
904904
}
905905

906+
static std::vector<std::byte> make_canon_makernote_main_tag0038_long_bytes()
907+
{
908+
std::vector<std::byte> mn;
909+
append_u16le(&mn, 1);
910+
append_u16le(&mn, 0x0038);
911+
append_u16le(&mn, 7);
912+
append_u32le(&mn, 76);
913+
append_u32le(&mn, 18U);
914+
append_u32le(&mn, 0);
915+
EXPECT_EQ(mn.size(), 18U);
916+
append_u32le(&mn, 76U);
917+
append_bytes(&mn, "LP-E6NH");
918+
while (mn.size() < 18U + 76U) {
919+
mn.push_back(std::byte { 0 });
920+
}
921+
return mn;
922+
}
923+
906924
static std::vector<std::byte> make_canon_custom_functions1d_makernote()
907925
{
908926
// Canon MakerNote with:
@@ -7755,6 +7773,32 @@ TEST(MakerNoteDecode, MarksCanonMainTag0038PlaceholderForByteBlob)
77557773
std::string_view("Canon_0x0038"));
77567774
}
77577775

7776+
TEST(MakerNoteDecode, KeepsCanonMainTag0038BatteryTypeForLongByteBlob)
7777+
{
7778+
const std::vector<std::byte> mn
7779+
= make_canon_makernote_main_tag0038_long_bytes();
7780+
const std::vector<std::byte> tiff = make_test_tiff_with_makernote("Canon",
7781+
mn);
7782+
7783+
MetaStore store;
7784+
std::array<ExifIfdRef, 8> ifds {};
7785+
ExifDecodeOptions options;
7786+
options.decode_makernote = true;
7787+
const ExifDecodeResult res = decode_exif_tiff(tiff, store, ifds, options);
7788+
EXPECT_EQ(res.status, ExifDecodeStatus::Ok);
7789+
7790+
store.finalize();
7791+
const std::span<const EntryId> ids = store.find_all(
7792+
exif_key("mk_canon0", 0x0038));
7793+
ASSERT_EQ(ids.size(), 1U);
7794+
const Entry& e = store.entry(ids[0]);
7795+
EXPECT_FALSE(any(e.flags, EntryFlags::ContextualName));
7796+
EXPECT_EQ(exif_entry_name(store, e, ExifTagNamePolicy::Canonical),
7797+
std::string_view("BatteryType"));
7798+
EXPECT_EQ(exif_entry_name(store, e, ExifTagNamePolicy::ExifToolCompat),
7799+
std::string_view("BatteryType"));
7800+
}
7801+
77587802
TEST(MakerNoteDecode, SelectsCanonCustom350dTableFromCountFallback)
77597803
{
77607804
const std::vector<std::byte> mn
@@ -8574,6 +8618,24 @@ TEST(MakerNoteDecode, PrefersCanonCameraInfoPsinfoFor1000dCohort)
85748618
ASSERT_EQ(store.find_all(exif_key("mk_canon_psinfo_0", 0x0090)).size(), 1U);
85758619
}
85768620

8621+
TEST(MakerNoteDecode, PrefersCanonCameraInfoPsinfoFor7dCohort)
8622+
{
8623+
std::vector<std::byte> mn = make_canon_camera_info_psinfo2_makernote();
8624+
const std::vector<std::byte> tiff
8625+
= make_test_tiff_with_makernote_and_model("Canon", "Canon EOS 7D", mn);
8626+
8627+
MetaStore store;
8628+
std::array<ExifIfdRef, 8> ifds {};
8629+
ExifDecodeOptions options;
8630+
options.decode_makernote = true;
8631+
const ExifDecodeResult res = decode_exif_tiff(tiff, store, ifds, options);
8632+
EXPECT_EQ(res.status, ExifDecodeStatus::Ok);
8633+
8634+
store.finalize();
8635+
EXPECT_TRUE(store.find_all(exif_key("mk_canon_psinfo2_0", 0x0090)).empty());
8636+
ASSERT_EQ(store.find_all(exif_key("mk_canon_psinfo_0", 0x0090)).size(), 1U);
8637+
}
8638+
85778639
TEST(MakerNoteDecode, PrefersCanonCameraInfoPsinfo2For5dMarkIiiCohort)
85788640
{
85798641
std::vector<std::byte> mn = make_canon_camera_info_psinfo2_makernote();
@@ -8594,6 +8656,46 @@ TEST(MakerNoteDecode, PrefersCanonCameraInfoPsinfo2For5dMarkIiiCohort)
85948656
1U);
85958657
}
85968658

8659+
TEST(MakerNoteDecode, PrefersCanonCameraInfoPsinfo2ForKissX7iCohort)
8660+
{
8661+
std::vector<std::byte> mn = make_canon_camera_info_psinfo2_makernote();
8662+
const std::vector<std::byte> tiff
8663+
= make_test_tiff_with_makernote_and_model("Canon", "Canon EOS Kiss X7i",
8664+
mn);
8665+
8666+
MetaStore store;
8667+
std::array<ExifIfdRef, 8> ifds {};
8668+
ExifDecodeOptions options;
8669+
options.decode_makernote = true;
8670+
const ExifDecodeResult res = decode_exif_tiff(tiff, store, ifds, options);
8671+
EXPECT_EQ(res.status, ExifDecodeStatus::Ok);
8672+
8673+
store.finalize();
8674+
EXPECT_TRUE(store.find_all(exif_key("mk_canon_psinfo_0", 0x0090)).empty());
8675+
ASSERT_EQ(store.find_all(exif_key("mk_canon_psinfo2_0", 0x0090)).size(),
8676+
1U);
8677+
}
8678+
8679+
TEST(MakerNoteDecode, PrefersCanonCameraInfoPsinfo2For1dXCohort)
8680+
{
8681+
std::vector<std::byte> mn = make_canon_camera_info_psinfo2_makernote();
8682+
const std::vector<std::byte> tiff
8683+
= make_test_tiff_with_makernote_and_model("Canon", "Canon EOS-1D X",
8684+
mn);
8685+
8686+
MetaStore store;
8687+
std::array<ExifIfdRef, 8> ifds {};
8688+
ExifDecodeOptions options;
8689+
options.decode_makernote = true;
8690+
const ExifDecodeResult res = decode_exif_tiff(tiff, store, ifds, options);
8691+
EXPECT_EQ(res.status, ExifDecodeStatus::Ok);
8692+
8693+
store.finalize();
8694+
EXPECT_TRUE(store.find_all(exif_key("mk_canon_psinfo_0", 0x0090)).empty());
8695+
ASSERT_EQ(store.find_all(exif_key("mk_canon_psinfo2_0", 0x0090)).size(),
8696+
1U);
8697+
}
8698+
85978699
TEST(MakerNoteDecode,
85988700
FallsBackToCanonCameraInfoPictureStyleWhenPsinfo2TailIsInvalid)
85998701
{

0 commit comments

Comments
 (0)