mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 08:46:18 +01:00
Adapt to server side metadata
This commit is contained in:
parent
eaa8b2d907
commit
bc9bbe9455
8 changed files with 335 additions and 36 deletions
|
@ -17,6 +17,7 @@ import 'package:nc_photos/entity/tagged_file.dart';
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:np_api/np_api.dart' as api;
|
import 'package:np_api/np_api.dart' as api;
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_common/object_util.dart';
|
||||||
import 'package:np_string/np_string.dart';
|
import 'package:np_string/np_string.dart';
|
||||||
|
|
||||||
part 'entity_converter.g.dart';
|
part 'entity_converter.g.dart';
|
||||||
|
@ -49,9 +50,18 @@ class ApiFavoriteConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApiFileConverter {
|
class ApiFileConverter {
|
||||||
static File fromApi(api.File file) {
|
static Metadata? _metadataFromApi(api.File file) {
|
||||||
var metadata = file.customProperties?["com.nkming.nc_photos:metadata"]
|
if (file.metadataPhotosSize != null) {
|
||||||
?.run((obj) => Metadata.fromJson(
|
return Metadata.fromApi(
|
||||||
|
etag: file.etag,
|
||||||
|
ifd0: file.metadataPhotosIfd0,
|
||||||
|
exif: file.metadataPhotosExif,
|
||||||
|
gps: file.metadataPhotosGps,
|
||||||
|
size: file.metadataPhotosSize!,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return file.customProperties?["com.nkming.nc_photos:metadata"]
|
||||||
|
?.let((obj) => Metadata.fromJson(
|
||||||
jsonDecode(obj),
|
jsonDecode(obj),
|
||||||
upgraderV1: MetadataUpgraderV1(
|
upgraderV1: MetadataUpgraderV1(
|
||||||
fileContentType: file.contentType,
|
fileContentType: file.contentType,
|
||||||
|
@ -66,15 +76,10 @@ class ApiFileConverter {
|
||||||
logFilePath: file.href,
|
logFilePath: file.href,
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
if (file.metadataPhotosIfd0 != null) {
|
|
||||||
final ifd0_metadata = Metadata.fromPhotosIfd0(file.metadataPhotosIfd0!);
|
|
||||||
if (metadata == null) {
|
|
||||||
metadata = ifd0_metadata;
|
|
||||||
} else {
|
|
||||||
metadata = metadata.copyWith(exif: ifd0_metadata.exif);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static File fromApi(api.File file) {
|
||||||
return File(
|
return File(
|
||||||
path: _hrefToPath(file.href),
|
path: _hrefToPath(file.href),
|
||||||
contentLength: file.contentLength,
|
contentLength: file.contentLength,
|
||||||
|
@ -90,7 +95,7 @@ class ApiFileConverter {
|
||||||
trashbinFilename: file.trashbinFilename,
|
trashbinFilename: file.trashbinFilename,
|
||||||
trashbinOriginalLocation: file.trashbinOriginalLocation,
|
trashbinOriginalLocation: file.trashbinOriginalLocation,
|
||||||
trashbinDeletionTime: file.trashbinDeletionTime,
|
trashbinDeletionTime: file.trashbinDeletionTime,
|
||||||
metadata: metadata,
|
metadata: _metadataFromApi(file),
|
||||||
isArchived: file.customProperties?["com.nkming.nc_photos:is-archived"]
|
isArchived: file.customProperties?["com.nkming.nc_photos:is-archived"]
|
||||||
?.run((obj) => obj == "true"),
|
?.run((obj) => obj == "true"),
|
||||||
overrideDateTime: file
|
overrideDateTime: file
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:exifdart/exifdart.dart';
|
import 'package:exifdart/exifdart.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:nc_photos/entity/exif.dart';
|
import 'package:nc_photos/entity/exif.dart';
|
||||||
|
|
||||||
extension ExifExtension on Exif {
|
extension ExifExtension on Exif {
|
||||||
|
@ -12,7 +13,7 @@ extension ExifExtension on Exif {
|
||||||
// invalid value
|
// invalid value
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return _gpsDmsToDouble(gpsLatitude!) * (gpsLatitudeRef == "S" ? -1 : 1);
|
return gpsDmsToDouble(gpsLatitude!) * (gpsLatitudeRef == "S" ? -1 : 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,12 +27,23 @@ extension ExifExtension on Exif {
|
||||||
// invalid value
|
// invalid value
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return _gpsDmsToDouble(gpsLongitude!) * (gpsLongitudeRef == "W" ? -1 : 1);
|
return gpsDmsToDouble(gpsLongitude!) * (gpsLongitudeRef == "W" ? -1 : 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
double _gpsDmsToDouble(List<Rational> dms) {
|
List<Rational> gpsDoubleToDms(double src) {
|
||||||
|
var tmp = src.abs();
|
||||||
|
final d = tmp.floor();
|
||||||
|
tmp -= d;
|
||||||
|
final ss = (tmp * 3600 * 100).floor();
|
||||||
|
final s = ss % (60 * 100);
|
||||||
|
final m = (ss / (60 * 100)).floor();
|
||||||
|
return [Rational(d, 1), Rational(m, 1), Rational(s, 100)];
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
double gpsDmsToDouble(List<Rational> dms) {
|
||||||
double product = dms[0].toDouble();
|
double product = dms[0].toDouble();
|
||||||
if (dms.length > 1) {
|
if (dms.length > 1) {
|
||||||
product += dms[1].toDouble() / 60;
|
product += dms[1].toDouble() / 60;
|
||||||
|
@ -41,3 +53,14 @@ double _gpsDmsToDouble(List<Rational> dms) {
|
||||||
}
|
}
|
||||||
return product;
|
return product;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rational doubleToRational(double src) {
|
||||||
|
final s = src.abs();
|
||||||
|
if (s < 1000) {
|
||||||
|
return Rational((s * 100000).truncate(), 100000);
|
||||||
|
} else if (s < 100000) {
|
||||||
|
return Rational((s * 1000).truncate(), 1000);
|
||||||
|
} else {
|
||||||
|
return Rational(s.truncate(), 1);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,10 +5,12 @@ import 'package:equatable/equatable.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/entity/exif.dart';
|
import 'package:nc_photos/entity/exif.dart';
|
||||||
|
import 'package:nc_photos/entity/exif_util.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/json_util.dart' as json_util;
|
import 'package:nc_photos/json_util.dart' as json_util;
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_common/object_util.dart';
|
||||||
import 'package:np_common/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
import 'package:np_common/type.dart';
|
import 'package:np_common/type.dart';
|
||||||
import 'package:np_string/np_string.dart';
|
import 'package:np_string/np_string.dart';
|
||||||
|
@ -185,13 +187,40 @@ class Metadata with EquatableMixin {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Metadata fromPhotosIfd0(Map<String, String> metadataPhotosIfd0) {
|
static Metadata? fromApi({
|
||||||
|
required String? etag,
|
||||||
|
Map<String, String>? ifd0,
|
||||||
|
Map<String, String>? exif,
|
||||||
|
Map<String, String>? gps,
|
||||||
|
required Map<String, String> size,
|
||||||
|
}) {
|
||||||
|
final lat = gps?["latitude"]?.let(double.tryParse);
|
||||||
|
final lng = gps?["longitude"]?.let(double.tryParse);
|
||||||
|
final alt = gps?["altitude"]?.let(double.tryParse);
|
||||||
return Metadata(
|
return Metadata(
|
||||||
lastUpdated: null,
|
lastUpdated: clock.now().toUtc(),
|
||||||
fileEtag: null,
|
fileEtag: etag,
|
||||||
imageWidth: null,
|
imageWidth: int.parse(size["width"]!),
|
||||||
imageHeight: null,
|
imageHeight: int.parse(size["height"]!),
|
||||||
exif: new Exif(metadataPhotosIfd0),
|
exif: ifd0 != null || exif != null || gps != null
|
||||||
|
? Exif({
|
||||||
|
if (ifd0 != null) ...ifd0,
|
||||||
|
if (exif != null) ...exif,
|
||||||
|
if (lat != null && lng != null) ...{
|
||||||
|
"GPSLatitude": gpsDoubleToDms(lat),
|
||||||
|
"GPSLatitudeRef": lat.isNegative ? "S" : "N",
|
||||||
|
"GPSLongitude": gpsDoubleToDms(lng),
|
||||||
|
"GPSLongitudeRef": lng.isNegative ? "W" : "E",
|
||||||
|
if (alt != null) ...{
|
||||||
|
"GPSAltitude": doubleToRational(alt),
|
||||||
|
"GPSAltitudeRef": alt.isNegative ? 1 : 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}..removeWhere((key, value) =>
|
||||||
|
key == "MakerNote" ||
|
||||||
|
key == "UserComment" ||
|
||||||
|
key == "ImageDescription"))
|
||||||
|
: null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,9 @@ class FileWebdavDataSource implements FileDataSource {
|
||||||
trashbinOriginalLocation: 1,
|
trashbinOriginalLocation: 1,
|
||||||
trashbinDeletionTime: 1,
|
trashbinDeletionTime: 1,
|
||||||
metadataPhotosIfd0: 1,
|
metadataPhotosIfd0: 1,
|
||||||
|
metadataPhotosExif: 1,
|
||||||
|
metadataPhotosGps: 1,
|
||||||
|
metadataPhotosSize: 1,
|
||||||
customNamespaces: {
|
customNamespaces: {
|
||||||
"com.nkming.nc_photos": "app",
|
"com.nkming.nc_photos": "app",
|
||||||
},
|
},
|
||||||
|
@ -277,6 +280,9 @@ class FileWebdavDataSource implements FileDataSource {
|
||||||
trashbinOriginalLocation,
|
trashbinOriginalLocation,
|
||||||
trashbinDeletionTime,
|
trashbinDeletionTime,
|
||||||
metadataPhotosIfd0,
|
metadataPhotosIfd0,
|
||||||
|
metadataPhotosExif,
|
||||||
|
metadataPhotosGps,
|
||||||
|
metadataPhotosSize,
|
||||||
Map<String, String>? customNamespaces,
|
Map<String, String>? customNamespaces,
|
||||||
List<String>? customProperties,
|
List<String>? customProperties,
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -305,6 +311,9 @@ class FileWebdavDataSource implements FileDataSource {
|
||||||
trashbinOriginalLocation: trashbinOriginalLocation,
|
trashbinOriginalLocation: trashbinOriginalLocation,
|
||||||
trashbinDeletionTime: trashbinDeletionTime,
|
trashbinDeletionTime: trashbinDeletionTime,
|
||||||
metadataPhotosIfd0: metadataPhotosIfd0,
|
metadataPhotosIfd0: metadataPhotosIfd0,
|
||||||
|
metadataPhotosExif: metadataPhotosExif,
|
||||||
|
metadataPhotosGps: metadataPhotosGps,
|
||||||
|
metadataPhotosSize: metadataPhotosSize,
|
||||||
customNamespaces: customNamespaces,
|
customNamespaces: customNamespaces,
|
||||||
customProperties: customProperties,
|
customProperties: customProperties,
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,7 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/connectivity_util.dart' as connectivity_util;
|
import 'package:nc_photos/connectivity_util.dart' as connectivity_util;
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/exif_extension.dart';
|
import 'package:nc_photos/entity/exif_util.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
|
|
|
@ -14,7 +14,7 @@ import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/collection.dart';
|
import 'package:nc_photos/entity/collection.dart';
|
||||||
import 'package:nc_photos/entity/collection/adapter.dart';
|
import 'package:nc_photos/entity/collection/adapter.dart';
|
||||||
import 'package:nc_photos/entity/collection_item.dart';
|
import 'package:nc_photos/entity/collection_item.dart';
|
||||||
import 'package:nc_photos/entity/exif_extension.dart';
|
import 'package:nc_photos/entity/exif_util.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
|
|
110
app/test/entity/exif_util_test.dart
Normal file
110
app/test/entity/exif_util_test.dart
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import 'package:exifdart/exifdart.dart';
|
||||||
|
import 'package:nc_photos/entity/exif_util.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group("exif_util", () {
|
||||||
|
group("gpsDmsToDouble", () {
|
||||||
|
test("United Nations HQ", () {
|
||||||
|
// 40° 44′ 58″ N, 73° 58′ 5″ W
|
||||||
|
final lat = gpsDmsToDouble([
|
||||||
|
Rational(40, 1),
|
||||||
|
Rational(44, 1),
|
||||||
|
Rational(58, 1),
|
||||||
|
]);
|
||||||
|
final lng = gpsDmsToDouble([
|
||||||
|
Rational(73, 1),
|
||||||
|
Rational(58, 1),
|
||||||
|
Rational(5, 1),
|
||||||
|
]);
|
||||||
|
expect(lat, closeTo(40.749444, .00001));
|
||||||
|
expect(lng, closeTo(73.968056, .00001));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("East Cape Lighthouse", () {
|
||||||
|
// 37° 41′ 20.2″ S, 178° 32′ 53.3″ E
|
||||||
|
final lat = gpsDmsToDouble([
|
||||||
|
Rational(37, 1),
|
||||||
|
Rational(41, 1),
|
||||||
|
Rational(202, 10),
|
||||||
|
]);
|
||||||
|
final lng = gpsDmsToDouble([
|
||||||
|
Rational(178, 1),
|
||||||
|
Rational(32, 1),
|
||||||
|
Rational(533, 10),
|
||||||
|
]);
|
||||||
|
expect(lat, closeTo(37.688944, .00001));
|
||||||
|
expect(lng, closeTo(178.548139, .00001));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group("gpsDoubleToDms", () {
|
||||||
|
test("United Nations HQ", () {
|
||||||
|
// 40.749444, -73.968056
|
||||||
|
final lat = gpsDoubleToDms(40.749444);
|
||||||
|
final lng = gpsDoubleToDms(-73.968056);
|
||||||
|
expect(
|
||||||
|
lat.map((e) => e.toString()),
|
||||||
|
[
|
||||||
|
Rational(40, 1).toString(),
|
||||||
|
Rational(44, 1).toString(),
|
||||||
|
Rational(5799, 100).toString(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
lng.map((e) => e.toString()),
|
||||||
|
[
|
||||||
|
Rational(73, 1).toString(),
|
||||||
|
Rational(58, 1).toString(),
|
||||||
|
Rational(500, 100).toString(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("East Cape Lighthouse", () {
|
||||||
|
// -37.688944, 178.548139
|
||||||
|
final lat = gpsDoubleToDms(-37.688944);
|
||||||
|
final lng = gpsDoubleToDms(178.548139);
|
||||||
|
expect(
|
||||||
|
lat.map((e) => e.toString()),
|
||||||
|
[
|
||||||
|
Rational(37, 1).toString(),
|
||||||
|
Rational(41, 1).toString(),
|
||||||
|
Rational(2019, 100).toString(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
lng.map((e) => e.toString()),
|
||||||
|
[
|
||||||
|
Rational(178, 1).toString(),
|
||||||
|
Rational(32, 1).toString(),
|
||||||
|
Rational(5330, 100).toString(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group("doubleToRational", () {
|
||||||
|
test("<1000", () {
|
||||||
|
expect(
|
||||||
|
doubleToRational(123.456789123).toString(),
|
||||||
|
Rational(12345678, 100000).toString(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(">1000 <100000", () {
|
||||||
|
expect(
|
||||||
|
doubleToRational(12345.6789123).toString(),
|
||||||
|
Rational(12345678, 1000).toString(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(">100000", () {
|
||||||
|
expect(
|
||||||
|
doubleToRational(12345678.9123).toString(),
|
||||||
|
Rational(12345678, 1).toString(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
|
import 'package:exifdart/exifdart.dart' hide Metadata;
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:nc_photos/entity/exif.dart';
|
import 'package:nc_photos/entity/exif.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
|
@ -245,6 +247,14 @@ void main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group("fromApi", () {
|
||||||
|
test("size", _fromApiSize);
|
||||||
|
group("gps", () {
|
||||||
|
test("place1", _fromApiGpsPlace1);
|
||||||
|
test("place2", _fromApiGpsPlace2);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
group("MetadataUpgraderV1", () {
|
group("MetadataUpgraderV1", () {
|
||||||
|
@ -1217,3 +1227,116 @@ void main() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _fromApiSize() {
|
||||||
|
withClock(
|
||||||
|
Clock(() => DateTime(2020, 1, 2, 3, 4, 5)),
|
||||||
|
() {
|
||||||
|
final actual = Metadata.fromApi(
|
||||||
|
etag: null,
|
||||||
|
size: {
|
||||||
|
"width": "1234",
|
||||||
|
"height": "5678",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
actual,
|
||||||
|
Metadata(
|
||||||
|
imageWidth: 1234,
|
||||||
|
imageHeight: 5678,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _fromApiGpsPlace1() {
|
||||||
|
final actual = Metadata.fromApi(
|
||||||
|
etag: null,
|
||||||
|
size: {
|
||||||
|
"width": "1234",
|
||||||
|
"height": "5678",
|
||||||
|
},
|
||||||
|
gps: {
|
||||||
|
"latitude": "40.749444",
|
||||||
|
"longitude": "-73.968056",
|
||||||
|
"altitude": "12.345678",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
actual?.exif,
|
||||||
|
_MetadataGpsMatcher(Exif({
|
||||||
|
"GPSLatitude": [Rational(40, 1), Rational(44, 1), Rational(5799, 100)],
|
||||||
|
"GPSLatitudeRef": "N",
|
||||||
|
"GPSLongitude": [Rational(73, 1), Rational(58, 1), Rational(500, 100)],
|
||||||
|
"GPSLongitudeRef": "W",
|
||||||
|
"GPSAltitude": Rational(1234567, 100000),
|
||||||
|
"GPSAltitudeRef": 0,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _fromApiGpsPlace2() {
|
||||||
|
final actual = Metadata.fromApi(
|
||||||
|
etag: null,
|
||||||
|
size: {
|
||||||
|
"width": "1234",
|
||||||
|
"height": "5678",
|
||||||
|
},
|
||||||
|
gps: {
|
||||||
|
"latitude": "-37.688944",
|
||||||
|
"longitude": "178.5481396",
|
||||||
|
"altitude": "-12.345678",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
actual?.exif,
|
||||||
|
_MetadataGpsMatcher(Exif({
|
||||||
|
"GPSLatitude": [Rational(37, 1), Rational(41, 1), Rational(2019, 100)],
|
||||||
|
"GPSLatitudeRef": "S",
|
||||||
|
"GPSLongitude": [Rational(178, 1), Rational(32, 1), Rational(5330, 100)],
|
||||||
|
"GPSLongitudeRef": "E",
|
||||||
|
"GPSAltitude": Rational(1234567, 100000),
|
||||||
|
"GPSAltitudeRef": 1,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MetadataGpsMatcher extends Matcher {
|
||||||
|
const _MetadataGpsMatcher(this.expected);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matches(Object? item, Map matchState) {
|
||||||
|
final actual = item as Exif;
|
||||||
|
final gpsLatitude = listEquals(
|
||||||
|
actual["GPSLatitude"]?.map((e) => e.toString()).toList(),
|
||||||
|
expected["GPSLatitude"]?.map((e) => e.toString()).toList(),
|
||||||
|
);
|
||||||
|
final gpsLatitudeRef =
|
||||||
|
actual["GPSLatitudeRef"] == expected["GPSLatitudeRef"];
|
||||||
|
final gpsLongitude = listEquals(
|
||||||
|
actual["GPSLongitude"]?.map((e) => e.toString()).toList(),
|
||||||
|
expected["GPSLongitude"]?.map((e) => e.toString()).toList(),
|
||||||
|
);
|
||||||
|
final gpsLongitudeRef =
|
||||||
|
actual["GPSLongitudeRef"] == expected["GPSLongitudeRef"];
|
||||||
|
final gpsAltitude = actual["GPSAltitude"]?.toString() ==
|
||||||
|
expected["GPSAltitude"]?.toString();
|
||||||
|
final gpsAltitudeRef =
|
||||||
|
actual["GPSAltitudeRef"] == expected["GPSAltitudeRef"];
|
||||||
|
|
||||||
|
return gpsLatitude &&
|
||||||
|
gpsLatitudeRef &&
|
||||||
|
gpsLongitude &&
|
||||||
|
gpsLongitudeRef &&
|
||||||
|
gpsAltitude &&
|
||||||
|
gpsAltitudeRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Description describe(Description description) {
|
||||||
|
return description.add(expected.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
final Exif expected;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue