diff --git a/README.md b/README.md index 3ab7ff2..66d60ea 100644 --- a/README.md +++ b/README.md @@ -500,12 +500,10 @@ its XMP metadata, its EXIF metadata and, for a JPEG, its dimensions. EXIF is read with [exifreader](https://github.com/mattiasw/ExifReader) from any image format it reads, JPEG, HEIC/HEIF, AVIF, PNG and WebP among them. The record's `exif` field is exifreader's EXIF tag output: each tag by name, with its -`value`, `description` and `computed` value. Dumps written before 2026-10-01 -hold `exif-reader`'s `Image`, `Photo` and `GPSInfo` groups there instead. An -EXIF block exifreader finds but reads no tag from is recorded, base64, as -`exifRaw`, with the reason in `exifError`. `collections`, `files`, `backup`, -`helper list-missing-thumbnails` and `helper fix-missing-thumbnails` take -`--json` for machine-readable output. +`value`, `description` and `computed` value. An EXIF block exifreader finds but +reads no tag from is recorded, base64, as `exifRaw`, with the reason in +`exifError`. `collections`, `files`, `backup`, `helper list-missing-thumbnails` +and `helper fix-missing-thumbnails` take `--json` for machine-readable output. `backup-metadata` fetches ML data in requests of up to 200 files. When a request fails, the error is logged, each of its files is written with the reason in an diff --git a/test/cli/metadata-exif.test.ts b/test/cli/metadata-exif.test.ts index dffe9a6..2deda57 100644 --- a/test/cli/metadata-exif.test.ts +++ b/test/cli/metadata-exif.test.ts @@ -27,6 +27,25 @@ const TIFF_ORIENTATION_6 = [ 0x00, 0x00, ]; +// A big-endian TIFF block holding Orientation 6 and DateTimeOriginal +// "0000:00:00 00:00:00", which a camera with an unset clock writes. +const TIFF_UNSET_DATE = [ + ...[0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08], // the first IFD at 8 + // The first IFD, at 8: two entries, then no next IFD. + ...[0x00, 0x02], + // Orientation (0x0112), SHORT, 6. + ...[0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00], + // The Exif IFD's offset (0x8769), LONG, 38. + ...[0x87, 0x69, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x26], + ...[0x00, 0x00, 0x00, 0x00], + // The Exif IFD, at 38: one entry, then no next IFD. + ...[0x00, 0x01], + // DateTimeOriginal (0x9003), 20 ASCII bytes at 56. + ...[0x90, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x38], + ...[0x00, 0x00, 0x00, 0x00], + ...new TextEncoder().encode("0000:00:00 00:00:00\0"), // at 56 +]; + // An APP1 segment whose length field matches its data. const app1 = (data: number[]): number[] => { const len = data.length + 2; @@ -44,6 +63,13 @@ describe("readPhotoExif", () => { }); }); + it("gives no dateTimeOriginal for a DateTimeOriginal of 0000:00:00 00:00:00", () => { + const data = [...EXIF_HEADER, ...TIFF_UNSET_DATE]; + expect(readPhotoExif(bytes(SOI, app1(data), SOS))).toStrictEqual({ + orientation: 6, + }); + }); + it.each([ [ "a file that is not an image",