// Normalize raw EXIF make+model strings for display. // // Examples: // "RICOH IMAGING COMPANY, LTD. GR II" → "Ricoh GR II" // "RICOH IMAGING COMPANY, LTD. RICOH GR III" → "Ricoh GR III" // "NIKON CORPORATION NIKON D600" → "Nikon D600" // "OLYMPUS IMAGING CORP. TG-3" → "Olympus TG-3" // "FUJI PHOTO FILM CO., LTD. SP-3000" → "Fuji Photo Film SP-3000" // "LEICA CAMERA AG LEICA Q (Typ 116)" → "Leica Q (Typ 116)" // "KONICA MINOLTA ALPHA SWEET DIGITAL" → "Konica Minolta Alpha Sweet Digital" // "samsung Galaxy S24+" → "Samsung Galaxy S24+" // "Apple iPhone 14 Pro" → "Apple iPhone 14 Pro" (no change) // "SONY ILCE-7M3" → "Sony ILCE-7M3" (model code preserved) // "FUJIFILM X-T30 II" → "Fujifilm X-T30 II" (roman numeral preserved) export function cleanCameraName(raw: string): string { let s = raw .replace(/\bIMAGING COMPANY,?\s*LTD\.?/gi, "") .replace(/\bIMAGING CORP\.?/gi, "") .replace(/\bPHOTO FILM CO\.,?\s*LTD\.?/gi, "PHOTO FILM") .replace(/\bCAMERA AG\b/gi, "") .replace(/\bCORPORATION\b/gi, "") .replace(/\bCO\.,?\s*LTD\.?/gi, "") .replace(/\bGmbH\b/g, "") .replace(/\s+/g, " ") .trim(); // Dedup adjacent identical tokens (case-insensitive) — e.g. "NIKON NIKON D300". const tokens = s.split(" "); const deduped: string[] = []; for (const t of tokens) { const prev = deduped[deduped.length - 1]; if (!prev || prev.toUpperCase() !== t.toUpperCase()) deduped.push(t); } // Title-case brand-like tokens: // - ALL-CAPS letters ≥ 4 chars (brands: NIKON, FUJIFILM, KONICA, MINOLTA) // - all-lowercase letters ≥ 2 chars (samsung, motorola) // Skip tokens with digits, hyphens, roman numerals, or mixed case. const titled = deduped.map((w) => { if (/^[A-Z]{4,}$/.test(w)) return w[0] + w.slice(1).toLowerCase(); if (/^[a-z]{2,}$/.test(w)) return w[0].toUpperCase() + w.slice(1); return w; }); return titled.join(" ").trim(); }