feat(exif): write the Xiaomi watermark blob (0x889e) so HyperOS Gallery can frame our captures
HyperOS Gallery reads EXIF 0x889e (a JSON blob) to build its watermark frame; without it a photo fails with "cannot recognize the parameters". CameraX/HAL never supplies that vendor tag, so a capture from this app lacked it while a stock-camera photo carried it. Read back the same props the stock camera derives the blob from (ro.product.device / ro.product.marketname / Build.MANUFACTURER) via the native module, synthesize the blob in exifWrite, and write it only when the source has none and the device is Xiaomi/Redmi/POCO - a source blob is never overwritten.
This commit is contained in:
+19
-2
@@ -2,7 +2,8 @@
|
||||
// EXIF helpers both export engines share (the codec itself lives in exifWrite.ts).
|
||||
import { File } from 'expo-file-system';
|
||||
import * as FileSystem from 'expo-file-system/legacy';
|
||||
import { writeJpegExif, SOFTWARE, type ExifStamp } from './exifWrite';
|
||||
import RecipescamExport from '../../modules/recipescam-export';
|
||||
import { writeJpegExif, SOFTWARE, type DeviceIdentity, type ExifStamp } from './exifWrite';
|
||||
|
||||
// RC_<yyyymmdd_hhmmss>[_exported].jpg — the name the gallery shows for a saved
|
||||
// photo. The exported variant marks a photo re-exported from the library.
|
||||
@@ -33,6 +34,22 @@ export async function readPhotoBytes(uri: string): Promise<Uint8Array | null> {
|
||||
}
|
||||
}
|
||||
|
||||
// Device identity for the Gallery watermark blob (exifWrite.watermarkBlob).
|
||||
// Read once — the props cannot change while the app runs. null when the native
|
||||
// module is older than this call, so stamping still works without it.
|
||||
let deviceIdentity: DeviceIdentity | null | undefined;
|
||||
export function getDeviceIdentity(): DeviceIdentity | null {
|
||||
if (deviceIdentity === undefined) {
|
||||
try {
|
||||
deviceIdentity = RecipescamExport.deviceInfo();
|
||||
} catch (e) {
|
||||
console.warn('deviceInfo unavailable:', e);
|
||||
deviceIdentity = null;
|
||||
}
|
||||
}
|
||||
return deviceIdentity;
|
||||
}
|
||||
|
||||
// Stamp EXIF into a file already on disk (the native engine writes the JPEG
|
||||
// itself, so those bytes never pass through JS). Never throws — a metadata
|
||||
// failure must not cost the user the photo.
|
||||
@@ -40,7 +57,7 @@ export async function stampExifOnFile(fileUri: string, stamp: ExifStamp, sourceU
|
||||
try {
|
||||
const target = new File(fileUri);
|
||||
const source = sourceUri ? await readPhotoBytes(sourceUri) : null;
|
||||
target.write(writeJpegExif(await target.bytes(), { software: SOFTWARE, ...stamp }, source));
|
||||
target.write(writeJpegExif(await target.bytes(), { software: SOFTWARE, device: getDeviceIdentity(), ...stamp }, source));
|
||||
} catch (e) {
|
||||
console.warn('EXIF stamp failed:', e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user