From b13018306ccb1cbbfb5c1040dd7862e6b022531a Mon Sep 17 00:00:00 2001 From: sneak Date: Fri, 2 Oct 2026 03:18:20 +0000 Subject: [PATCH] Test the GPS hemisphere rules readPhotoExif applies Two readPhotoExif tests on hand-built GPS IFDs: a GPSLatitude with GPSLatitudeRef "S" gives a negative gpsLatitude, and a GPSLatitude and GPSLongitude without their reference tags give neither field. Model: opus-5-5 --- test/cli/metadata-exif.test.ts | 63 ++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/cli/metadata-exif.test.ts b/test/cli/metadata-exif.test.ts index a21ad6a..509f1d8 100644 --- a/test/cli/metadata-exif.test.ts +++ b/test/cli/metadata-exif.test.ts @@ -67,6 +67,51 @@ const TIFF_ALTITUDE_WITHOUT_REF = [ ...[0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x02], // 25/2, at 44 ]; +// A big-endian TIFF block holding a GPSLatitude of 33° 30' 0" and a +// GPSLatitudeRef of "S". +const TIFF_SOUTHERN_LATITUDE = [ + ...[0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08], // the first IFD at 8 + // The first IFD, at 8: one entry, then no next IFD. + ...[0x00, 0x01], + // The GPS IFD's offset (0x8825), LONG, 26. + ...[0x88, 0x25, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a], + ...[0x00, 0x00, 0x00, 0x00], + // The GPS IFD, at 26: two entries, then no next IFD. + ...[0x00, 0x02], + // GPSLatitudeRef (0x0001), 2 ASCII bytes: "S". + ...[0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x53, 0x00, 0x00, 0x00], + // GPSLatitude (0x0002), three RATIONALs at 56. + ...[0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x38], + ...[0x00, 0x00, 0x00, 0x00], + ...[0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01], // 33/1, at 56 + ...[0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x01], // 30/1 + ...[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], // 0/1 +]; + +// A big-endian TIFF block holding a GPSLatitude of 40° 26' 46" and a +// GPSLongitude of 79° 58' 56", and neither GPSLatitudeRef nor GPSLongitudeRef. +const TIFF_POSITION_WITHOUT_REFS = [ + ...[0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08], // the first IFD at 8 + // The first IFD, at 8: one entry, then no next IFD. + ...[0x00, 0x01], + // The GPS IFD's offset (0x8825), LONG, 26. + ...[0x88, 0x25, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1a], + ...[0x00, 0x00, 0x00, 0x00], + // The GPS IFD, at 26: two entries, then no next IFD. + ...[0x00, 0x02], + // GPSLatitude (0x0002), three RATIONALs at 56. + ...[0x00, 0x02, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x38], + // GPSLongitude (0x0004), three RATIONALs at 80. + ...[0x00, 0x04, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x50], + ...[0x00, 0x00, 0x00, 0x00], + ...[0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01], // 40/1, at 56 + ...[0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x01], // 26/1 + ...[0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x01], // 46/1 + ...[0x00, 0x00, 0x00, 0x4f, 0x00, 0x00, 0x00, 0x01], // 79/1, at 80 + ...[0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x01], // 58/1 + ...[0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x01], // 56/1 +]; + // A big-endian TIFF block holding Orientation 6 and a Make whose value lies // past the end of the file. const TIFF_MAKE_PAST_END = [ @@ -182,6 +227,24 @@ describe("readPhotoExif", () => { }); }); + it("reads a GPSLatitude with GPSLatitudeRef S as south of the equator", () => { + const data = [...EXIF_HEADER, ...TIFF_SOUTHERN_LATITUDE]; + expect( + readPhotoExif(readAllExifTags(bytes(SOI, app1(data), SOS))), + ).toStrictEqual({ + gpsLatitude: -33.5, + }); + }); + + it("gives no gpsLatitude or gpsLongitude without their reference tags", () => { + const data = [...EXIF_HEADER, ...TIFF_POSITION_WITHOUT_REFS]; + const tags = readAllExifTags(bytes(SOI, app1(data), SOS)); + // The position is read; only its hemisphere is unknown. + expect(tags.GPSLatitude?.computed).toStrictEqual([40, 26, 46]); + expect(tags.GPSLongitude?.computed).toStrictEqual([79, 58, 56]); + expect(readPhotoExif(tags)).toStrictEqual({}); + }); + it("gives no make for a Make whose value lies past the end of the file", () => { const data = [...EXIF_HEADER, ...TIFF_MAKE_PAST_END]; expect(