feat: extract and store GPS coords
This commit is contained in:
@@ -271,6 +271,75 @@ function parseExifDate(dateStr: string | undefined): Date | null {
|
||||
return isNaN(date.getTime()) ? null : date;
|
||||
}
|
||||
|
||||
function parseGpsParts(parts: number[]): number | null {
|
||||
if (parts.length === 0 || !Number.isFinite(parts[0])) return null;
|
||||
const [deg, min, sec] = parts;
|
||||
const sign = deg < 0 ? -1 : 1;
|
||||
let value = Math.abs(deg);
|
||||
if (Number.isFinite(min)) value += Math.abs(min) / 60;
|
||||
if (Number.isFinite(sec)) value += Math.abs(sec) / 3600;
|
||||
return sign * value;
|
||||
}
|
||||
|
||||
function parseGpsValue(value: unknown): number | null {
|
||||
if (typeof value === "number") {
|
||||
return Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) return null;
|
||||
const direct = Number(trimmed);
|
||||
if (!Number.isNaN(direct)) return direct;
|
||||
const parts = trimmed.match(/-?\d+(?:\.\d+)?/g);
|
||||
if (!parts) return null;
|
||||
return parseGpsParts(parts.map((part) => Number(part)).filter(Number.isFinite));
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
const parts = value
|
||||
.map((part) => {
|
||||
if (typeof part === "number") return part;
|
||||
if (typeof part === "string") return Number(part);
|
||||
return NaN;
|
||||
})
|
||||
.filter(Number.isFinite);
|
||||
return parseGpsParts(parts);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function applyRefSign(value: number, ref: unknown): number {
|
||||
if (typeof ref !== "string") return value;
|
||||
const normalized = ref.trim().toUpperCase();
|
||||
if (normalized === "S" || normalized === "W") return -Math.abs(value);
|
||||
if (normalized === "N" || normalized === "E") return Math.abs(value);
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseGpsCoord(
|
||||
value: unknown,
|
||||
ref: unknown,
|
||||
kind: "lat" | "lon",
|
||||
): number | null {
|
||||
const parsed = parseGpsValue(value);
|
||||
if (parsed === null) return null;
|
||||
const signed = applyRefSign(parsed, ref);
|
||||
if (!Number.isFinite(signed)) return null;
|
||||
if (kind === "lat") {
|
||||
return signed >= -90 && signed <= 90 ? signed : null;
|
||||
}
|
||||
return signed >= -180 && signed <= 180 ? signed : null;
|
||||
}
|
||||
|
||||
function extractGps(tags: Record<string, unknown>) {
|
||||
const lat = parseGpsCoord(tags.GPSLatitude, tags.GPSLatitudeRef, "lat");
|
||||
const lon = parseGpsCoord(tags.GPSLongitude, tags.GPSLongitudeRef, "lon");
|
||||
if (lat === null || lon === null) return null;
|
||||
return { lat, lon };
|
||||
}
|
||||
|
||||
function isPlausibleCaptureTs(date: Date) {
|
||||
const ts = date.getTime();
|
||||
if (!Number.isFinite(ts)) return false;
|
||||
@@ -351,7 +420,9 @@ export async function handleProcessAsset(raw: unknown) {
|
||||
thumb_small_key: null,
|
||||
thumb_med_key: null,
|
||||
poster_key: null,
|
||||
raw_tags_json: null
|
||||
raw_tags_json: null,
|
||||
gps_lat: null,
|
||||
gps_lon: null
|
||||
};
|
||||
let rawTags: Record<string, unknown> = {};
|
||||
let captureTs: Date | null = null;
|
||||
@@ -425,6 +496,11 @@ export async function handleProcessAsset(raw: unknown) {
|
||||
if (asset.media_type === "image") {
|
||||
rawTags = await tryReadExifTags();
|
||||
maybeSetCaptureDateFromTags(rawTags);
|
||||
const gps = extractGps(rawTags);
|
||||
if (gps) {
|
||||
updates.gps_lat = gps.lat;
|
||||
updates.gps_lon = gps.lon;
|
||||
}
|
||||
await applyObjectMtimeFallback();
|
||||
|
||||
|
||||
@@ -470,6 +546,11 @@ export async function handleProcessAsset(raw: unknown) {
|
||||
} else if (asset.media_type === "video") {
|
||||
rawTags = await tryReadExifTags();
|
||||
maybeSetCaptureDateFromTags(rawTags);
|
||||
const gps = extractGps(rawTags);
|
||||
if (gps) {
|
||||
updates.gps_lat = gps.lat;
|
||||
updates.gps_lon = gps.lon;
|
||||
}
|
||||
|
||||
const ffprobeOutput = await runCommand("ffprobe", [
|
||||
"-v",
|
||||
@@ -583,7 +664,9 @@ export async function handleProcessAsset(raw: unknown) {
|
||||
"thumb_small_key",
|
||||
"thumb_med_key",
|
||||
"poster_key",
|
||||
"raw_tags_json"
|
||||
"raw_tags_json",
|
||||
"gps_lat",
|
||||
"gps_lon"
|
||||
)}, status = 'ready', error_message = null
|
||||
where id = ${asset.id}
|
||||
`;
|
||||
|
||||
Reference in New Issue
Block a user