1import 'dart:io';
2
3import 'package:exif/exif.dart';
4
5import '../app_logger.dart';
6
7const List<String> exifTags = [
8 'DateTimeOriginal',
9 'ExposureTime',
10 'FNumber',
11 'Flash',
12 'FocalLengthIn35mmFilm',
13 'ISOSpeedRatings',
14 'LensMake',
15 'LensModel',
16 'Make',
17 'Model',
18];
19
20const int scaleFactor = 1000000;
21
22Future<Map<String, dynamic>?> parseAndNormalizeExif({required File file}) async {
23 try {
24 final Map<String, IfdTag> exifData = await readExifFromBytes(await file.readAsBytes());
25 appLogger.i('Parsed EXIF data: $exifData');
26
27 if (exifData.isEmpty) return null;
28 final Map<String, dynamic> normalized = {};
29
30 for (final entry in exifData.entries) {
31 final fullTag = entry.key;
32 String tag;
33 if (fullTag.contains(' ')) {
34 tag = fullTag.split(' ').last.trim();
35 } else {
36 tag = fullTag.trim();
37 }
38 final value = entry.value.printable;
39 final camelKey = tag.isNotEmpty ? tag[0].toLowerCase() + tag.substring(1) : tag;
40 switch (tag) {
41 case 'DateTimeOriginal':
42 normalized[camelKey] = _exifDateToIso(value);
43 break;
44 case 'ExposureTime':
45 normalized[camelKey] = _parseScaledInt(value);
46 break;
47 case 'FNumber':
48 normalized[camelKey] = _parseScaledInt(value);
49 break;
50 case 'Flash':
51 normalized[camelKey] = value;
52 break;
53 case 'FocalLengthIn35mmFilm':
54 normalized[camelKey] = _parseScaledInt(value);
55 break;
56 case 'ISOSpeedRatings':
57 normalized[camelKey] = _parseInt(value);
58 break;
59 case 'LensMake':
60 normalized[camelKey] = value;
61 break;
62 case 'LensModel':
63 normalized[camelKey] = value;
64 break;
65 case 'Make':
66 normalized[camelKey] = value;
67 break;
68 case 'Model':
69 normalized[camelKey] = value;
70 break;
71 }
72 }
73
74 return normalized;
75 } catch (e) {
76 return null;
77 }
78}
79
80int? _parseScaledInt(String? value) {
81 if (value == null) return null;
82 // Handle fraction like "1/40"
83 if (value.contains('/')) {
84 final parts = value.split('/');
85 if (parts.length == 2) {
86 final numerator = double.tryParse(parts[0].trim());
87 final denominator = double.tryParse(parts[1].trim());
88 if (numerator != null && denominator != null && denominator != 0) {
89 return (numerator / denominator * scaleFactor).round();
90 }
91 }
92 }
93 final numVal = double.tryParse(value.replaceAll(RegExp(r'[^0-9.]'), ''));
94 if (numVal == null) return null;
95 return (numVal * scaleFactor).round();
96}
97
98int? _parseInt(String? value) {
99 if (value == null) return null;
100 final intVal = int.tryParse(value.replaceAll(RegExp(r'[^0-9]'), ''));
101 if (intVal == null) return null;
102 return intVal * scaleFactor;
103}
104
105String? _exifDateToIso(String? exifDate) {
106 if (exifDate == null) return null;
107 // EXIF format: yyyy:MM:dd HH:mm:ss
108 final match = RegExp(r'^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$').firstMatch(exifDate);
109 if (match == null) return null;
110 final formatted =
111 '${match.group(1)}-${match.group(2)}-${match.group(3)}T${match.group(4)}:${match.group(5)}:${match.group(6)}';
112 return formatted;
113}