JJ_MKVMaker/lib/core/photo_date.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]);
}