JJ_MKVMaker/lib/core/photo_info.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;
}