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.
This commit is contained in:
2026-09-11 19:47:51 +07:00
parent 17ee01b62a
commit ea9ff01a22
2 changed files with 27 additions and 1 deletions
+26 -1
View File
@@ -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'));