exif(): read HEIF/HEIC originals with exifreader (closes #145) #147
+5
-1
@@ -58,8 +58,12 @@ export interface PhotoExif {
|
|||||||
gpsAltitude?: number;
|
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 =>
|
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 =>
|
const asNumber = (v: unknown): number | undefined =>
|
||||||
typeof v === "number" && Number.isFinite(v) ? v : 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
|
...[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.
|
// An APP1 segment whose length field matches its data.
|
||||||
const app1 = (data: number[]): number[] => {
|
const app1 = (data: number[]): number[] => {
|
||||||
const len = data.length + 2;
|
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([
|
it.each([
|
||||||
[
|
[
|
||||||
"a file that is not an image",
|
"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", () => {
|
it("returns nothing for a file that is not an image", () => {
|
||||||
const text = new TextEncoder().encode("just some text, not an image");
|
const text = new TextEncoder().encode("just some text, not an image");
|
||||||
expect(extractImageMetadata(text)).toBeUndefined();
|
expect(extractImageMetadata(text)).toBeUndefined();
|
||||||
|
|||||||
Reference in New Issue
Block a user