photo.exif() and quak backup-metadata --exif now read EXIF with exifreader, so HEIC/HEIF originals get it, a live photo's image included. exifreader replaces exif-reader and extractExifFromJpeg. PhotoExif is unchanged.
Both callers use readExifTags (src/exif.ts), which asks exifreader for EXIF tags only, unnamed ones included, so a broken XMP or ICC block cannot cost a file its EXIF. A file exifreader cannot read, such as a video, has no EXIF. A text tag whose value lies outside the file gives no field.
The fixture test/exif.heic holds the same values as the library tests' hand-built JPEG; test/exif-heic.ts says how it was made. Each input the deleted scan tests used is now checked against readPhotoExif.
Licence: exifreader is MPL-2.0, which permits unmodified use from WTFPL code.
Format change: exifRaw holds the whole EXIF block exifreader found (for a JPEG, the APP1 segment, marker and length included). A tag exifreader has no name for is keyed undefined- plus its number; the thumbnail's tags are not recorded.
Judgement call: a latitude or longitude without its hemisphere reference tag is left out.
Judgement call: exifError is set when exifreader finds an EXIF block but reads no tag from it; a JPEG broken before any EXIF block gets none, where the old scan named the bad segment.
Judgement call: a date the JavaScript date parser rejects, such as 0000:00:00 00:00:00 or a month above 12, gives no dateTimeOriginal. One it rolls over, such as 2021:02:30 or hour 24, gives the rolled-over date.
Model: opus-5-5
Implements https://git.eeqj.de/sneak/quak/issues/145.
`photo.exif()` and `quak backup-metadata --exif` now read EXIF with `exifreader`, so HEIC/HEIF originals get it, a live photo's image included. `exifreader` replaces `exif-reader` and `extractExifFromJpeg`. `PhotoExif` is unchanged.
Both callers use `readExifTags` (`src/exif.ts`), which asks `exifreader` for EXIF tags only, unnamed ones included, so a broken XMP or ICC block cannot cost a file its EXIF. A file `exifreader` cannot read, such as a video, has no EXIF. A text tag whose value lies outside the file gives no field.
The fixture `test/exif.heic` holds the same values as the library tests' hand-built JPEG; `test/exif-heic.ts` says how it was made. Each input the deleted scan tests used is now checked against `readPhotoExif`.
- Licence: `exifreader` is MPL-2.0, which permits unmodified use from WTFPL code.
- Format change: `exifRaw` holds the whole EXIF block `exifreader` found (for a JPEG, the APP1 segment, marker and length included). A tag `exifreader` has no name for is keyed `undefined-` plus its number; the thumbnail's tags are not recorded.
- Judgement call: a latitude or longitude without its hemisphere reference tag is left out.
- Judgement call: `exifError` is set when `exifreader` finds an EXIF block but reads no tag from it; a JPEG broken before any EXIF block gets none, where the old scan named the bad segment.
- Judgement call: a date the JavaScript date parser rejects, such as `0000:00:00 00:00:00` or a month above 12, gives no `dateTimeOriginal`. One it rolls over, such as `2021:02:30` or hour 24, gives the rolled-over date.
Model: opus-5-5
photo.exif() and backup-metadata --exif read EXIF with exifreader,
which reads JPEG, HEIC/HEIF, AVIF, PNG and WebP, so a HEIC original or
a live photo's HEIC image gets its EXIF. exifreader replaces
exif-reader and the hand-written JPEG segment scan. PhotoExif is
unchanged. The exif field of backup-metadata --exif is now
exifreader's tag output, and exifRaw holds the whole EXIF block it
could not read. Tests use a real 933-byte HEIC fixture, test/exif.heic,
whose recipe is in test/exif-heic.ts.
Model: opus-5-5
README.md:503-504: "Dumps written before 2026-10-01 hold exif-reader's Image, Photo and GPSInfo groups there instead." describes an old dump layout. quak is pre-1.0 and has no dumps to describe, so docs about an old layout are a defect. Acceptable: the README describes only the current exif format, which the sentence before it already does. Drop this sentence.
src/exif.ts:71: the PR's third judgement call is that a date that is not a real calendar date, such as 0000:00:00 00:00:00 (what cameras with an unset clock write), gives no dateTimeOriginal. That check is new code, and no test covers it. Without it, exif() would return an Invalid Date. Acceptable: a test that EXIF with DateTimeOriginal0000:00:00 00:00:00 gives no dateTimeOriginal.
Judgement call: the brief asked for the exif format change to be stated in the README. I read that as asking for the current format to be stated, not the old one.
Model: opus-5-5
Review: fail.
1. `README.md:503-504`: "Dumps written before 2026-10-01 hold `exif-reader`'s `Image`, `Photo` and `GPSInfo` groups there instead." describes an old dump layout. quak is pre-1.0 and has no dumps to describe, so docs about an old layout are a defect. Acceptable: the README describes only the current `exif` format, which the sentence before it already does. Drop this sentence.
2. `src/exif.ts:71`: the PR's third judgement call is that a date that is not a real calendar date, such as `0000:00:00 00:00:00` (what cameras with an unset clock write), gives no `dateTimeOriginal`. That check is new code, and no test covers it. Without it, `exif()` would return an Invalid Date. Acceptable: a test that EXIF with `DateTimeOriginal` `0000:00:00 00:00:00` gives no `dateTimeOriginal`.
Judgement call: the brief asked for the `exif` format change to be stated in the README. I read that as asking for the current format to be stated, not the old one.
Model: opus-5-5
Add a readPhotoExif test: EXIF whose DateTimeOriginal is
0000:00:00 00:00:00, as a camera with an unset clock writes it, gives no
dateTimeOriginal while its other fields are still read. Remove the README
sentence describing the backup's exif field in dumps from an earlier
version.
Model: opus-5-5
src/exif.ts:94: gpsAltitude now comes from exifreader's computed position, which exifreader sets only when GPSAltitudeRef is present. A file with GPSAltitude and no GPSAltitudeRef therefore loses its altitude. The code on next returned it as above sea level, which is the EXIF standard's default for that tag, and README.md:725 says each field is absent only when the file lacks it. Leaving out latitude and longitude that have no reference tag is fine, since the standard gives those no default. Acceptable: altitude is read from the GPSAltitude tag and negated only when GPSAltitudeRef is 1, with a test of a file that has GPSAltitude and no GPSAltitudeRef.
src/exif.ts:3 says backup-metadata --exif records every EXIF tag it finds. readExifTags drops the tags exifreader has no name for, and the thumbnail IFD's tags, though the dump on next kept both. src/exif.ts:12 says each tag's computed is a plain string or number, but a tag with several values computes to an array: GPSLatitude gives [40, 26, 46]. Acceptable: both comments say what the code does. If the dump is meant to keep every tag, readExifTags passes includeUnknown: true instead.
PR body, third judgement call: it says a date that is not a real calendar date now gives no dateTimeOriginal. 2021:02:30 10:00:00 still gives 2021-03-02, because asDate (src/exif.ts:64-72) leaves day overflow to the Date parser. Acceptable: the PR body names only the dates that are actually dropped, those with a zero or out-of-range field such as 0000:00:00 00:00:00.
Model: opus-5-5
Review: fail.
1. `src/exif.ts:94`: `gpsAltitude` now comes from exifreader's computed position, which exifreader sets only when `GPSAltitudeRef` is present. A file with `GPSAltitude` and no `GPSAltitudeRef` therefore loses its altitude. The code on `next` returned it as above sea level, which is the EXIF standard's default for that tag, and `README.md:725` says each field is absent only when the file lacks it. Leaving out latitude and longitude that have no reference tag is fine, since the standard gives those no default. Acceptable: altitude is read from the `GPSAltitude` tag and negated only when `GPSAltitudeRef` is 1, with a test of a file that has `GPSAltitude` and no `GPSAltitudeRef`.
2. `src/exif.ts:3` says `backup-metadata --exif` records every EXIF tag it finds. `readExifTags` drops the tags exifreader has no name for, and the thumbnail IFD's tags, though the dump on `next` kept both. `src/exif.ts:12` says each tag's `computed` is a plain string or number, but a tag with several values computes to an array: `GPSLatitude` gives `[40, 26, 46]`. Acceptable: both comments say what the code does. If the dump is meant to keep every tag, `readExifTags` passes `includeUnknown: true` instead.
3. PR body, third judgement call: it says a date that is not a real calendar date now gives no `dateTimeOriginal`. `2021:02:30 10:00:00` still gives 2021-03-02, because `asDate` (`src/exif.ts:64-72`) leaves day overflow to the `Date` parser. Acceptable: the PR body names only the dates that are actually dropped, those with a zero or out-of-range field such as `0000:00:00 00:00:00`.
Model: opus-5-5
Rework decision on finding 2 of the review above: readExifTags passes includeUnknown: true, so the backup-metadata --exif dump keeps tags exifreader has no name for. Both comments in src/exif.ts then say exactly what the code does, including that the thumbnail's tags are not kept and that a tag with several values computes to an array. Findings 1 and 3 are fixed as their "Acceptable" sentences say.
Model: opus-5-5
Rework decision on finding 2 of the review above: `readExifTags` passes `includeUnknown: true`, so the `backup-metadata --exif` dump keeps tags `exifreader` has no name for. Both comments in `src/exif.ts` then say exactly what the code does, including that the thumbnail's tags are not kept and that a tag with several values computes to an array. Findings 1 and 3 are fixed as their "Acceptable" sentences say.
Model: opus-5-5
exif() takes the altitude from the GPSAltitude tag and negates it only when
GPSAltitudeRef is 1, so a file with GPSAltitude and no GPSAltitudeRef keeps its
altitude, above sea level. A test covers that file.
backup-metadata --exif keeps the EXIF tags exifreader has no name for. The
comments in src/exif.ts say the thumbnail's tags are not kept and that a tag
with several values computes to an array.
Model: opus-5-5
src/exif.ts:87-91: when a text tag's value lies outside the file (a bad offset or count), exifreader returns the placeholder string "<faulty value>", and readPhotoExif passes it on as make, model, lensModel or offsetTimeOriginal. A JPEG whose Make offset points past the end of the file now gets that string as its make; on next the field was absent. Acceptable: a tag exifreader could not read gives no field, as the README's "absent when the file lacks it" says, with a test of such a file.
src/exif.ts:25: includeUnknown: true, decided in #147 (comment) so that the backup-metadata --exif dump keeps the tags exifreader has no name for, is not covered by any test. Acceptable: a test in test/cli/metadata-exif.test.ts that extractImageMetadata records a tag exifreader has no name for, keyed undefined-<tag number>.
Model: opus-5-5
Review: fail.
1. `src/exif.ts:87-91`: when a text tag's value lies outside the file (a bad offset or count), exifreader returns the placeholder string "<faulty value>", and `readPhotoExif` passes it on as `make`, `model`, `lensModel` or `offsetTimeOriginal`. A JPEG whose `Make` offset points past the end of the file now gets that string as its make; on `next` the field was absent. Acceptable: a tag exifreader could not read gives no field, as the README's "absent when the file lacks it" says, with a test of such a file.
2. `src/exif.ts:25`: `includeUnknown: true`, decided in https://git.eeqj.de/sneak/quak/pulls/147#issuecomment-108406 so that the `backup-metadata --exif` dump keeps the tags exifreader has no name for, is not covered by any test. Acceptable: a test in `test/cli/metadata-exif.test.ts` that `extractImageMetadata` records a tag exifreader has no name for, keyed undefined-<tag number>.
Model: opus-5-5
A text tag whose value lies outside the file, which exifreader reads as
"<faulty value>", no longer gives a make, model, lens model or offset
field. Tests cover such a JPEG and the backup-metadata dump keying a tag
exifreader has no name for as undefined-<tag number>.
Model: opus-5-5
README.md:728-730: the read surface says EXIF is read from "any image format exifreader reads: JPEG, HEIC/HEIF, AVIF, PNG and WebP", and that any other original gives {}. exifreader also reads TIFF, and so the TIFF-based raw originals such as DNG, plus JPEG XL. exif() returns their fields: a TIFF original gives its orientation. The issue's definition of done asks the README to state the formats that are read. Acceptable: the README names every format exif() reads, or presents the list as examples, so that "any other original gives {}" is true.
PR body, last judgement call: it counts an out-of-range hour among the readings that give no dateTimeOriginal, but 2021:07:15 24:00:00 gives 2021-07-16 00:00 UTC. Acceptable: either the body stops counting hour 24 as out of range, or the code also drops hour 24 and a test covers it.
Model: opus-5-5
Review: fail.
1. `README.md:728-730`: the read surface says EXIF is read from "any image format exifreader reads: JPEG, HEIC/HEIF, AVIF, PNG and WebP", and that any other original gives `{}`. exifreader also reads TIFF, and so the TIFF-based raw originals such as DNG, plus JPEG XL. `exif()` returns their fields: a TIFF original gives its orientation. The issue's definition of done asks the README to state the formats that are read. Acceptable: the README names every format `exif()` reads, or presents the list as examples, so that "any other original gives `{}`" is true.
2. PR body, last judgement call: it counts an out-of-range hour among the readings that give no `dateTimeOriginal`, but `2021:07:15 24:00:00` gives 2021-07-16 00:00 UTC. Acceptable: either the body stops counting hour 24 as out of range, or the code also drops hour 24 and a test covers it.
Model: opus-5-5
The read surface listed JPEG, HEIC/HEIF, AVIF, PNG and WebP as every
format exif() reads, but exifreader reads more, so "any other original
gives {}" was false. The list is now examples.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Implements #145.
photo.exif()andquak backup-metadata --exifnow read EXIF withexifreader, so HEIC/HEIF originals get it, a live photo's image included.exifreaderreplacesexif-readerandextractExifFromJpeg.PhotoExifis unchanged.Both callers use
readExifTags(src/exif.ts), which asksexifreaderfor EXIF tags only, unnamed ones included, so a broken XMP or ICC block cannot cost a file its EXIF. A fileexifreadercannot read, such as a video, has no EXIF. A text tag whose value lies outside the file gives no field.The fixture
test/exif.heicholds the same values as the library tests' hand-built JPEG;test/exif-heic.tssays how it was made. Each input the deleted scan tests used is now checked againstreadPhotoExif.exifreaderis MPL-2.0, which permits unmodified use from WTFPL code.exifRawholds the whole EXIF blockexifreaderfound (for a JPEG, the APP1 segment, marker and length included). A tagexifreaderhas no name for is keyedundefined-plus its number; the thumbnail's tags are not recorded.exifErroris set whenexifreaderfinds an EXIF block but reads no tag from it; a JPEG broken before any EXIF block gets none, where the old scan named the bad segment.0000:00:00 00:00:00or a month above 12, gives nodateTimeOriginal. One it rolls over, such as2021:02:30or hour 24, gives the rolled-over date.Model: opus-5-5
Review: fail.
README.md:503-504: "Dumps written before 2026-10-01 holdexif-reader'sImage,PhotoandGPSInfogroups there instead." describes an old dump layout. quak is pre-1.0 and has no dumps to describe, so docs about an old layout are a defect. Acceptable: the README describes only the currentexifformat, which the sentence before it already does. Drop this sentence.src/exif.ts:71: the PR's third judgement call is that a date that is not a real calendar date, such as0000:00:00 00:00:00(what cameras with an unset clock write), gives nodateTimeOriginal. That check is new code, and no test covers it. Without it,exif()would return an Invalid Date. Acceptable: a test that EXIF withDateTimeOriginal0000:00:00 00:00:00gives nodateTimeOriginal.Judgement call: the brief asked for the
exifformat change to be stated in the README. I read that as asking for the current format to be stated, not the old one.Model: opus-5-5
Review: fail.
src/exif.ts:94:gpsAltitudenow comes from exifreader's computed position, which exifreader sets only whenGPSAltitudeRefis present. A file withGPSAltitudeand noGPSAltitudeReftherefore loses its altitude. The code onnextreturned it as above sea level, which is the EXIF standard's default for that tag, andREADME.md:725says each field is absent only when the file lacks it. Leaving out latitude and longitude that have no reference tag is fine, since the standard gives those no default. Acceptable: altitude is read from theGPSAltitudetag and negated only whenGPSAltitudeRefis 1, with a test of a file that hasGPSAltitudeand noGPSAltitudeRef.src/exif.ts:3saysbackup-metadata --exifrecords every EXIF tag it finds.readExifTagsdrops the tags exifreader has no name for, and the thumbnail IFD's tags, though the dump onnextkept both.src/exif.ts:12says each tag'scomputedis a plain string or number, but a tag with several values computes to an array:GPSLatitudegives[40, 26, 46]. Acceptable: both comments say what the code does. If the dump is meant to keep every tag,readExifTagspassesincludeUnknown: trueinstead.PR body, third judgement call: it says a date that is not a real calendar date now gives no
dateTimeOriginal.2021:02:30 10:00:00still gives 2021-03-02, becauseasDate(src/exif.ts:64-72) leaves day overflow to theDateparser. Acceptable: the PR body names only the dates that are actually dropped, those with a zero or out-of-range field such as0000:00:00 00:00:00.Model: opus-5-5
Rework decision on finding 2 of the review above:
readExifTagspassesincludeUnknown: true, so thebackup-metadata --exifdump keeps tagsexifreaderhas no name for. Both comments insrc/exif.tsthen say exactly what the code does, including that the thumbnail's tags are not kept and that a tag with several values computes to an array. Findings 1 and 3 are fixed as their "Acceptable" sentences say.Model: opus-5-5
Review: fail.
src/exif.ts:87-91: when a text tag's value lies outside the file (a bad offset or count), exifreader returns the placeholder string "<faulty value>", andreadPhotoExifpasses it on asmake,model,lensModeloroffsetTimeOriginal. A JPEG whoseMakeoffset points past the end of the file now gets that string as its make; onnextthe field was absent. Acceptable: a tag exifreader could not read gives no field, as the README's "absent when the file lacks it" says, with a test of such a file.src/exif.ts:25:includeUnknown: true, decided in #147 (comment) so that thebackup-metadata --exifdump keeps the tags exifreader has no name for, is not covered by any test. Acceptable: a test intest/cli/metadata-exif.test.tsthatextractImageMetadatarecords a tag exifreader has no name for, keyed undefined-<tag number>.Model: opus-5-5
Review: fail.
README.md:728-730: the read surface says EXIF is read from "any image format exifreader reads: JPEG, HEIC/HEIF, AVIF, PNG and WebP", and that any other original gives{}. exifreader also reads TIFF, and so the TIFF-based raw originals such as DNG, plus JPEG XL.exif()returns their fields: a TIFF original gives its orientation. The issue's definition of done asks the README to state the formats that are read. Acceptable: the README names every formatexif()reads, or presents the list as examples, so that "any other original gives{}" is true.PR body, last judgement call: it counts an out-of-range hour among the readings that give no
dateTimeOriginal, but2021:07:15 24:00:00gives 2021-07-16 00:00 UTC. Acceptable: either the body stops counting hour 24 as out of range, or the code also drops hour 24 and a test covers it.Model: opus-5-5
The read surface listed JPEG, HEIC/HEIF, AVIF, PNG and WebP as every format exif() reads, but exifreader reads more, so "any other original gives {}" was false. The list is now examples. Model: opus-5-5Review: pass.
Model: opus-5-5