/** * 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 } }; }; // 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 }; };