exif(): read HEIF/HEIC originals with exifreader (closes #145) #147
+5
-1
@@ -58,8 +58,12 @@ export interface PhotoExif {
|
||||
gpsAltitude?: number;
|
||||
}
|
||||
|
||||
// exifreader gives "<faulty value>" for a tag whose value lies outside the
|
||||
// file; that tag is left out like one the file lacks.
|
||||
const asString = (v: unknown): string | undefined =>
|
||||
typeof v === "string" && v.length > 0 ? v : undefined;
|
||||
typeof v === "string" && v.length > 0 && v !== "<faulty value>"
|
||||
? v
|
||||
: undefined;
|
||||
|
||||
const asNumber = (v: unknown): number | undefined =>
|
||||
typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
||||
|
||||
@@ -63,6 +63,30 @@ const TIFF_ALTITUDE_WITHOUT_REF = [
|
||||
...[0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x02], // 25/2, at 44
|
||||
];
|
||||
|
||||
// A big-endian TIFF block holding Orientation 6 and a Make whose value lies
|
||||
// past the end of the file.
|
||||
const TIFF_MAKE_PAST_END = [
|
||||
...[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],
|
||||
// Make (0x010f), 6 ASCII bytes at 4096.
|
||||
...[0x01, 0x0f, 0x00, 0x02, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x10, 0x00],
|
||||
// Orientation (0x0112), SHORT, 6.
|
||||
...[0x01, 0x12, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00],
|
||||
...[0x00, 0x00, 0x00, 0x00],
|
||||
];
|
||||
|
||||
// A big-endian TIFF block with one IFD entry that exifreader has no name for:
|
||||
// tag 0xc000 (49152), SHORT, 7.
|
||||
const TIFF_UNNAMED_TAG = [
|
||||
...[0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08], // the first IFD at 8
|
||||
// The first IFD, at 8: one entry, then no next IFD.
|
||||
...[0x00, 0x01],
|
||||
// Tag 0xc000, SHORT, 7.
|
||||
...[0xc0, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x00],
|
||||
...[0x00, 0x00, 0x00, 0x00],
|
||||
];
|
||||
|
||||
// An APP1 segment whose length field matches its data.
|
||||
const app1 = (data: number[]): number[] => {
|
||||
const len = data.length + 2;
|
||||
@@ -94,6 +118,13 @@ describe("readPhotoExif", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("gives no make for a Make whose value lies past the end of the file", () => {
|
||||
const data = [...EXIF_HEADER, ...TIFF_MAKE_PAST_END];
|
||||
expect(readPhotoExif(bytes(SOI, app1(data), SOS))).toStrictEqual({
|
||||
orientation: 6,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
[
|
||||
"a file that is not an image",
|
||||
@@ -170,6 +201,14 @@ describe("extractImageMetadata", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keys a tag exifreader has no name for by its number", () => {
|
||||
const meta = extractImageMetadata(
|
||||
bytes(SOI, app1([...EXIF_HEADER, ...TIFF_UNNAMED_TAG]), SOS),
|
||||
);
|
||||
expect(meta?.exifError).toBeUndefined();
|
||||
expect(meta?.exif).toMatchObject({ "undefined-49152": { value: 7 } });
|
||||
});
|
||||
|
||||
it("returns nothing for a file that is not an image", () => {
|
||||
const text = new TextEncoder().encode("just some text, not an image");
|
||||
expect(extractImageMetadata(text)).toBeUndefined();
|
||||
|
||||
Reference in New Issue
Block a user