fix: GPS extraction broken by exifr pick filter — use exifr.gps() separately
The pick option creates an allowlist for the merged EXIF output, which drops latitude/longitude even when gps:true is set. Fixed by calling exifr.gps() as a separate step (bypasses the pick filter entirely). Also adds a startup backfill that re-extracts GPS for already-indexed photos where gps_lat IS NULL, including reverse geocoding for place names. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
eeab631ac9
commit
3c0cb2b68d
2 files changed
+48
-6
No files matched your search
@@ -2,8 +2,10 @@ import 'dotenv/config';
|
||||
import chokidar from 'chokidar';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import exifr from 'exifr';
|
||||
import { openDb } from './db.js';
|
||||
import { ingestFile, isSupported } from './ingest.js';
|
||||
import { reverseGeocode } from './geocode.js';
|
||||
|
||||
const PHOTO_DIR = process.env.PHOTO_DIR ?? null;
|
||||
const DB_PATH = process.env.DB_PATH ?? './data/library.db';
|
||||
@@ -107,6 +109,40 @@ function syncWatchFolders() {
|
||||
syncWatchFolders();
|
||||
setInterval(syncWatchFolders, 30_000);
|
||||
|
||||
// Backfill GPS for photos indexed before the exifr pick-filter bug was fixed
|
||||
async function backfillGps() {
|
||||
const rows = db.raw.prepare(
|
||||
"SELECT id, filepath FROM photos WHERE gps_lat IS NULL"
|
||||
).all().filter(r => fs.existsSync(r.filepath));
|
||||
|
||||
if (rows.length === 0) return;
|
||||
console.log(`[worker] GPS backfill: checking ${rows.length} photos`);
|
||||
|
||||
const updateStmt = db.raw.prepare(
|
||||
'UPDATE photos SET gps_lat = ?, gps_lon = ?, place_city = ?, place_country = ? WHERE id = ?'
|
||||
);
|
||||
|
||||
let updated = 0;
|
||||
for (const { id, filepath } of rows) {
|
||||
try {
|
||||
const gps = await exifr.gps(filepath);
|
||||
if (gps?.latitude == null) continue;
|
||||
let city = null, country = null;
|
||||
try {
|
||||
const place = await reverseGeocode(gps.latitude, gps.longitude);
|
||||
city = place?.city ?? null;
|
||||
country = place?.country ?? null;
|
||||
} catch {}
|
||||
updateStmt.run(gps.latitude, gps.longitude, city, country, id);
|
||||
updated++;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (updated > 0) console.log(`[worker] GPS backfill: updated ${updated} photos with location data`);
|
||||
}
|
||||
|
||||
setTimeout(backfillGps, 5000);
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\n[worker] shutting down');
|
||||
db.setIngestStatus({ active: false, current: null, scanned, errors });
|
||||
|
||||
+12
-6
@@ -44,14 +44,17 @@ async function ingestPhoto(filepath, db, posterDir, settings) {
|
||||
try {
|
||||
exif = await exifr.parse(filepath, {
|
||||
pick: ['DateTimeOriginal', 'Make', 'Model', 'Orientation'],
|
||||
gps: true,
|
||||
}) ?? {};
|
||||
} catch {}
|
||||
|
||||
const dateTaken = exif.DateTimeOriginal ?? null;
|
||||
const cameraModel = [exif.Make, exif.Model].filter(Boolean).join(' ') || null;
|
||||
const gpsLat = exif.latitude ?? null;
|
||||
const gpsLon = exif.longitude ?? null;
|
||||
|
||||
let gpsLat = null, gpsLon = null;
|
||||
try {
|
||||
const gps = await exifr.gps(filepath);
|
||||
if (gps?.latitude != null) { gpsLat = gps.latitude; gpsLon = gps.longitude; }
|
||||
} catch {}
|
||||
|
||||
let placeCity = null, placeCountry = null;
|
||||
if (gpsLat !== null && gpsLon !== null) {
|
||||
@@ -142,12 +145,15 @@ async function ingestVideo(filepath, db, posterDir, settings) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. exifr — GPS + more accurate date (best-effort)
|
||||
// 2. exifr — more accurate date + GPS (best-effort, parsed separately to avoid pick filtering GPS)
|
||||
let gpsLat = null, gpsLon = null;
|
||||
try {
|
||||
const exif = await exifr.parse(filepath, { gps: true, pick: ['DateTimeOriginal'] }) ?? {};
|
||||
const exif = await exifr.parse(filepath, { pick: ['DateTimeOriginal'] }) ?? {};
|
||||
if (exif.DateTimeOriginal) dateTaken = new Date(exif.DateTimeOriginal).toISOString();
|
||||
if (exif.latitude != null) { gpsLat = exif.latitude; gpsLon = exif.longitude; }
|
||||
} catch {}
|
||||
try {
|
||||
const gps = await exifr.gps(filepath);
|
||||
if (gps?.latitude != null) { gpsLat = gps.latitude; gpsLon = gps.longitude; }
|
||||
} catch {}
|
||||
|
||||
let placeCity = null, placeCountry = null;
|
||||
|
||||
Reference in new issue
Block a user