web: stamp the photo's own day on the GPS mark
The GPS mark printed Date.now(), so a photo taken in 2019 carried the day it was opened. It now prints the frame's EXIF date — DateTimeOriginal, falling back on CreateDate then ModifyDate — wherever the position came from: - readCapturedAt() reads the date off the file, and readGps() uses it for a position found in the same EXIF. - adoptPhoto holds it in its own state, so a frame with a date but no position still stamps the date when the position is typed in by hand. - The device's own position stamps it too. That path runs inside adoptPhoto, where the render still holds the previous photo's date, so locateMe takes the date as an argument rather than reading state — the panel's own button, which has no such date to hand, passes none and reads the state as before. A file with no date at all still falls back on the visitor's clock: there is nothing else to believe.
This commit is contained in:
@@ -113,6 +113,27 @@ async function shrinkOrKeep(bytes: Uint8Array, maxDim: number, maxBytes: number)
|
||||
}
|
||||
}
|
||||
|
||||
// When the photo was taken, per its own EXIF. DateTimeOriginal is the shutter;
|
||||
// the two file timestamps behind it are what an editor rewrote along the way.
|
||||
// EXIF stores the time without a timezone, in the camera's local time, and exifr
|
||||
// hands it back as a Date in ours — which is what a camera prints on a stamp, and
|
||||
// what someone opening a 2019 photo today expects to read under the coordinates.
|
||||
// Null when the file carries no date at all; the caller falls back to its clock.
|
||||
export async function readCapturedAt(bytes: Uint8Array): Promise<number | null> {
|
||||
try {
|
||||
const tags = (await exifr.parse(bytes, {
|
||||
pick: ['DateTimeOriginal', 'CreateDate', 'ModifyDate'],
|
||||
})) as Record<string, unknown> | undefined;
|
||||
for (const tag of ['DateTimeOriginal', 'CreateDate', 'ModifyDate']) {
|
||||
const value = tags?.[tag];
|
||||
if (value instanceof Date && Number.isFinite(value.getTime())) return value.getTime();
|
||||
}
|
||||
} catch {
|
||||
// Unreadable EXIF is the same as no EXIF here.
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// EXIF GPS of the loaded photo, in the shape the renderer + EXIF writer expect.
|
||||
// Returns null when the photo has none — the caller then offers manual entry.
|
||||
export async function readGps(bytes: Uint8Array): Promise<GPSInfo | null> {
|
||||
@@ -122,7 +143,8 @@ export async function readGps(bytes: Uint8Array): Promise<GPSInfo | null> {
|
||||
return {
|
||||
latitude: gps.latitude,
|
||||
longitude: gps.longitude,
|
||||
timestamp: Date.now(),
|
||||
// The stamp prints when the photo was taken, not when it was opened.
|
||||
timestamp: (await readCapturedAt(bytes)) ?? Date.now(),
|
||||
source: 'exif',
|
||||
};
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user