Files
Immich/mobile/lib/utils/camera.utils.dart
T
Brent SaltzmanandCodex 3c383e46d1 fix: avoid duplicate manufacturer in camera name (#32657)
* fix(web,mobile): avoid duplicate manufacturer in camera name

Some cameras (e.g. Canon) already include the manufacturer in the EXIF
model, which made the details view show "Canon Canon EOS R6m2". Only
prepend the make when the model does not already start with it.

* refactor(web,mobile): address review feedback on camera name helper

- Use toLowerCase instead of toLocaleLowerCase for the comparison
- Hoist the whitespace regex to a static in the Dart helper
- Match any word of the make, so "EASTMAN KODAK COMPANY" with
  "KODAK EASYSHARE C875" is deduplicated too
- Compute the camera name once in DetailPanel

---------

Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
(cherry picked from commit cb2ea1e308d8a85f2f340f50b646f161d3528670)
2026-10-29 20:41:02 +03:00

39 lines
1.4 KiB
Dart

final _whitespace = RegExp(r'\s+');
final _wordChar = RegExp(r'\w');
/// Builds the display name of a camera from its EXIF make and model.
///
/// Many cameras (e.g. Canon) already include the manufacturer in the model
/// (`Canon EOS R6m2`), so prefixing the make again would produce
/// "Canon Canon EOS R6m2". The make is only prepended when the model does not
/// already start with it. Makes made up of several words are matched word by
/// word, since the model usually repeats only part of them:
/// `NIKON CORPORATION` / `NIKON D850` and
/// `EASTMAN KODAK COMPANY` / `KODAK EASYSHARE C875`.
///
/// Returns `null` when neither a make nor a model is available.
String? getCameraName(String? make, String? model) {
final trimmedMake = make?.trim() ?? '';
final trimmedModel = model?.trim() ?? '';
if (trimmedMake.isEmpty && trimmedModel.isEmpty) {
return null;
}
if (trimmedMake.isEmpty) {
return trimmedModel;
}
if (trimmedModel.isEmpty) {
return trimmedMake;
}
final lowerModel = trimmedModel.toLowerCase();
final modelIncludesMake = trimmedMake.toLowerCase().split(_whitespace).any((word) {
final charAfterWord = lowerModel.length > word.length ? lowerModel[word.length] : '';
return lowerModel.startsWith(word) && !_wordChar.hasMatch(charAfterWord);
});
return modelIncludesMake ? trimmedModel : '$trimmedMake $trimmedModel';
}