export const EXIF_LABELS: Record = { Make: 'Производитель', Model: 'Модель', LensMake: 'Производитель объектива', LensModel: 'Объектив', ExposureTime: 'Выдержка', ShutterSpeedValue: 'Выдержка (APEX)', FNumber: 'Диафрагма', ApertureValue: 'Диафрагма (APEX)', MaxApertureValue: 'Макс. диафрагма', FocalLength: 'Фокусное расстояние', FocalLengthIn35mmFilm: 'Фокусное (35 мм)', ISOSpeedRatings: 'ISO', PhotographicSensitivity: 'ISO', ExposureProgram: 'Программа съёмки', ExposureMode: 'Режим экспозиции', ExposureBiasValue: 'Экспокоррекция', BrightnessValue: 'Яркость (APEX)', MeteringMode: 'Режим замера', LightSource: 'Источник света', Flash: 'Вспышка', WhiteBalance: 'Баланс белого', Orientation: 'Ориентация', Software: 'ПО', ColorSpace: 'Цветовое пространство', SceneCaptureType: 'Тип сцены', Contrast: 'Контраст', Saturation: 'Насыщенность', Sharpness: 'Резкость', CustomRendered: 'Обработка', GainControl: 'Усиление', SensingMethod: 'Метод съёмки', SubjectDistanceRange: 'Дистанция', DigitalZoomRatio: 'Цифровой зум', CompressedBitsPerPixel: 'Сжатие (бит/пиксель)', ImageDescription: 'Описание' }; export const EXIF_SKIP = new Set([ 'GPSLatitude', 'GPSLongitude', 'GPSLatitudeRef', 'GPSLongitudeRef', 'GPSAltitude', 'GPSAltitudeRef', 'GPSVersionID', 'GPSDateStamp', 'GPSTimeStamp', 'GPSProcessingMethod', 'GPSAreaInformation', 'GPSImgDirection', 'GPSImgDirectionRef', 'GPSDestBearing', 'GPSDestBearingRef', 'GPSSpeed', 'GPSSpeedRef', 'GPSTrack', 'GPSTrackRef', 'GPSMapDatum', 'GPSDifferential', 'DateTime', 'DateTimeDigitized', 'DateTimeOriginal', 'SubSecTime', 'SubSecTimeDigitized', 'SubSecTimeOriginal', 'ExifVersion', 'FlashpixVersion', 'InteroperabilityVersion', 'InteroperabilityIndex', 'ExifTag', 'GPSTag', 'InteroperabilityTag', 'ExifIFDPointer', 'GPSInfoIFDPointer', 'ComponentsConfiguration', 'MakerNote', 'UserComment', 'FileSource', 'SceneType', 'PhotometricInterpretation', 'YCbCrPositioning', 'ResolutionUnit', 'BitsPerSample', 'XResolution', 'YResolution', 'ImageWidth', 'ImageLength', 'PixelXDimension', 'PixelYDimension' ]); export const EXIF_ENUMS: Record> = { Orientation: { 1: 'Горизонтальная', 2: 'Зеркальная (гориз.)', 3: 'Повёрнута на 180°', 4: 'Зеркальная (верт.)', 5: 'Зеркальная + 90° CW', 6: 'Повёрнута на 90° CW', 7: 'Зеркальная + 90° CCW', 8: 'Повёрнута на 90° CCW' }, ExposureProgram: { 0: 'Не задано', 1: 'Ручная', 2: 'Программная', 3: 'Приоритет диафрагмы', 4: 'Приоритет выдержки', 5: 'Творческая', 6: 'Спортивная', 7: 'Портрет', 8: 'Пейзаж' }, ExposureMode: { 0: 'Авто', 1: 'Ручной', 2: 'Авто-брекетинг' }, MeteringMode: { 0: 'Неизвестно', 1: 'Средневзвешенный', 2: 'Центрально-взвешенный', 3: 'Точечный', 4: 'Многозонный', 5: 'Матричный', 6: 'Частичный', 255: 'Другое' }, LightSource: { 0: 'Неизвестно', 1: 'Дневной свет', 2: 'Люминесцентный', 3: 'Лампа накаливания', 4: 'Вспышка', 9: 'Хорошая погода', 10: 'Облачно', 11: 'Тень' }, Flash: { 0: 'Не сработала', 1: 'Сработала', 9: 'Сработала (принуд.)', 16: 'Отключена', 24: 'Не сработала (авто)', 25: 'Сработала (авто)' }, WhiteBalance: { 0: 'Авто', 1: 'Ручной' }, SceneCaptureType: { 0: 'Стандартный', 1: 'Пейзаж', 2: 'Портрет', 3: 'Ночной' }, Contrast: { 0: 'Обычный', 1: 'Мягкий', 2: 'Жёсткий' }, Saturation: { 0: 'Обычная', 1: 'Низкая', 2: 'Высокая' }, Sharpness: { 0: 'Обычная', 1: 'Мягкая', 2: 'Жёсткая' }, CustomRendered: { 0: 'Обычная', 1: 'Особая' }, GainControl: { 0: 'Нет', 1: 'Малое усиление', 2: 'Большое усиление', 3: 'Низкое усиление', 4: 'Высокое усиление' }, SensingMethod: { 1: 'Не задано', 2: 'Однокристальный', 3: 'Двухчиповый', 4: 'Трёхчиповый', 5: 'Цветной последовательный', 7: 'Трёхлинейный', 8: 'Цветной последовательный' }, SubjectDistanceRange: { 0: 'Неизвестно', 1: 'Макро', 2: 'Близко', 3: 'Далеко' }, ColorSpace: { 1: 'sRGB', 65535: 'Некалиброванное' } }; export function formatExifValue(key: string, value: unknown): string { if (value == null) return ''; const n = Number(value); if (key === 'ExposureTime' && !Number.isNaN(n) && n > 0) { return n >= 1 ? `${n} с` : `1/${Math.round(1 / n)}`; } if ( (key === 'FNumber' || key === 'ApertureValue' || key === 'MaxApertureValue') && !Number.isNaN(n) ) { return `f/${n}`; } if ((key === 'FocalLength' || key === 'FocalLengthIn35mmFilm') && !Number.isNaN(n)) { return `${n} мм`; } if (key === 'ExposureBiasValue' && !Number.isNaN(n)) { return `${n > 0 ? '+' : ''}${n} EV`; } const enums = EXIF_ENUMS[key]; if (enums && !Number.isNaN(n) && enums[n] != null) return enums[n]; return String(value); } export interface ExifRow { key: string; label: string; value: string; } export const EXIF_BASE: string[] = [ 'Make', 'Model', 'LensModel', 'ExposureTime', 'FNumber', 'ISOSpeedRatings', 'FocalLength', 'FocalLengthIn35mmFilm' ]; export function buildExifRows( exif: Record, labels: Record = {} ): ExifRow[] { return Object.entries(exif) .filter(([k, v]) => !EXIF_SKIP.has(k) && v != null && String(v) !== '') .map(([k, v]) => ({ key: k, label: labels[k] ?? EXIF_LABELS[k] ?? k, value: formatExifValue(k, v) })) .sort((a, b) => a.label.localeCompare(b.label, 'ru')); } export const EXIF_CATALOG: string[] = Object.keys(EXIF_LABELS);