diff --git a/worker/index.js b/worker/index.js index 91752a6..ba61426 100644 --- a/worker/index.js +++ b/worker/index.js @@ -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 }); diff --git a/worker/ingest.js b/worker/ingest.js index 8fb0bd1..6c24d08 100644 --- a/worker/ingest.js +++ b/worker/ingest.js @@ -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;