Vercel/src/photo/form/server.ts
Rich Manalang ec55005df2
feat: add Nikon Z Picture Control support (#361)
* feat: add Nikon Z Picture Control support

* refactor: Consolidate Fujifilm and Nikon MakerNote parsing logic and remove unused code and comments.

* fix: decode film parameter before fetching photo data.

* fix: decode URL-encoded film parameters for improved routing

* feat: Add default set of Nikon Picture Controls for consistent labels and allow for Picture Controls not already in the database to be picked from the drop down.

* feat: Add camera make context to film components for conditional Fujifilm simulation display
2025-12-28 14:23:33 -05:00

85 lines
2.8 KiB
TypeScript

import {
getCompatibleExifValue,
convertApertureValueToFNumber,
getAspectRatioFromExif,
getOffsetFromExif,
} from '@/utility/exif';
import {
convertTimestampWithOffsetToPostgresString,
convertTimestampToNaivePostgresString,
} from '@/utility/date';
import { GEO_PRIVACY_ENABLED } from '@/app/config';
import { PhotoExif } from '..';
import { FujifilmRecipe } from '@/platforms/fujifilm/recipe';
import { FujifilmSimulation } from '@/platforms/fujifilm/simulation';
import type { ExifData, ExifTags } from 'ts-exif-parser';
import { NikonPictureControl } from '@/platforms/nikon/simulation';
export const convertExifToFormData = (
exif: ExifData,
exifr?: any,
film?: FujifilmSimulation | NikonPictureControl,
recipeData?: FujifilmRecipe,
): Partial<Record<keyof PhotoExif, string | undefined>> => {
let title: string | undefined = exifr?.title?.value;
let caption: string | undefined;
const description: string | undefined =
exif.tags?.ImageDescription ||
exifr?.ImageDescription ||
exifr?.description?.value;
const tags: string[] | undefined = exifr?.subject;
if (title && title !== description) {
caption = description;
} else {
title = description;
}
// Convenience function with exif + exifr in scope
const getExifValue = (
key: keyof ExifTags,
exifrSpecificKey?: string,
) => getCompatibleExifValue(key, exif, exifr, exifrSpecificKey);
const dateTimeOriginal = getExifValue('DateTimeOriginal');
return {
aspectRatio: getAspectRatioFromExif(exif).toString(),
make: getExifValue('Make'),
model: getExifValue('Model'),
focalLength: getExifValue('FocalLength')?.toString(),
focalLengthIn35MmFormat:getExifValue('FocalLengthIn35mmFormat')?.toString(),
lensMake: getExifValue('LensMake'),
lensModel: getExifValue('LensModel'),
fNumber: (
getExifValue('FNumber')?.toString() ||
convertApertureValueToFNumber(getExifValue('ApertureValue'))
),
iso:
getExifValue('ISO')?.toString() ||
getExifValue('ISOSpeed')?.toString(),
exposureTime: getExifValue('ExposureTime')?.toString(),
exposureCompensation: getExifValue('ExposureCompensation')?.toString(),
latitude: !GEO_PRIVACY_ENABLED
? getExifValue('GPSLatitude', 'latitude')?.toString()
: undefined,
longitude: !GEO_PRIVACY_ENABLED
? getExifValue('GPSLongitude', 'longitude')?.toString()
: undefined,
film,
recipeData: JSON.stringify(recipeData),
...dateTimeOriginal && {
takenAt: convertTimestampWithOffsetToPostgresString(
dateTimeOriginal,
getOffsetFromExif(exif, exifr),
),
takenAtNaive:
convertTimestampToNaivePostgresString(dateTimeOriginal),
},
...title && { title },
...caption && { caption },
...Array.isArray(tags) && { tags: tags.join(', ') },
};
};