From ea9ff01a22f1bf10f91e1b5c301c980868e533e3 Mon Sep 17 00:00:00 2001 From: locphamtran Date: Fri, 11 Sep 2026 19:47:51 +0700 Subject: [PATCH] feat(exif): name the capture device and place (0x9a00/0x0110/0x889d) for the Gallery frame The framing tool matches Make/Model against its known-device list and prints the place name; a CameraX capture only carried the raw model codename (2203121C) and no 0x9a00/0x889d, which is what a stock camera photo always has. State the market name plus the geotag locality, and leave a source that already carries 0x9a00 untouched so a library photo of another device is never relabelled. --- src/utils/exifWrite.ts | 27 ++++++++++++++++++++++++++- src/utils/exportEngine.ts | 1 + 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/utils/exifWrite.ts b/src/utils/exifWrite.ts index 10cfb82..0111320 100644 --- a/src/utils/exifWrite.ts +++ b/src/utils/exifWrite.ts @@ -22,6 +22,10 @@ export interface ExifStamp { // DateTimeOriginal and only fill it in when the source has none. dateTime?: Date | null; gps?: { latitude: number; longitude: number } | null; + // Place name shown on the Gallery frame (EXIF 0x889d). Stock photos carry it + // whenever the capture was geotagged; ours have to state it from the same + // info the watermark prints. + locationName?: string | null; width?: number | null; height?: number | null; dpi?: number | null; @@ -394,10 +398,31 @@ export function writeJpegExif(jpeg: Uint8Array, stamp: ExifStamp, sourceJpeg?: U // HyperOS Gallery refuses to open its watermark editor without 0x889e, and a // CameraX capture never carries it. Never overwrite a blob the source has. - if (stamp.device && !exif.has(0x889e) && /xiaomi|redmi|poco/i.test(stamp.device.manufacturer)) { + const xiaomi = !!stamp.device && /xiaomi|redmi|poco/i.test(stamp.device.manufacturer); + if (stamp.device && xiaomi && !exif.has(0x889e)) { exif.set(0x889e, asciiEntry(0x889e, watermarkBlob(stamp.device))); } + // Stock Xiaomi photos name the device in 0x9a00 and in Model (0x0110) as the + // MARKET name ("Xiaomi 14 Ultra"); the framing tool matches those against its + // known-device list and reports an unknown device otherwise. A CameraX capture + // only has the raw codename, so state the market name. A source that already + // carries 0x9a00 is a stock photo of some other device — leave it alone. + if (stamp.device && xiaomi && !ifd0.has(0x9a00)) { + const market = (stamp.device.marketName || stamp.device.model).trim(); + if (market) { + ifd0.set(0x9a00, asciiEntry(0x9a00, market)); + ifd0.set(0x0110, asciiEntry(0x0110, market)); + } + } + + // Place name the frame prints (0x889d) — stock writes it in both IFDs. + const place = stamp.locationName?.trim(); + if (place) { + if (!ifd0.has(0x889d)) ifd0.set(0x889d, asciiEntry(0x889d, place)); + if (!exif.has(0x889d)) exif.set(0x889d, asciiEntry(0x889d, place)); + } + if (stamp.gps) { const { latitude: lat, longitude: lon } = stamp.gps; gps.set(0x0001, asciiEntry(0x0001, lat >= 0 ? 'N' : 'S')); diff --git a/src/utils/exportEngine.ts b/src/utils/exportEngine.ts index 1e2091c..87e4a76 100644 --- a/src/utils/exportEngine.ts +++ b/src/utils/exportEngine.ts @@ -721,6 +721,7 @@ export async function processAndExportPhoto( width: resultImage.width(), height: resultImage.height(), dpi: options?.dpi ?? null, + locationName: useGeotag && gpsInfo ? gpsInfo.locality : null, device: getDeviceIdentity(), }, await readPhotoBytes(sourceUri)