Read GPSAltitude without its reference tag; keep unnamed EXIF tags
check / check (push) Successful in 1m25s

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
This commit is contained in:
2026-10-01 19:35:52 +00:00
parent 9c0c7c9489
commit e623fd2015
2 changed files with 38 additions and 4 deletions
+14 -4
View File
@@ -1,7 +1,7 @@
// EXIF in an original's bytes, read with exifreader, which reads it from JPEG,
// HEIC/HEIF, AVIF, PNG, WebP and the other image formats it supports.
// `backup-metadata --exif` records every EXIF tag it finds; `Photo.exif()`
// returns the common fields picked from them here.
// `backup-metadata --exif` records every EXIF tag it finds except the
// thumbnail's; `Photo.exif()` returns the common fields picked from them here.
import ExifReader, { type ExpandedTags } from "exifreader";
@@ -9,7 +9,11 @@ import ExifReader, { type ExpandedTags } from "exifreader";
// them (`gps`), and where the EXIF block lies in `bytes` (`metadataRange`).
// Undefined when exifreader cannot read the file at all, such as a video. An
// EXIF block it finds but reads no tag from comes back as an empty `exif`.
// Each tag's `computed` holds its value as a plain string or number.
// `exif` holds every tag except the thumbnail's; a tag exifreader has no name
// for is keyed `undefined-<tag number>`. Each tag's `computed` holds its value
// as a string or number, or as an array of them for a tag with several values,
// such as `GPSLatitude`'s `[40, 26, 46]`. A fraction with a zero denominator
// computes to null.
export const readExifTags = (bytes: Uint8Array): ExpandedTags | undefined => {
try {
return ExifReader.loadView(
@@ -18,6 +22,7 @@ export const readExifTags = (bytes: Uint8Array): ExpandedTags | undefined => {
expanded: true,
computed: true,
includeOffsets: true,
includeUnknown: true,
includeTags: { exif: true, gps: true },
},
);
@@ -77,6 +82,7 @@ export const readPhotoExif = (bytes: Uint8Array): PhotoExif => {
const tags = readExifTags(bytes);
const exif = tags?.exif;
const gps = tags?.gps;
const altitude = asNumber(exif?.GPSAltitude?.computed);
const fields: PhotoExif = {
make: asString(exif?.Make?.computed),
model: asString(exif?.Model?.computed),
@@ -91,7 +97,11 @@ export const readPhotoExif = (bytes: Uint8Array): PhotoExif => {
orientation: asNumber(exif?.Orientation?.computed),
gpsLatitude: asNumber(gps?.Latitude),
gpsLongitude: asNumber(gps?.Longitude),
gpsAltitude: asNumber(gps?.Altitude),
// A GPSAltitudeRef of 1 means the altitude is below sea level.
gpsAltitude:
altitude !== undefined && exif?.GPSAltitudeRef?.value === 1
? -altitude
: altitude,
};
// Leave out what the file lacks, so a missing field is absent rather than
// present and undefined.