Refine Nikon implementation
This commit is contained in:
parent
f6367e29e7
commit
0e97d292a1
@ -67,21 +67,20 @@ export default async function PhotoFilmPage({
|
||||
params,
|
||||
}: PhotoFilmProps) {
|
||||
const { photoId, film } = await params;
|
||||
const decodedFilm = decodeURIComponent(film);
|
||||
|
||||
const { photo, photos, photosGrid, indexNumber } =
|
||||
await getPhotosNearIdCachedCached(photoId, decodedFilm);
|
||||
await getPhotosNearIdCachedCached(photoId, film);
|
||||
|
||||
if (!photo) { redirect(PATH_ROOT); }
|
||||
|
||||
const { count, dateRange } = await getPhotosMetaCached({ film: decodedFilm });
|
||||
const { count, dateRange } = await getPhotosMetaCached({ film });
|
||||
|
||||
return (
|
||||
<PhotoDetailPage {...{
|
||||
photo,
|
||||
photos,
|
||||
photosGrid,
|
||||
film: decodedFilm,
|
||||
film,
|
||||
indexNumber,
|
||||
count,
|
||||
dateRange,
|
||||
|
||||
@ -30,7 +30,7 @@ export async function GET(
|
||||
] = await Promise.all([
|
||||
getPhotosCached({
|
||||
limit: MAX_PHOTOS_TO_SHOW_PER_CATEGORY,
|
||||
film: film,
|
||||
film,
|
||||
}),
|
||||
getIBMPlexMono(),
|
||||
getImageResponseCacheControlHeaders(),
|
||||
|
||||
@ -66,12 +66,11 @@ export default async function FilmPage({
|
||||
params,
|
||||
}: FilmProps) {
|
||||
const { film } = await params;
|
||||
const decodedFilm = decodeURIComponent(film);
|
||||
|
||||
const [
|
||||
photos,
|
||||
{ count, dateRange },
|
||||
] = await getPhotosFilmDataCachedCached(decodedFilm);
|
||||
] = await getPhotosFilmDataCachedCached(film);
|
||||
|
||||
if (photos.length === 0) { redirect(PATH_ROOT); }
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
"start": "next start",
|
||||
"lint": "eslint .",
|
||||
"test": "jest --watch --transformIgnorePatterns 'node_modules/(?!my-library-dir)/'",
|
||||
"analyze": "ANALYZE=true next build"
|
||||
"analyze": "ANALYZE=true next build --webpack"
|
||||
},
|
||||
"packageManager": "pnpm@10.26.2",
|
||||
"dependencies": {
|
||||
|
||||
@ -218,7 +218,7 @@ export const pathForRecipe = (recipe: string) =>
|
||||
`${PREFIX_RECIPE}/${recipe}`;
|
||||
|
||||
export const pathForFilm = (film: string) =>
|
||||
`${PREFIX_FILM}/${film}`;
|
||||
`${PREFIX_FILM}/${parameterize(film)}`;
|
||||
|
||||
export const pathForFocalLength = (focal: number) =>
|
||||
`${PREFIX_FOCAL_LENGTH}/${focal}mm`;
|
||||
|
||||
@ -15,15 +15,14 @@ export default function FilmOverview({
|
||||
dateRange?: PhotoDateRangePostgres,
|
||||
animateOnFirstLoadOnly?: boolean,
|
||||
}) {
|
||||
const decodedFilm = decodeURIComponent(film);
|
||||
return (
|
||||
<PhotoGridContainer {...{
|
||||
cacheKey: `film-${film}`,
|
||||
photos,
|
||||
count,
|
||||
film: decodedFilm,
|
||||
film,
|
||||
header: <FilmHeader {...{
|
||||
film: decodedFilm,
|
||||
film,
|
||||
photos,
|
||||
count,
|
||||
dateRange,
|
||||
|
||||
@ -16,11 +16,7 @@ import {
|
||||
isStringNikonPictureControl,
|
||||
labelForNikonPictureControl,
|
||||
} from '@/platforms/nikon/simulation';
|
||||
import {
|
||||
deparameterize,
|
||||
formatCount,
|
||||
formatCountDescriptive,
|
||||
} from '@/utility/string';
|
||||
import { deparameterize } from '@/utility/string';
|
||||
import { AnnotatedTag } from '@/photo/form';
|
||||
import PhotoFilmIcon from './PhotoFilmIcon';
|
||||
import { AppTextState } from '@/i18n/state';
|
||||
|
||||
@ -151,8 +151,8 @@ const FORM_METADATA = (
|
||||
film: {
|
||||
section: 'exif',
|
||||
label: 'film',
|
||||
note: 'Intended for Fujifilm / Nikon cameras and analog scans',
|
||||
noteShort: 'Fujifilm / Nikon cameras / analog scans',
|
||||
note: 'Intended for Fujifilm / Nikon / analog scans',
|
||||
noteShort: 'Fujifilm / Nikon / analog scans',
|
||||
tagOptions: filmOptions,
|
||||
tagOptionsLimit: 1,
|
||||
shouldNotOverwriteWithNullDataOnSync: true,
|
||||
|
||||
@ -13,8 +13,8 @@ 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';
|
||||
import { parameterize } from '@/utility/string';
|
||||
|
||||
export const convertExifToFormData = (
|
||||
exif: ExifData,
|
||||
@ -67,7 +67,9 @@ export const convertExifToFormData = (
|
||||
longitude: !GEO_PRIVACY_ENABLED
|
||||
? getExifValue('GPSLongitude', 'longitude')?.toString()
|
||||
: undefined,
|
||||
film,
|
||||
// Make sure film is parameterized since
|
||||
// Nikon picture controls may not be parameterized
|
||||
film: film ? parameterize(film) : undefined,
|
||||
recipeData: JSON.stringify(recipeData),
|
||||
...dateTimeOriginal && {
|
||||
takenAt: convertTimestampWithOffsetToPostgresString(
|
||||
|
||||
@ -153,6 +153,7 @@ export const extractImageDataFromBlobPath = async (
|
||||
error,
|
||||
};
|
||||
};
|
||||
|
||||
const generateBase64 = async (
|
||||
image: ArrayBuffer,
|
||||
middleware?: (sharp: Sharp) => Sharp,
|
||||
|
||||
@ -1,10 +1,6 @@
|
||||
import type { ExifData } from 'ts-exif-parser';
|
||||
import { isMakeNikon } from '.';
|
||||
|
||||
// Nikon MakerNote Header
|
||||
const NIKON_MAKERNOTE_HEADER = 'Nikon\x00\x02\x00\x00\x00';
|
||||
const HEADER_SIZE = 18;
|
||||
|
||||
export const isExifForNikon = (data: ExifData) => isMakeNikon(data.tags?.Make);
|
||||
|
||||
export const parseNikonMakerNote = (
|
||||
@ -16,17 +12,17 @@ export const parseNikonMakerNote = (
|
||||
return;
|
||||
}
|
||||
|
||||
// Assume Type 3 for Z series
|
||||
// Skip 10 bytes header
|
||||
const baseOffset = 10;
|
||||
|
||||
const tiffStart = 10;
|
||||
if (bytes.length < tiffStart + 8) return;
|
||||
|
||||
const isLE = bytes.toString('hex', tiffStart, tiffStart + 2) === '4949';
|
||||
|
||||
const readUInt16 = (offset: number) => isLE ? bytes.readUInt16LE(offset) : bytes.readUInt16BE(offset);
|
||||
const readUInt32 = (offset: number) => isLE ? bytes.readUInt32LE(offset) : bytes.readUInt32BE(offset);
|
||||
const readUInt16 = (offset: number) => isLE
|
||||
? bytes.readUInt16LE(offset)
|
||||
: bytes.readUInt16BE(offset);
|
||||
const readUInt32 = (offset: number) => isLE
|
||||
? bytes.readUInt32LE(offset)
|
||||
: bytes.readUInt32BE(offset);
|
||||
|
||||
const ifdOffset = readUInt32(tiffStart + 4);
|
||||
let currentOffset = tiffStart + ifdOffset;
|
||||
@ -55,7 +51,8 @@ export const parseNikonMakerNote = (
|
||||
}
|
||||
|
||||
if (offset + count <= bytes.length) {
|
||||
value = bytes.toString('ascii', offset, offset + count - 1); // -1 to remove null terminator
|
||||
// -1 to remove null terminator
|
||||
value = bytes.toString('ascii', offset, offset + count - 1);
|
||||
}
|
||||
} else if (type === 7) {
|
||||
let offset = valueOffsetOrData;
|
||||
|
||||
@ -1,99 +1,78 @@
|
||||
|
||||
import { deparameterize } from '@/utility/string';
|
||||
import { parseNikonMakerNote } from './server';
|
||||
|
||||
const TAG_ID_PICTURE_CONTROL_DATA = 0x0023;
|
||||
|
||||
export type NikonPictureControl = string;
|
||||
|
||||
export interface NikonPictureControlLabel {
|
||||
small: string
|
||||
medium: string
|
||||
large: string
|
||||
}
|
||||
|
||||
const NIKON_PICTURE_CONTROL_LABELS: Record<string, NikonPictureControlLabel> = {
|
||||
'auto': { small: 'Auto', medium: 'Auto', large: 'Auto' },
|
||||
'standard': { small: 'Standard', medium: 'Standard', large: 'Standard' },
|
||||
'neutral': { small: 'Neutral', medium: 'Neutral', large: 'Neutral' },
|
||||
'vivid': { small: 'Vivid', medium: 'Vivid', large: 'Vivid' },
|
||||
'monochrome': { small: 'Monochrome', medium: 'Monochrome', large: 'Monochrome' },
|
||||
'portrait': { small: 'Portrait', medium: 'Portrait', large: 'Portrait' },
|
||||
'landscape': { small: 'Landscape', medium: 'Landscape', large: 'Landscape' },
|
||||
'flat': { small: 'Flat', medium: 'Flat', large: 'Flat' },
|
||||
'rich-tone-portrait': { small: 'Rich Tone Portrait', medium: 'Rich Tone Portrait', large: 'Rich Tone Portrait' },
|
||||
'deep-tone-monochrome': { small: 'Deep Tone Mono', medium: 'Deep Tone Mono', large: 'Deep Tone Monochrome' },
|
||||
'flat-monochrome': { small: 'Flat Mono', medium: 'Flat Mono', large: 'Flat Monochrome' },
|
||||
'dream': { small: 'Dream', medium: 'Dream', large: 'Dream' },
|
||||
'morning': { small: 'Morning', medium: 'Morning', large: 'Morning' },
|
||||
'pop': { small: 'Pop', medium: 'Pop', large: 'Pop' },
|
||||
'sunday': { small: 'Sunday', medium: 'Sunday', large: 'Sunday' },
|
||||
'somber': { small: 'Somber', medium: 'Somber', large: 'Somber' },
|
||||
'dramatic': { small: 'Dramatic', medium: 'Dramatic', large: 'Dramatic' },
|
||||
'silence': { small: 'Silence', medium: 'Silence', large: 'Silence' },
|
||||
'bleached': { small: 'Bleached', medium: 'Bleached', large: 'Bleached' },
|
||||
'melancholic': { small: 'Melancholic', medium: 'Melancholic', large: 'Melancholic' },
|
||||
'pure': { small: 'Pure', medium: 'Pure', large: 'Pure' },
|
||||
'denim': { small: 'Denim', medium: 'Denim', large: 'Denim' },
|
||||
'toy': { small: 'Toy', medium: 'Toy', large: 'Toy' },
|
||||
'sepia': { small: 'Sepia', medium: 'Sepia', large: 'Sepia' },
|
||||
'blue': { small: 'Blue', medium: 'Blue', large: 'Blue' },
|
||||
'red': { small: 'Red', medium: 'Red', large: 'Red' },
|
||||
'pink': { small: 'Pink', medium: 'Pink', large: 'Pink' },
|
||||
'charcoal': { small: 'Charcoal', medium: 'Charcoal', large: 'Charcoal' },
|
||||
'graphite': { small: 'Graphite', medium: 'Graphite', large: 'Graphite' },
|
||||
'binary': { small: 'Binary', medium: 'Binary', large: 'Binary' },
|
||||
'carbon': { small: 'Carbon', medium: 'Carbon', large: 'Carbon' },
|
||||
};
|
||||
const NIKON_PICTURE_CONTROLS = [
|
||||
'auto',
|
||||
'standard',
|
||||
'neutral',
|
||||
'vivid',
|
||||
'monochrome',
|
||||
'portrait',
|
||||
'landscape',
|
||||
'flat',
|
||||
'rich-tone-portrait',
|
||||
'deep-tone-monochrome',
|
||||
'flat-monochrome',
|
||||
'dream',
|
||||
'morning',
|
||||
'pop',
|
||||
'sunday',
|
||||
'somber',
|
||||
'dramatic',
|
||||
'silence',
|
||||
'bleached',
|
||||
'melancholic',
|
||||
'pure',
|
||||
'denim',
|
||||
'toy',
|
||||
'sepia',
|
||||
'blue',
|
||||
'red',
|
||||
'pink',
|
||||
'charcoal',
|
||||
'graphite',
|
||||
'binary',
|
||||
'carbon',
|
||||
] as const;
|
||||
|
||||
const NIKON_STRING_TO_MODE: Record<string, string> = {
|
||||
'AUTO': 'auto',
|
||||
'STANDARD': 'standard',
|
||||
'NEUTRAL': 'neutral',
|
||||
'VIVID': 'vivid',
|
||||
'MONOCHROME': 'monochrome',
|
||||
'PORTRAIT': 'portrait',
|
||||
'LANDSCAPE': 'landscape',
|
||||
'FLAT': 'flat',
|
||||
'RICH TONE PORTRAIT': 'rich-tone-portrait',
|
||||
'DEEP TONE MONOCHROME': 'deep-tone-monochrome',
|
||||
'FLAT MONOCHROME': 'flat-monochrome',
|
||||
'DREAM': 'dream',
|
||||
'MORNING': 'morning',
|
||||
'POP': 'pop',
|
||||
'SUNDAY': 'sunday',
|
||||
'SOMBER': 'somber',
|
||||
'DRAMATIC': 'dramatic',
|
||||
'SILENCE': 'silence',
|
||||
'BLEACHED': 'bleached',
|
||||
'MELANCHOLIC': 'melancholic',
|
||||
'PURE': 'pure',
|
||||
'DENIM': 'denim',
|
||||
'TOY': 'toy',
|
||||
'SEPIA': 'sepia',
|
||||
'BLUE': 'blue',
|
||||
'RED': 'red',
|
||||
'PINK': 'pink',
|
||||
'CHARCOAL': 'charcoal',
|
||||
'GRAPHITE': 'graphite',
|
||||
'BINARY': 'binary',
|
||||
'CARBON': 'carbon',
|
||||
};
|
||||
export type NikonPictureControl = typeof NIKON_PICTURE_CONTROLS[number];
|
||||
|
||||
export const isStringNikonPictureControl = (film?: string): film is NikonPictureControl =>
|
||||
const NIKON_PICTURE_CONTROL_LABELS =
|
||||
NIKON_PICTURE_CONTROLS.reduce((labels, control) => {
|
||||
labels[control] = {
|
||||
small: deparameterize(control),
|
||||
medium: deparameterize(control),
|
||||
large: deparameterize(control),
|
||||
};
|
||||
return labels;
|
||||
}, {} as Record<string, NikonPictureControlLabel>);
|
||||
|
||||
export const isStringNikonPictureControl = (
|
||||
film?: string,
|
||||
): boolean =>
|
||||
film !== undefined &&
|
||||
Object.keys(NIKON_PICTURE_CONTROL_LABELS).includes(film);
|
||||
NIKON_PICTURE_CONTROLS.includes(film as any);
|
||||
|
||||
export const labelForNikonPictureControl = (film: NikonPictureControl): NikonPictureControlLabel =>
|
||||
export const labelForNikonPictureControl = (
|
||||
film: NikonPictureControl | string,
|
||||
): NikonPictureControlLabel =>
|
||||
NIKON_PICTURE_CONTROL_LABELS[film] ?? {
|
||||
small: film,
|
||||
medium: film,
|
||||
large: film,
|
||||
};
|
||||
|
||||
export const getNikonPictureControlFromMakerNote = (
|
||||
bytes: Buffer,
|
||||
): NikonPictureControl | undefined => {
|
||||
let pictureControl: string | undefined;
|
||||
export const getNikonPictureControlFromMakerNote = (bytes: Buffer)=> {
|
||||
let pictureControl: NikonPictureControl | undefined;
|
||||
|
||||
parseNikonMakerNote(
|
||||
bytes,
|
||||
@ -103,22 +82,13 @@ export const getNikonPictureControlFromMakerNote = (
|
||||
if (value.length >= 28) {
|
||||
const name = value.toString('ascii', 8, 28);
|
||||
// Remove null bytes and trim
|
||||
pictureControl = name.replace(/\0/g, '').trim();
|
||||
pictureControl = name
|
||||
.replace(/\0/g, '')
|
||||
.trim() as NikonPictureControl;
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (pictureControl) {
|
||||
if (NIKON_STRING_TO_MODE[pictureControl]) {
|
||||
return NIKON_STRING_TO_MODE[pictureControl];
|
||||
}
|
||||
const upper = pictureControl.toUpperCase();
|
||||
if (NIKON_STRING_TO_MODE[upper]) {
|
||||
return NIKON_STRING_TO_MODE[upper];
|
||||
}
|
||||
return pictureControl;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user