diff --git a/.vscode/settings.json b/.vscode/settings.json index 4840a4de..5ef0cd4d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -27,7 +27,10 @@ "headlessui", "hgetall", "hset", + "IIIA", + "ILCE", "ILIKE", + "ILME", "jpgs", "Lightbox", "Makernote", diff --git a/__tests__/camera.test.ts b/__tests__/camera.test.ts index 03b8181b..6bdacae6 100644 --- a/__tests__/camera.test.ts +++ b/__tests__/camera.test.ts @@ -1,4 +1,5 @@ import { Camera, formatCameraText } from '@/camera'; +import { MAKE_SONY } from '@/platforms/sony'; const APPLE : Camera = { make: 'Apple', model: 'iPhone 11 Pro' }; const APPLE_01 : Camera = { make: 'Apple', model: 'iPhone 11' }; @@ -11,6 +12,46 @@ const RICOH : Camera = { model: 'RICOH GR III', }; +export const SONY_CAMERAS = { + 'SONY ILCE-1M2': 'Sony A1 II', + 'SONY ILCE-1': 'Sony A1', + 'SONY ILCE-9M3': 'Sony A9 III', + 'SONY ILCE-9M2': 'Sony A9 II', + 'SONY ILCE-9': 'Sony A9', + 'SONY ILCE-7RM5': 'Sony A7R V', + 'SONY ILCE-7RM4': 'Sony A7R IV', + 'SONY ILCE-7RM4A': 'Sony A7R IVA', + 'SONY ILCE-7RM3': 'Sony A7R III', + 'SONY ILCE-7RM3A': 'Sony A7R IIIA', + 'SONY ILCE-7RM2': 'Sony A7R II', + 'SONY ILCE-7R': 'Sony A7R', + 'SONY ILCE-7SM3': 'Sony A7S III', + 'SONY ILCE-7SM2': 'Sony A7S II', + 'SONY ILCE-7S': 'Sony A7S', + 'SONY ILCE-7M4': 'Sony A7 IV', + 'SONY ILCE-7M3': 'Sony A7 III', + 'SONY ILCE-7M2': 'Sony A7 II', + 'SONY ILCE-7': 'Sony A7', + 'SONY ILCE-7CR': 'Sony A7CR', + 'SONY ILCE-7CM2': 'Sony A7C II', + 'SONY ILCE-7C': 'Sony A7C', + 'SONY ILCE-6700': 'Sony A6700', + 'SONY ILCE-6600': 'Sony A6600', + 'SONY ILCE-6500': 'Sony A6500', + 'SONY ILCE-6400': 'Sony A6400', + 'SONY ILCE-6300': 'Sony A6300', + 'SONY ILCE-6100': 'Sony A6100', + 'SONY ILCE-6000': 'Sony A6000', + 'SONY ILCE-5100': 'Sony A5100', + 'SONY ILCE-5000': 'Sony A5000', + 'SONY ILCE-3500': 'Sony A3500', + 'SONY ILCE-3000': 'Sony A3000', + 'SONY ILME-FX3': 'Sony FX3', + 'SONY ILME-FX6V': 'Sony FX6', + 'SONY ILME-FX6VK': 'Sony FX6', + 'SONY ILCE-QX1': 'Sony AQX1', +}; + describe('Camera', () => { it('labels full text correctly', () => { expect(formatCameraText(APPLE)).toBe('iPhone 11 Pro'); @@ -33,5 +74,11 @@ describe('Camera', () => { expect(formatCameraText(RICOH, 'short')).toBe('GR III'); expect(formatCameraText(NIKON, 'short')).toBe('D7000'); }); + it('formats Sony cameras', () => { + Object.entries(SONY_CAMERAS).forEach(([model, expected]) => { + const camera = { make: MAKE_SONY, model }; + expect(formatCameraText(camera, 'medium')) + .toBe(expected.toLocaleUpperCase()); + }); + }); }); - diff --git a/__tests__/number.test.ts b/__tests__/number.test.ts index 1d19a108..fa3f2e4d 100644 --- a/__tests__/number.test.ts +++ b/__tests__/number.test.ts @@ -1,6 +1,22 @@ -import { roundToString, roundToNumber } from '@/utility/number'; +import { + convertNumberToRomanNumeral, + roundToString, + roundToNumber, +} from '@/utility/number'; describe('number', () => { + it('converts to roman numerals', () => { + expect(convertNumberToRomanNumeral(1)).toBe('I'); + expect(convertNumberToRomanNumeral(2)).toBe('II'); + expect(convertNumberToRomanNumeral(3)).toBe('III'); + expect(convertNumberToRomanNumeral(4)).toBe('IV'); + expect(convertNumberToRomanNumeral(5)).toBe('V'); + expect(convertNumberToRomanNumeral(6)).toBe('VI'); + expect(convertNumberToRomanNumeral(7)).toBe('VII'); + expect(convertNumberToRomanNumeral(8)).toBe('VIII'); + expect(convertNumberToRomanNumeral(9)).toBe('IX'); + expect(convertNumberToRomanNumeral(10)).toBe('X'); + }); describe('rounds to a', () => { it('string', () => { expect(roundToString(1.2345, 1)).toBe('1.2'); diff --git a/src/camera/index.ts b/src/camera/index.ts index 0bc31f41..403f6615 100644 --- a/src/camera/index.ts +++ b/src/camera/index.ts @@ -1,5 +1,6 @@ import type { Photo } from '@/photo'; import { isCameraMakeApple } from '@/platforms/apple'; +import { formatSonyModel, isMakeSony } from '@/platforms/sony'; import { parameterize } from '@/utility/string'; const CAMERA_PLACEHOLDER: Camera = { make: 'Camera', model: 'Model' }; @@ -58,7 +59,7 @@ export const cameraFromPhoto = ( : fallback ?? CAMERA_PLACEHOLDER; export const formatCameraText = ( - { make, model: modelRaw }: Camera, + { make, model: _model }: Camera, length: 'long' | // Unmodified make and model 'medium' | // Make and model, with modifiers removed @@ -67,11 +68,11 @@ export const formatCameraText = ( ) => { // Capture simple make without modifiers like 'Corporation' or 'Company' const makeSimple = make.match(/^(\S+)/)?.[1]; + let model = isMakeSony(make) ? formatSonyModel(_model) : _model; const doesModelStartWithMake = ( makeSimple && - modelRaw.toLocaleLowerCase().startsWith(makeSimple.toLocaleLowerCase()) + model.toLocaleLowerCase().startsWith(makeSimple.toLocaleLowerCase()) ); - let model = modelRaw; switch (length) { case 'long': return `${make} ${model}`; diff --git a/src/platforms/sony.ts b/src/platforms/sony.ts new file mode 100644 index 00000000..251bc5b8 --- /dev/null +++ b/src/platforms/sony.ts @@ -0,0 +1,28 @@ +import { convertNumberToRomanNumeral } from '@/utility/number'; + +export const MAKE_SONY = 'SONY'; + +export const isMakeSony = (make: string) => + make === MAKE_SONY; + +export const formatSonyModel = (model: string) => { + const [ + _, + type, + series, + letter, + version, + modifier, + // eslint-disable-next-line max-len + ] = /^SONY (ILCE|ILME)-([0-9]*)([a-ln-z]*)M*([0-9]*)([a-z]*)/gi.exec(model) ?? []; + const versionNumber = parseInt(version || '0'); + const versionRomanNumeral = versionNumber > 1 && versionNumber < 10 + ? ` ${convertNumberToRomanNumeral(versionNumber)}` + : ''; + if (type === 'ILCE' || type === 'ILME') { + return type === 'ILCE' + ? `A${series}${letter}${versionRomanNumeral || version}${modifier}` + : `FX${series}${version}`; + } + return model; +}; diff --git a/src/utility/number.ts b/src/utility/number.ts index 2bb5deba..f097c6f5 100644 --- a/src/utility/number.ts +++ b/src/utility/number.ts @@ -91,3 +91,30 @@ export const formatBytesToMB = ( precision = 1, ) => `${(bytes / byteSize / byteSize).toFixed(precision)}MB`; + +export const convertNumberToRomanNumeral = (number: number) => { + const romanNumerals = [ + { value: 1000, numeral: 'M' }, + { value: 900, numeral: 'CM' }, + { value: 500, numeral: 'D' }, + { value: 400, numeral: 'CD' }, + { value: 100, numeral: 'C' }, + { value: 90, numeral: 'XC' }, + { value: 50, numeral: 'L' }, + { value: 40, numeral: 'XL' }, + { value: 10, numeral: 'X' }, + { value: 9, numeral: 'IX' }, + { value: 5, numeral: 'V' }, + { value: 4, numeral: 'IV' }, + { value: 1, numeral: 'I' }, + ]; + + let result = ''; + for (const romanNumeral of romanNumerals) { + while (number >= romanNumeral.value) { + result += romanNumeral.numeral; + number -= romanNumeral.value; + } + } + return result; +};