exif() returns every EXIF tag in the file (closes #156)
check / check (push) Failing after 59s
check / check (push) Failing after 59s
`photo.exif()` now returns every EXIF tag in the file, as the owner ruled, typed `ExifTags`: each tag keyed by name with `exifreader`'s `id`, `value`, `description` and `computed`. A tag with no name is keyed `undefined-` plus its number, and the embedded thumbnail's tags sit under `Thumbnail`. The thirteen typed methods stay, with the same names and types; each now picks its field from `exif()`'s tags. GPS latitude and longitude are worked out from the GPS tags and their reference tags, and a position with no reference tags gives neither. `backup-metadata --exif` output is unchanged. Model: opus-5-5 Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
This commit was merged in pull request #159.
This commit is contained in:
+31
-25
@@ -13,14 +13,19 @@
|
||||
//
|
||||
// A `Photo` also fetches its own bytes: `original()`, `thumbnail()`,
|
||||
// `download()`, `content()`, `exif()` and the methods that each return one
|
||||
// field of `exif()` go through the on-disk content cache (issue #46), and are
|
||||
// EXIF field go through the on-disk content cache (issue #46), and are
|
||||
// the one place in this module that may touch the network. A library opened
|
||||
// without a content source leaves that cache absent, and those methods then
|
||||
// throw. `savePath` and `isLocal` look only at the disk and need no cache.
|
||||
|
||||
import { readFile } from "node:fs/promises";
|
||||
|
||||
import { readPhotoExif, type PhotoExif } from "../exif.js";
|
||||
import {
|
||||
readAllExifTags,
|
||||
readPhotoExif,
|
||||
type ExifTags,
|
||||
type PhotoExif,
|
||||
} from "../exif.js";
|
||||
import type { CollectionType, EnteFile, FileType } from "../model/types.js";
|
||||
import type { ContentOptions, ContentResult, PhotoContent } from "./content.js";
|
||||
import type { AlbumRecord, PhotoRecord, DerivedRecords } from "./records.js";
|
||||
@@ -156,74 +161,75 @@ export class Photo implements PhotoExifMethods {
|
||||
return readFile(path);
|
||||
}
|
||||
|
||||
// The common EXIF fields of the original, read from `content()`, so this
|
||||
// may download it. EXIF is read from any image format exifreader reads,
|
||||
// JPEG and HEIC/HEIF among them; any other file gives `{}`, and a video
|
||||
// gives it without fetching anything. Like the other content methods, it
|
||||
// throws when there is no content cache, video or not.
|
||||
async exif(opts?: ContentOptions): Promise<PhotoExif> {
|
||||
// Every EXIF tag of the original, keyed by name (see `ExifTags`), read
|
||||
// from `content()`, so this may download it. EXIF is read from any image
|
||||
// format exifreader reads, JPEG and HEIC/HEIF among them; any other file
|
||||
// gives `{}`, and a video gives it without fetching anything. Like the
|
||||
// other content methods, it throws when there is no content cache, video
|
||||
// or not.
|
||||
async exif(opts?: ContentOptions): Promise<ExifTags> {
|
||||
this.cacheOrThrow();
|
||||
if (this.rec.fileType === "video") return {};
|
||||
return readPhotoExif(await this.content(opts));
|
||||
return readAllExifTags(await this.content(opts));
|
||||
}
|
||||
|
||||
// One field of `exif()` each, named and typed as in `PhotoExif`, and
|
||||
// undefined when the file lacks it. Each call runs `exif()`, which reads
|
||||
// the original again.
|
||||
// One field each, named and typed as in `PhotoExif`, picked from the tags
|
||||
// `exif()` returns, and undefined when the file lacks it. Each call runs
|
||||
// `exif()`, which reads the original again.
|
||||
async make(opts?: ContentOptions): Promise<PhotoExif["make"]> {
|
||||
return (await this.exif(opts)).make;
|
||||
return readPhotoExif(await this.exif(opts)).make;
|
||||
}
|
||||
async model(opts?: ContentOptions): Promise<PhotoExif["model"]> {
|
||||
return (await this.exif(opts)).model;
|
||||
return readPhotoExif(await this.exif(opts)).model;
|
||||
}
|
||||
async lensModel(opts?: ContentOptions): Promise<PhotoExif["lensModel"]> {
|
||||
return (await this.exif(opts)).lensModel;
|
||||
return readPhotoExif(await this.exif(opts)).lensModel;
|
||||
}
|
||||
async dateTimeOriginal(
|
||||
opts?: ContentOptions,
|
||||
): Promise<PhotoExif["dateTimeOriginal"]> {
|
||||
return (await this.exif(opts)).dateTimeOriginal;
|
||||
return readPhotoExif(await this.exif(opts)).dateTimeOriginal;
|
||||
}
|
||||
async offsetTimeOriginal(
|
||||
opts?: ContentOptions,
|
||||
): Promise<PhotoExif["offsetTimeOriginal"]> {
|
||||
return (await this.exif(opts)).offsetTimeOriginal;
|
||||
return readPhotoExif(await this.exif(opts)).offsetTimeOriginal;
|
||||
}
|
||||
async exposureTime(
|
||||
opts?: ContentOptions,
|
||||
): Promise<PhotoExif["exposureTime"]> {
|
||||
return (await this.exif(opts)).exposureTime;
|
||||
return readPhotoExif(await this.exif(opts)).exposureTime;
|
||||
}
|
||||
async fNumber(opts?: ContentOptions): Promise<PhotoExif["fNumber"]> {
|
||||
return (await this.exif(opts)).fNumber;
|
||||
return readPhotoExif(await this.exif(opts)).fNumber;
|
||||
}
|
||||
async iso(opts?: ContentOptions): Promise<PhotoExif["iso"]> {
|
||||
return (await this.exif(opts)).iso;
|
||||
return readPhotoExif(await this.exif(opts)).iso;
|
||||
}
|
||||
async focalLength(
|
||||
opts?: ContentOptions,
|
||||
): Promise<PhotoExif["focalLength"]> {
|
||||
return (await this.exif(opts)).focalLength;
|
||||
return readPhotoExif(await this.exif(opts)).focalLength;
|
||||
}
|
||||
async orientation(
|
||||
opts?: ContentOptions,
|
||||
): Promise<PhotoExif["orientation"]> {
|
||||
return (await this.exif(opts)).orientation;
|
||||
return readPhotoExif(await this.exif(opts)).orientation;
|
||||
}
|
||||
async gpsLatitude(
|
||||
opts?: ContentOptions,
|
||||
): Promise<PhotoExif["gpsLatitude"]> {
|
||||
return (await this.exif(opts)).gpsLatitude;
|
||||
return readPhotoExif(await this.exif(opts)).gpsLatitude;
|
||||
}
|
||||
async gpsLongitude(
|
||||
opts?: ContentOptions,
|
||||
): Promise<PhotoExif["gpsLongitude"]> {
|
||||
return (await this.exif(opts)).gpsLongitude;
|
||||
return readPhotoExif(await this.exif(opts)).gpsLongitude;
|
||||
}
|
||||
async gpsAltitude(
|
||||
opts?: ContentOptions,
|
||||
): Promise<PhotoExif["gpsAltitude"]> {
|
||||
return (await this.exif(opts)).gpsAltitude;
|
||||
return readPhotoExif(await this.exif(opts)).gpsAltitude;
|
||||
}
|
||||
|
||||
private cacheOrThrow(): PhotoContent {
|
||||
|
||||
Reference in New Issue
Block a user