44 lines
1.5 KiB
Dart
44 lines
1.5 KiB
Dart
import 'dart:io';
|
|
import 'dart:typed_data';
|
|
|
|
import 'package:image/image.dart' show decodeJpgExif;
|
|
|
|
/// X-plore 비교 i: 일괄 이름 바꾸기의 날짜 - 사진은 찍은 때 (JPEG Exif DateTimeOriginal), 없으면 null.
|
|
/// 파일 앞부분만 읽는다 (Exif 는 맨 앞 APP1, 64KB 를 넘지 않음).
|
|
Future<DateTime?> photoTakenAt(String path) async {
|
|
final ext = path.toLowerCase();
|
|
if (!(ext.endsWith('.jpg') || ext.endsWith('.jpeg'))) return null;
|
|
RandomAccessFile? f;
|
|
try {
|
|
f = await File(path).open();
|
|
final head = await f.read(256 * 1024);
|
|
return parseExifDate(_exifDate(head));
|
|
} catch (_) {
|
|
return null;
|
|
} finally {
|
|
await f?.close();
|
|
}
|
|
}
|
|
|
|
String? _exifDate(Uint8List head) {
|
|
try {
|
|
final exif = decodeJpgExif(head);
|
|
if (exif == null) return null;
|
|
// 찍은 때 → 디지털로 만든 때 → 바뀐 때 (ifd0 의 DateTime)
|
|
for (final v in [exif.exifIfd[0x9003], exif.exifIfd[0x9004], exif.imageIfd[0x0132]]) {
|
|
final s = v?.toString().trim();
|
|
if (s != null && s.isNotEmpty) return s;
|
|
}
|
|
} catch (_) {}
|
|
return null;
|
|
}
|
|
|
|
/// "2024:05:01 12:34:56" → DateTime (틀리거나 0 이면 null)
|
|
DateTime? parseExifDate(String? s) {
|
|
final m = RegExp(r'^(\d{4})[:\-](\d{2})[:\-](\d{2})[ T](\d{2}):(\d{2}):(\d{2})').firstMatch(s ?? '');
|
|
if (m == null) return null;
|
|
final n = [for (var i = 1; i <= 6; i++) int.parse(m[i]!)];
|
|
if (n[0] < 1900 || n[1] < 1 || n[1] > 12 || n[2] < 1 || n[2] > 31) return null;
|
|
return DateTime(n[0], n[1], n[2], n[3], n[4], n[5]);
|
|
}
|