124_webcalibre2/src/lib/transformers.ts

111 lines
3.7 KiB
TypeScript
Raw Normal View History

/**
* Helper: Converts [Degrees, Minutes, Seconds] to Decimal Degrees
*/
const convertDMSToDD = (dms: number[] | undefined, ref: string | undefined): number | null => {
if (!dms || dms.length < 3) return null;
const [degrees, minutes, seconds] = dms;
let dd = degrees + (minutes / 60) + (seconds / 3600);
if (ref === 'S' || ref === 'W') {
dd = dd * -1;
}
return parseFloat(dd.toFixed(6));
};
/**
* MAPPER: Transforms the raw database JSON into a structured GUI object.
* Designed to handle the lowercase keys generated by the sanitizer.
*/
export const mapImageMetadata = (metadata: any) => {
if (!metadata || !metadata.details) {
return { title: metadata?.title || "Unknown File" };
}
const { details, dimensions, title } = metadata;
const { image = {}, photo = {}, gpsInfo = {} } = details;
return {
fileName: title,
device: `${image.make || ''} ${image.model || ''}`.trim() || 'Unknown Device',
// Dates from EXIF are strings after sanitization
timestamp: photo.dateTimeOriginal ? new Date(photo.dateTimeOriginal) : null,
resolution: {
width: photo.pixelXDimension || dimensions?.split('x')[0],
height: photo.pixelYDimension || dimensions?.split('x')[1],
},
settings: {
aperture: photo.fNumber ? `f/${photo.fNumber}` : 'N/A',
shutterSpeed: photo.exposureTime
? (photo.exposureTime < 1
? `1/${Math.round(1 / photo.exposureTime)}s`
: `${photo.exposureTime}s`)
: 'N/A',
iso: photo.iSOSpeedRatings || 'N/A',
focalLength: photo.focalLength ? `${photo.focalLength}mm` : 'N/A',
},
location: {
latitude: convertDMSToDD(gpsInfo.gpsLatitude, gpsInfo.gpsLatitudeRef),
longitude: convertDMSToDD(gpsInfo.gpsLongitude, gpsInfo.gpsLongitudeRef),
altitude: gpsInfo.gpsAltitude ? Math.round(gpsInfo.gpsAltitude) : null,
mapUrl: (gpsInfo.gpsLatitude && gpsInfo.gpsLongitude)
? `https://www.google.com/maps?q=${convertDMSToDD(gpsInfo.gpsLatitude, gpsInfo.gpsLatitudeRef)},${convertDMSToDD(gpsInfo.gpsLongitude, gpsInfo.gpsLongitudeRef)}`
: null
}
};
2026-02-12 11:55:43 +00:00
};
// src/lib/transformers.ts
/**
* Transforms the raw database FileNode into a structured object for the UI.
*/
export const mapMetadata = (node: any) => {
if (!node) return { title: "Unknown File" };
const meta = node.metadata || {};
const fileName = node.name;
const extension = fileName.split('.').pop()?.toLowerCase();
// --- PDF Logic ---
if (extension === 'pdf') {
return {
fileName,
type: 'PDF Document',
title: meta.title || fileName,
author: meta.author || 'Unknown Author',
subject: meta.subject || 'N/A',
pageCount: meta.pageCount || 0,
keywords: meta.keywords || 'None',
textPreview: node.description || meta.textPreview || '', // description field often stores the preview
creator: meta.creator || 'N/A',
producer: meta.producer || 'N/A'
};
}
// --- Image Logic (Sharp/Exif) ---
if (['jpg', 'jpeg', 'png', 'webp'].includes(extension || '')) {
// Note: your extractor lowercase keys via the sanitizer
return {
fileName,
type: 'Image',
device: `${meta.image?.make || ''} ${meta.image?.model || ''}`.trim() || 'Unknown Device',
timestamp: meta.photo?.dateTimeOriginal ? new Date(meta.photo.dateTimeOriginal) : null,
settings: {
aperture: meta.photo?.fNumber ? `f/${meta.photo.fNumber}` : 'N/A',
iso: meta.photo?.iSOSpeedRatings || 'N/A',
focalLength: meta.photo?.focalLength ? `${meta.photo.focalLength}mm` : 'N/A',
}
};
}
// --- Default Fallback ---
return {
fileName,
type: extension?.toUpperCase() || 'File',
details: meta
};
};