import * as exifr from 'exifr' import { DEFAULT_FOCAL_MM, FILM_35MM_DIAGONAL_MM } from './sharpConstants' export type FocalSource = 'exif-35mm' | 'exif-mm-approx' | 'default-30mm' export interface FocalEstimate { focalPx: number focalMmUsed: number source: FocalSource exif35mm?: number exifFocalMm?: number } export function convertFocalMmToPx(width: number, height: number, focalMm: number): number { return (focalMm * Math.hypot(width, height)) / FILM_35MM_DIAGONAL_MM } function parseFiniteNumber(value: unknown): number | undefined { if (typeof value === 'number' && Number.isFinite(value)) { return value } if (typeof value === 'string' && value.trim().length > 0) { const parsed = Number(value) if (Number.isFinite(parsed)) { return parsed } } if (value && typeof value === 'object') { const maybeRational = value as { numerator?: unknown; denominator?: unknown } const numerator = typeof maybeRational.numerator === 'number' ? maybeRational.numerator : undefined const denominator = typeof maybeRational.denominator === 'number' ? maybeRational.denominator : undefined if ( numerator !== undefined && denominator !== undefined && Number.isFinite(numerator) && Number.isFinite(denominator) && denominator !== 0 ) { return numerator / denominator } } return undefined } export async function estimateFocalLengthFromFile( file: File, width: number, height: number, ): Promise { let exifData: Record | undefined try { exifData = (await exifr.parse(file, [ 'FocalLengthIn35mmFilm', 'FocalLenIn35mmFilm', 'FocalLength', ])) as Record | undefined } catch { exifData = undefined } const focal35mm = parseFiniteNumber(exifData?.FocalLengthIn35mmFilm) ?? parseFiniteNumber(exifData?.FocalLenIn35mmFilm) const focalMmExif = parseFiniteNumber(exifData?.FocalLength) if (focal35mm !== undefined && focal35mm >= 1) { return { focalPx: convertFocalMmToPx(width, height, focal35mm), focalMmUsed: focal35mm, source: 'exif-35mm', exif35mm: focal35mm, exifFocalMm: focalMmExif, } } if (focalMmExif !== undefined && focalMmExif > 0) { const normalizedFocalMm = focalMmExif < 10 ? focalMmExif * 8.4 : focalMmExif return { focalPx: convertFocalMmToPx(width, height, normalizedFocalMm), focalMmUsed: normalizedFocalMm, source: 'exif-mm-approx', exifFocalMm: focalMmExif, } } return { focalPx: convertFocalMmToPx(width, height, DEFAULT_FOCAL_MM), focalMmUsed: DEFAULT_FOCAL_MM, source: 'default-30mm', } }