exif(): read HEIF/HEIC originals with exifreader (closes #145)
check / check (push) Successful in 1m21s
check / check (push) Successful in 1m21s
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
This commit is contained in:
@@ -25,7 +25,16 @@ import { Library, type LibraryOptions } from "../../src/library/index.js";
|
||||
import type { ContentSource } from "../../src/library/content.js";
|
||||
import type { CollectionsPage, FilesPage } from "../../src/client.js";
|
||||
import type { Collection, EnteFile } from "../../src/model/types.js";
|
||||
import { asLivePhoto, cdnSource, IMAGE, livePhotoZip } from "../live-photo.js";
|
||||
import type { PhotoExif } from "../../src/exif.js";
|
||||
import { HEIC_WITH_EXIF } from "../exif-heic.js";
|
||||
import {
|
||||
asLivePhoto,
|
||||
cdnSource,
|
||||
IMAGE,
|
||||
livePhotoHash,
|
||||
livePhotoZip,
|
||||
VIDEO,
|
||||
} from "../live-photo.js";
|
||||
|
||||
const USER_ID = 7;
|
||||
|
||||
@@ -297,7 +306,7 @@ const JPEG_WITH_EXIF = new Uint8Array([
|
||||
]);
|
||||
|
||||
// A JPEG whose EXIF segment is laid out correctly but holds "XX" where the TIFF
|
||||
// byte order belongs, so exif-reader cannot parse it.
|
||||
// byte order belongs, so exifreader cannot parse it.
|
||||
const JPEG_WITH_BAD_EXIF = new Uint8Array([
|
||||
...[0xff, 0xd8], // start of image
|
||||
...[0xff, 0xe1, ...u16(2 + 6 + 2)], // APP1 and its length
|
||||
@@ -411,7 +420,49 @@ describe("Photo save path, local copy, content and EXIF", () => {
|
||||
await lib.close();
|
||||
});
|
||||
|
||||
it("returns no EXIF fields for an original that is not a JPEG", async () => {
|
||||
// What exif() returns for HEIC_WITH_EXIF, which holds the same values as
|
||||
// JPEG_WITH_EXIF.
|
||||
const heicFields: PhotoExif = {
|
||||
make: "Canon",
|
||||
model: "EOS R5",
|
||||
lensModel: "RF50mm F1.8 STM",
|
||||
dateTimeOriginal: new Date(Date.UTC(2021, 6, 15, 14, 30)),
|
||||
offsetTimeOriginal: "+02:00",
|
||||
exposureTime: 1 / 250,
|
||||
fNumber: 2.8,
|
||||
iso: 400,
|
||||
focalLength: 50,
|
||||
orientation: 6,
|
||||
gpsLatitude: 40 + 26 / 60 + 46 / 3600,
|
||||
gpsLongitude: -(79 + 58 / 60 + 56 / 3600),
|
||||
gpsAltitude: -12.5,
|
||||
};
|
||||
|
||||
it("reads the same common EXIF fields from a HEIC original", async () => {
|
||||
const lib = await open({ contentSource: stubSource(HEIC_WITH_EXIF) });
|
||||
expect(await lib.photos.byID({ fileID: 1 })!.exif()).toStrictEqual(
|
||||
heicFields,
|
||||
);
|
||||
await lib.close();
|
||||
});
|
||||
|
||||
it("reads the EXIF of a live photo whose image is a HEIC", async () => {
|
||||
const { file: live, body } = await asLivePhoto(
|
||||
file(1, 1),
|
||||
livePhotoZip({ "image.heic": HEIC_WITH_EXIF, "video.mov": VIDEO }),
|
||||
livePhotoHash(HEIC_WITH_EXIF, VIDEO),
|
||||
);
|
||||
const lib = await open({
|
||||
client: new FilesClient([live]),
|
||||
contentSource: cdnSource(new Map([[1, body]])),
|
||||
});
|
||||
expect(await lib.photos.byID({ fileID: 1 })!.exif()).toStrictEqual(
|
||||
heicFields,
|
||||
);
|
||||
await lib.close();
|
||||
});
|
||||
|
||||
it("returns no EXIF fields for an original that is not an image", async () => {
|
||||
const lib = await open();
|
||||
expect(await lib.photos.byID({ fileID: 1 })!.exif()).toStrictEqual({});
|
||||
await lib.close();
|
||||
|
||||
Reference in New Issue
Block a user