71 lines
2.9 KiB
Dart
71 lines
2.9 KiB
Dart
import 'dart:typed_data';
|
|
|
|
import 'package:image/image.dart' as img;
|
|
|
|
/// X-plore 맞추기 (관리자 지시 10/20): 보기 화면의 사진 정보 (Exif) - 찍은 날 · 기기 · 크기 · 위치.
|
|
/// image 패키지 (MIT - 앱이 이미 쓰는 것, 라이선스 화면에 있음) 로 읽는다. 새 의존 없음. 위치는 보기 화면이 누를 때만 보이고,
|
|
/// 지도 · 인터넷으로 보내지 않는다 (이 파일은 숫자만 읽는다).
|
|
class PhotoInfo {
|
|
/// 그림 크기 (픽셀, 모르면 null)
|
|
final int? width, height;
|
|
|
|
/// 찍은 날 ("2024-05-01 12:30:00", Exif 의 DateTimeOriginal · 없으면 DateTime)
|
|
final String? taken;
|
|
|
|
/// 기기 ("Canon EOS 5D" - Make · Model)
|
|
final String? device;
|
|
|
|
/// 위치 (도, 남 · 서는 음수)
|
|
final double? latitude, longitude;
|
|
|
|
const PhotoInfo({this.width, this.height, this.taken, this.device, this.latitude, this.longitude});
|
|
|
|
bool get hasLocation => latitude != null && longitude != null;
|
|
|
|
/// Exif 에서 읽은 것이 있는지 (크기는 Exif 가 없어도 읽음)
|
|
bool get hasExif => taken != null || device != null || hasLocation;
|
|
|
|
/// 위치 글 ("37.500000, 127.000000")
|
|
String get locationText => hasLocation ? '${latitude!.toStringAsFixed(6)}, ${longitude!.toStringAsFixed(6)}' : '';
|
|
}
|
|
|
|
/// 그림 파일의 바이트에서 사진 정보를 읽는다 (Exif 는 JPEG 만 - 다른 형식은 크기만). 읽지 못하는 것은 null
|
|
PhotoInfo readPhotoInfo(Uint8List bytes) {
|
|
int? w, h;
|
|
try {
|
|
final info = img.findDecoderForData(bytes)?.startDecode(bytes);
|
|
if (info != null && info.width > 0 && info.height > 0) {
|
|
w = info.width;
|
|
h = info.height;
|
|
}
|
|
} catch (_) {}
|
|
String? taken, device;
|
|
double? lat, lon;
|
|
try {
|
|
final e = img.decodeJpgExif(bytes);
|
|
if (e != null) {
|
|
String? s(img.IfdValue? v) {
|
|
final t = v?.toString().replaceAll('\u0000', '').trim();
|
|
return t == null || t.isEmpty ? null : t;
|
|
}
|
|
|
|
final when = s(e.exifIfd['DateTimeOriginal']) ?? s(e.imageIfd['DateTime']);
|
|
// "2024:05:01 12:30:00" → "2024-05-01 12:30:00"
|
|
taken = when?.replaceFirstMapped(RegExp(r'^(\d{4}):(\d{2}):(\d{2})'), (m) => '${m[1]}-${m[2]}-${m[3]}');
|
|
final make = s(e.imageIfd['Make']), model = s(e.imageIfd['Model']);
|
|
device = model == null ? make : (make == null || model.toLowerCase().startsWith(make.toLowerCase()) ? model : '$make $model');
|
|
// GPS: 1 위도 N/S · 2 위도 (도 · 분 · 초) · 3 경도 E/W · 4 경도
|
|
lat = _coord(e.gpsIfd[2], s(e.gpsIfd[1]));
|
|
lon = _coord(e.gpsIfd[4], s(e.gpsIfd[3]));
|
|
}
|
|
} catch (_) {}
|
|
return PhotoInfo(width: w, height: h, taken: taken, device: device, latitude: lat, longitude: lon);
|
|
}
|
|
|
|
double? _coord(img.IfdValue? v, String? ref) {
|
|
if (v == null || v.length < 3) return null;
|
|
final d = v.toDouble(0) + v.toDouble(1) / 60 + v.toDouble(2) / 3600;
|
|
if (d.isNaN || d.isInfinite) return null;
|
|
return ref == 'S' || ref == 'W' ? -d : d;
|
|
}
|