feat(exif): stamp RC_<ts> filenames + EXIF on capture and export
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
// Gallery-facing photo metadata: the RC_ filename convention and the file-level
|
||||
// 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';
|
||||
|
||||
// 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.
|
||||
export function rcPhotoName(exported: boolean, date = new Date()): string {
|
||||
const p = (n: number) => String(n).padStart(2, '0');
|
||||
const stamp = `${date.getFullYear()}${p(date.getMonth() + 1)}${p(date.getDate())}_${p(date.getHours())}${p(date.getMinutes())}${p(date.getSeconds())}`;
|
||||
return `RC_${stamp}${exported ? '_exported' : ''}.jpg`;
|
||||
}
|
||||
|
||||
// Cache path for the next export. Two renders landing in the same second would
|
||||
// collide on the name AND on RN's <Image> cache (keyed by URI), so the
|
||||
// timestamp walks forward a second at a time instead of taking a suffix.
|
||||
export function nextPhotoPath(exported: boolean): string {
|
||||
let ms = Date.now();
|
||||
for (;;) {
|
||||
const path = `${FileSystem.cacheDirectory}${rcPhotoName(exported, new Date(ms))}`;
|
||||
if (!new File(path).exists) return path;
|
||||
ms += 1000;
|
||||
}
|
||||
}
|
||||
|
||||
export async function readPhotoBytes(uri: string): Promise<Uint8Array | null> {
|
||||
try {
|
||||
return await new File(uri).bytes();
|
||||
} catch (e) {
|
||||
console.warn('EXIF source read failed:', e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
export async function stampExifOnFile(fileUri: string, stamp: ExifStamp, sourceUri?: string | null): Promise<void> {
|
||||
try {
|
||||
const target = new File(fileUri);
|
||||
const source = sourceUri ? await readPhotoBytes(sourceUri) : null;
|
||||
target.write(writeJpegExif(await target.bytes(), { software: SOFTWARE, ...stamp }, source));
|
||||
} catch (e) {
|
||||
console.warn('EXIF stamp failed:', e);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user