Owner's words, on the milestone PR (#134 (comment), 2026-10-01 17:56 UTC):
this work never should have landed on next. HEIF support for exif is table stakes.
"This work" is photo.exif(), which landed on next from #142 and reads EXIF from JPEG only. This also answers question 3 of #141 (comment): HEIF is required now, not as a follow-up.
Definition of done:
photo.exif() returns the same common fields for a HEIC/HEIF original as for a JPEG, a live photo's HEIC image included.
quak backup-metadata --exif also reads EXIF from HEIC/HEIF originals.
EXIF is read with one widely used, maintained library, not a hand-written parser per format.
Tests use a real HEIC file carrying EXIF (small, committed as a fixture) as well as JPEG. README states the formats that are read.
The check gate is green on next. One PR to next, independently reviewed and squash-merged.
Model: opus-5-5
Owner's words, on the milestone PR (https://git.eeqj.de/sneak/quak/pulls/134#issuecomment-107590, 2026-10-01 17:56 UTC):
> this work never should have landed on next. HEIF support for exif is table stakes.
"This work" is `photo.exif()`, which landed on `next` from https://git.eeqj.de/sneak/quak/pulls/142 and reads EXIF from JPEG only. This also answers question 3 of https://git.eeqj.de/sneak/quak/issues/141#issuecomment-107489: HEIF is required now, not as a follow-up.
Definition of done:
- `photo.exif()` returns the same common fields for a HEIC/HEIF original as for a JPEG, a live photo's HEIC image included.
- `quak backup-metadata --exif` also reads EXIF from HEIC/HEIF originals.
- EXIF is read with one widely used, maintained library, not a hand-written parser per format.
- Tests use a real HEIC file carrying EXIF (small, committed as a fixture) as well as JPEG. README states the formats that are read.
- The check gate is green on `next`. One PR to `next`, independently reviewed and squash-merged.
Model: opus-5-5
clawbot
self-assigned this 2026-10-01 20:06:01 +02:00
Implementer's brief. Move all EXIF reading to exifreader (https://github.com/mattiasw/ExifReader). It is actively maintained, widely used, and reads JPEG, HEIC/HEIF, AVIF, PNG and WebP. exifr is used more but has not been published since 2022. exifreader is MPL-2.0, which permits using it unmodified from WTFPL code; say so in one line in the PR.
src/exif.ts.
readPhotoExif(bytes) reads the same 13 fields through exifreader, for every format it supports.
Keep PhotoExif's shape and value types exactly as they are:
dateTimeOriginal keeps today's meaning: the camera's clock in the Date's UTC fields;
GPS values stay signed decimal degrees and metres;
iso keeps its current rule.
A file with no EXIF, or with EXIF that cannot be read, still returns {}.
A video still returns {} without downloading.
Confirm each tag name and value type against exifreader's own types.
quak backup-metadata --exif (src/metadata-backup.ts) reads EXIF through exifreader too, so HEIC/HEIF originals get it.
The exif object in its JSON becomes exifreader's tag output. Say that format change in one line in the PR and in the README.
Keep exifError for a block that cannot be read.
Keep the jpeg-js dimensions and the XMP scan unchanged.
Removals. Remove extractExifFromJpeg and the exif-reader dependency once nothing uses them. Pin exifreader exactly, as package.json pins everything else.
Tests.
Commit one small real HEIC file that carries EXIF, kept under 100 KB, as a fixture.
Make it in a throwaway Docker container (for example libheif and exiftool on Alpine), and remove the container afterwards.
Say in a comment beside the fixture how it was made.
exif() on that HEIC returns the expected fields, and so does a live photo whose image is that HEIC.
The existing JPEG cases still pass unchanged.
quak backup-metadata --exif records EXIF for the HEIC.
Every behaviour the deleted extractExifFromJpeg tests guarded keeps a test against the new code: a non-image has no EXIF, and malformed EXIF yields {} or exifError. Never weaken an assertion.
Docs. README "Read surface" lists the formats exif() reads, and the CLI section covers --exif.
Process
Branch from current next, with one PR whose base is next. Title: exif(): read HEIF/HEIC originals with exifreader (closes #145). Label it needs-review and assign clawbot.
#143 is being built at the same time and touches src/library/read.ts. Stay out of that file except for exif(), and rebase onto next before pushing.
Gate only with make check, and format only with make fmt. Both run in Docker. Run Docker builds under flock -w 1800 /srv/code/tmp/.docker-build.lock. A timeout there exits 1 without running anything; retry, and never read it as red. Remove every container you start.
Edit files by hand. No sed -i, perl -pi, awk or scripted replacements.
Keep the change small and plain enough for a newcomer to follow in one reading. Coin no new terms.
Ask no interactive questions.
Commits and the PR body end with a Model: line naming your model id. Never name the company.
Stay under 2 GiB of RAM.
Model: opus-5-5
**Implementer's brief.** Move all EXIF reading to `exifreader` (https://github.com/mattiasw/ExifReader). It is actively maintained, widely used, and reads JPEG, HEIC/HEIF, AVIF, PNG and WebP. `exifr` is used more but has not been published since 2022. `exifreader` is MPL-2.0, which permits using it unmodified from WTFPL code; say so in one line in the PR.
**`src/exif.ts`.**
- `readPhotoExif(bytes)` reads the same 13 fields through `exifreader`, for every format it supports.
- Keep `PhotoExif`'s shape and value types exactly as they are:
- `dateTimeOriginal` keeps today's meaning: the camera's clock in the `Date`'s UTC fields;
- GPS values stay signed decimal degrees and metres;
- `iso` keeps its current rule.
- A file with no EXIF, or with EXIF that cannot be read, still returns `{}`.
- A video still returns `{}` without downloading.
- Confirm each tag name and value type against `exifreader`'s own types.
**`quak backup-metadata --exif`** (`src/metadata-backup.ts`) reads EXIF through `exifreader` too, so HEIC/HEIF originals get it.
- The `exif` object in its JSON becomes `exifreader`'s tag output. Say that format change in one line in the PR and in the README.
- Keep `exifError` for a block that cannot be read.
- Keep the `jpeg-js` dimensions and the XMP scan unchanged.
**Removals.** Remove `extractExifFromJpeg` and the `exif-reader` dependency once nothing uses them. Pin `exifreader` exactly, as `package.json` pins everything else.
**Tests.**
- Commit one small real HEIC file that carries EXIF, kept under 100 KB, as a fixture.
- Make it in a throwaway Docker container (for example `libheif` and `exiftool` on Alpine), and remove the container afterwards.
- Say in a comment beside the fixture how it was made.
- `exif()` on that HEIC returns the expected fields, and so does a live photo whose image is that HEIC.
- The existing JPEG cases still pass unchanged.
- `quak backup-metadata --exif` records EXIF for the HEIC.
- Every behaviour the deleted `extractExifFromJpeg` tests guarded keeps a test against the new code: a non-image has no EXIF, and malformed EXIF yields `{}` or `exifError`. Never weaken an assertion.
**Docs.** README "Read surface" lists the formats `exif()` reads, and the CLI section covers `--exif`.
**Process**
- Branch from current `next`, with one PR whose base is `next`. Title: `exif(): read HEIF/HEIC originals with exifreader (closes #145)`. Label it `needs-review` and assign `clawbot`.
- https://git.eeqj.de/sneak/quak/issues/143 is being built at the same time and touches `src/library/read.ts`. Stay out of that file except for `exif()`, and rebase onto `next` before pushing.
- Gate only with `make check`, and format only with `make fmt`. Both run in Docker. Run Docker builds under `flock -w 1800 /srv/code/tmp/.docker-build.lock`. A timeout there exits 1 without running anything; retry, and never read it as red. Remove every container you start.
- Edit files by hand. No `sed -i`, `perl -pi`, `awk` or scripted replacements.
- Keep the change small and plain enough for a newcomer to follow in one reading. Coin no new terms.
- Ask no interactive questions.
- Commits and the PR body end with a `Model:` line naming your model id. Never name the company.
- Stay under 2 GiB of RAM.
Model: opus-5-5
Owner ruling on the EXIF shape (chat, 2026-10-01 ~19:05 UTC): both. exif() returns everything, and one async accessor per field calls exif() and returns that value. Filed as #148, to land after this one or together with it. Keep this issue's field list in one place that both can use.
model: opus-5-5
Owner ruling on the EXIF shape (chat, 2026-10-01 ~19:05 UTC): both. `exif()` returns everything, and one async accessor per field calls `exif()` and returns that value. Filed as https://git.eeqj.de/sneak/quak/issues/148, to land after this one or together with it. Keep this issue's field list in one place that both can use.
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.
Owner's words, on the milestone PR (#134 (comment), 2026-10-01 17:56 UTC):
"This work" is
photo.exif(), which landed onnextfrom #142 and reads EXIF from JPEG only. This also answers question 3 of #141 (comment): HEIF is required now, not as a follow-up.Definition of done:
photo.exif()returns the same common fields for a HEIC/HEIF original as for a JPEG, a live photo's HEIC image included.quak backup-metadata --exifalso reads EXIF from HEIC/HEIF originals.next. One PR tonext, independently reviewed and squash-merged.Model: opus-5-5
Implementer's brief. Move all EXIF reading to
exifreader(https://github.com/mattiasw/ExifReader). It is actively maintained, widely used, and reads JPEG, HEIC/HEIF, AVIF, PNG and WebP.exifris used more but has not been published since 2022.exifreaderis MPL-2.0, which permits using it unmodified from WTFPL code; say so in one line in the PR.src/exif.ts.readPhotoExif(bytes)reads the same 13 fields throughexifreader, for every format it supports.PhotoExif's shape and value types exactly as they are:dateTimeOriginalkeeps today's meaning: the camera's clock in theDate's UTC fields;isokeeps its current rule.{}.{}without downloading.exifreader's own types.quak backup-metadata --exif(src/metadata-backup.ts) reads EXIF throughexifreadertoo, so HEIC/HEIF originals get it.exifobject in its JSON becomesexifreader's tag output. Say that format change in one line in the PR and in the README.exifErrorfor a block that cannot be read.jpeg-jsdimensions and the XMP scan unchanged.Removals. Remove
extractExifFromJpegand theexif-readerdependency once nothing uses them. Pinexifreaderexactly, aspackage.jsonpins everything else.Tests.
libheifandexiftoolon Alpine), and remove the container afterwards.exif()on that HEIC returns the expected fields, and so does a live photo whose image is that HEIC.quak backup-metadata --exifrecords EXIF for the HEIC.extractExifFromJpegtests guarded keeps a test against the new code: a non-image has no EXIF, and malformed EXIF yields{}orexifError. Never weaken an assertion.Docs. README "Read surface" lists the formats
exif()reads, and the CLI section covers--exif.Process
next, with one PR whose base isnext. Title:exif(): read HEIF/HEIC originals with exifreader (closes #145). Label itneeds-reviewand assignclawbot.src/library/read.ts. Stay out of that file except forexif(), and rebase ontonextbefore pushing.make check, and format only withmake fmt. Both run in Docker. Run Docker builds underflock -w 1800 /srv/code/tmp/.docker-build.lock. A timeout there exits 1 without running anything; retry, and never read it as red. Remove every container you start.sed -i,perl -pi,awkor scripted replacements.Model:line naming your model id. Never name the company.Model: opus-5-5
clawbot referenced this issue2026-10-01 20:06:46 +02:00
Owner ruling on the EXIF shape (chat, 2026-10-01 ~19:05 UTC): both.
exif()returns everything, and one async accessor per field callsexif()and returns that value. Filed as #148, to land after this one or together with it. Keep this issue's field list in one place that both can use.model: opus-5-5