mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-01-22 08:46:18 +01:00
Switch to use exiv2 for client side exif
This commit is contained in:
parent
f1704cc37d
commit
cebabdc6a8
9 changed files with 190 additions and 140 deletions
|
@ -1,9 +1,9 @@
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:exifdart/exifdart.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_common/type.dart';
|
import 'package:np_common/type.dart';
|
||||||
|
import 'package:np_exiv2/np_exiv2.dart';
|
||||||
|
|
||||||
part 'exif.g.dart';
|
part 'exif.g.dart';
|
||||||
|
|
||||||
|
@ -47,9 +47,9 @@ class Exif with EquatableMixin {
|
||||||
.map((e) {
|
.map((e) {
|
||||||
dynamic jsonValue;
|
dynamic jsonValue;
|
||||||
if (e.value is Rational) {
|
if (e.value is Rational) {
|
||||||
jsonValue = e.value.toJson();
|
jsonValue = (e.value as Rational).toJson();
|
||||||
} else if (e.value is List) {
|
} else if (e.value is List) {
|
||||||
jsonValue = e.value.map((e) {
|
jsonValue = (e.value as List).map((e) {
|
||||||
if (e is Rational) {
|
if (e is Rational) {
|
||||||
return e.toJson();
|
return e.toJson();
|
||||||
} else {
|
} else {
|
||||||
|
@ -69,11 +69,12 @@ class Exif with EquatableMixin {
|
||||||
json.entries.map((e) {
|
json.entries.map((e) {
|
||||||
dynamic exifValue;
|
dynamic exifValue;
|
||||||
if (e.value is Map) {
|
if (e.value is Map) {
|
||||||
exifValue = Rational.fromJson(e.value.cast<String, dynamic>());
|
exifValue =
|
||||||
|
_rationalFromJson((e.value as Map).cast<String, dynamic>());
|
||||||
} else if (e.value is List) {
|
} else if (e.value is List) {
|
||||||
exifValue = e.value.map((e) {
|
exifValue = (e.value as List).map((e) {
|
||||||
if (e is Map) {
|
if (e is Map) {
|
||||||
return Rational.fromJson(e.cast<String, dynamic>());
|
return _rationalFromJson(e.cast<String, dynamic>());
|
||||||
} else {
|
} else {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
@ -187,3 +188,15 @@ class Exif with EquatableMixin {
|
||||||
|
|
||||||
static final dateTimeFormat = DateFormat("yyyy:MM:dd HH:mm:ss");
|
static final dateTimeFormat = DateFormat("yyyy:MM:dd HH:mm:ss");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension on Rational {
|
||||||
|
Map<String, int> toJson() => {
|
||||||
|
"n": numerator,
|
||||||
|
"d": denominator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Rational _rationalFromJson(Map<String, dynamic> json) {
|
||||||
|
return Rational(
|
||||||
|
json["n"] ?? json["numerator"], json["d"] ?? json["denominator"]);
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:exifdart/exifdart.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:nc_photos/entity/exif.dart';
|
import 'package:nc_photos/entity/exif.dart';
|
||||||
|
import 'package:np_exiv2/np_exiv2.dart';
|
||||||
|
|
||||||
extension ExifExtension on Exif {
|
extension ExifExtension on Exif {
|
||||||
double? get gpsLatitudeDeg {
|
double? get gpsLatitudeDeg {
|
||||||
|
|
|
@ -140,5 +140,8 @@ final supportedVideoFormatMimes =
|
||||||
|
|
||||||
const _metadataSupportedFormatMimes = [
|
const _metadataSupportedFormatMimes = [
|
||||||
"image/jpeg",
|
"image/jpeg",
|
||||||
|
"image/png",
|
||||||
|
"image/webp",
|
||||||
"image/heic",
|
"image/heic",
|
||||||
|
"image/gif",
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
import 'dart:io' as io;
|
import 'dart:io' as io;
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:exifdart/exifdart.dart' as exifdart;
|
|
||||||
import 'package:exifdart/exifdart_io.dart';
|
|
||||||
import 'package:exifdart/exifdart_memory.dart';
|
|
||||||
import 'package:image_size_getter/image_size_getter.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/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/entity/exif.dart';
|
import 'package:nc_photos/entity/exif.dart';
|
||||||
import 'package:nc_photos/entity/file.dart' as app;
|
import 'package:nc_photos/entity/file.dart' as app;
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
|
||||||
import 'package:nc_photos/file_extension.dart';
|
import 'package:nc_photos/file_extension.dart';
|
||||||
import 'package:nc_photos/image_size_getter_util.dart';
|
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_collection/np_collection.dart';
|
||||||
|
import 'package:np_exiv2/np_exiv2.dart' as exiv2;
|
||||||
|
|
||||||
part 'load_metadata.g.dart';
|
part 'load_metadata.g.dart';
|
||||||
|
|
||||||
|
@ -24,8 +20,7 @@ class LoadMetadata {
|
||||||
Account account, app.File file, Uint8List binary) {
|
Account account, app.File file, Uint8List binary) {
|
||||||
return _loadMetadata(
|
return _loadMetadata(
|
||||||
mime: file.contentType ?? "",
|
mime: file.contentType ?? "",
|
||||||
exifdartReaderBuilder: () => MemoryBlobReader(binary),
|
reader: () => exiv2.readBuffer(binary),
|
||||||
imageSizeGetterInputBuilder: () => AsyncMemoryInput(binary),
|
|
||||||
filename: file.path,
|
filename: file.path,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -37,90 +32,70 @@ class LoadMetadata {
|
||||||
mime = mime ?? await file.readMime();
|
mime = mime ?? await file.readMime();
|
||||||
return _loadMetadata(
|
return _loadMetadata(
|
||||||
mime: mime ?? "",
|
mime: mime ?? "",
|
||||||
exifdartReaderBuilder: () => FileReader(file),
|
reader: () => exiv2.readFile(file.path),
|
||||||
imageSizeGetterInputBuilder: () => AsyncFileInput(file),
|
|
||||||
filename: file.path,
|
filename: file.path,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<app.Metadata> _loadMetadata({
|
Future<app.Metadata> _loadMetadata({
|
||||||
required String mime,
|
required String mime,
|
||||||
required exifdart.AbstractBlobReader Function() exifdartReaderBuilder,
|
required exiv2.ReadResult Function() reader,
|
||||||
required AsyncImageInput Function() imageSizeGetterInputBuilder,
|
|
||||||
String? filename,
|
String? filename,
|
||||||
}) async {
|
}) async {
|
||||||
var metadata = exifdart.Metadata();
|
final exiv2.ReadResult result;
|
||||||
if (file_util.isMetadataSupportedMime(mime)) {
|
try {
|
||||||
try {
|
result = reader();
|
||||||
metadata = await exifdart.readMetadata(exifdartReaderBuilder());
|
} catch (e, stacktrace) {
|
||||||
} catch (e, stacktrace) {
|
_log.shout(
|
||||||
_log.shout(
|
"[_loadMetadata] Failed while readMetadata for $mime file: ${logFilename(filename)}",
|
||||||
"[_loadMetadata] Failed while readMetadata for $mime file: ${logFilename(filename)}",
|
e,
|
||||||
e,
|
stacktrace);
|
||||||
stacktrace);
|
rethrow;
|
||||||
// ignore exif
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
final metadata = {
|
||||||
int imageWidth = 0, imageHeight = 0;
|
...result.iptcData
|
||||||
if (metadata.imageWidth == null || metadata.imageHeight == null) {
|
.map((e) {
|
||||||
try {
|
try {
|
||||||
final resolution =
|
return MapEntry(e.tagKey, e.value.asTyped());
|
||||||
await AsyncImageSizeGetter.getSize(imageSizeGetterInputBuilder());
|
} catch (_) {
|
||||||
// image size getter doesn't handle exif orientation
|
_log.shout(
|
||||||
if (metadata.exif?.containsKey("Orientation") == true &&
|
"[_loadMetadata] Unable to convert IPTC tag: ${e.tagKey}, ${e.value.toDebugString()}");
|
||||||
metadata.exif!["Orientation"] >= 5 &&
|
return null;
|
||||||
metadata.exif!["Orientation"] <= 8) {
|
}
|
||||||
// 90 deg CW/CCW
|
})
|
||||||
imageWidth = resolution.height;
|
.nonNulls
|
||||||
imageHeight = resolution.width;
|
.toMap(),
|
||||||
} else {
|
...result.exifData
|
||||||
imageWidth = resolution.width;
|
.map((e) {
|
||||||
imageHeight = resolution.height;
|
try {
|
||||||
}
|
return MapEntry(e.tagKey, e.value.asTyped());
|
||||||
} catch (e, stacktrace) {
|
} catch (_) {
|
||||||
// is this even an image file?
|
_log.shout(
|
||||||
_log.shout(
|
"[_loadMetadata] Unable to convert EXIF tag: ${e.tagKey}, ${e.value.toDebugString()}");
|
||||||
"[_loadMetadata] Failed while getSize for $mime file: ${logFilename(filename)}",
|
return null;
|
||||||
e,
|
}
|
||||||
stacktrace);
|
})
|
||||||
}
|
.nonNulls
|
||||||
} else {
|
.toMap(),
|
||||||
if (metadata.rotateAngleCcw != null &&
|
|
||||||
metadata.rotateAngleCcw! % 180 != 0) {
|
|
||||||
imageWidth = metadata.imageHeight!;
|
|
||||||
imageHeight = metadata.imageWidth!;
|
|
||||||
} else {
|
|
||||||
imageWidth = metadata.imageWidth!;
|
|
||||||
imageHeight = metadata.imageHeight!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final map = {
|
|
||||||
if (metadata.exif != null) "exif": metadata.exif,
|
|
||||||
if (imageWidth > 0 && imageHeight > 0)
|
|
||||||
"resolution": {
|
|
||||||
"width": imageWidth,
|
|
||||||
"height": imageHeight,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
return _buildMetadata(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
app.Metadata _buildMetadata(Map<String, dynamic> map) {
|
var imageWidth = 0, imageHeight = 0;
|
||||||
int? imageWidth, imageHeight;
|
// exiv2 doesn't handle exif orientation
|
||||||
Exif? exif;
|
if (metadata.containsKey("Orientation") &&
|
||||||
if (map.containsKey("resolution")) {
|
metadata["Orientation"] as int >= 5 &&
|
||||||
imageWidth = map["resolution"]["width"];
|
metadata["Orientation"] as int <= 8) {
|
||||||
imageHeight = map["resolution"]["height"];
|
// 90 deg CW/CCW
|
||||||
}
|
imageWidth = result.height;
|
||||||
if (map.containsKey("exif")) {
|
imageHeight = result.width;
|
||||||
exif = Exif(map["exif"]);
|
} else {
|
||||||
|
imageWidth = result.width;
|
||||||
|
imageHeight = result.height;
|
||||||
}
|
}
|
||||||
|
|
||||||
return app.Metadata(
|
return app.Metadata(
|
||||||
imageWidth: imageWidth,
|
imageWidth: imageWidth,
|
||||||
imageHeight: imageHeight,
|
imageHeight: imageHeight,
|
||||||
exif: exif,
|
exif: metadata.isNotEmpty ? Exif(metadata) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -397,6 +397,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.5"
|
version: "2.0.5"
|
||||||
|
euc:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: euc
|
||||||
|
sha256: "569b21c71ee5a3aa3e96f70512cd10d1fad7b438429fade65ec2a50038a09dc5"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.6+8"
|
||||||
event_bus:
|
event_bus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -405,15 +413,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
exifdart:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
path: "."
|
|
||||||
ref: "1.3.0"
|
|
||||||
resolved-ref: c91db71fbb3d3cee1c79716502dd37eadc8327ff
|
|
||||||
url: "https://gitlab.com/nc-photos/exifdart.git"
|
|
||||||
source: git
|
|
||||||
version: "1.3.0"
|
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1148,6 +1147,20 @@ packages:
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
np_exiv2:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../np_exiv2"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "1.0.0"
|
||||||
|
np_exiv2_lib:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../np_exiv2_lib"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "0.0.1"
|
||||||
np_geocoder:
|
np_geocoder:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -56,10 +56,6 @@ dependencies:
|
||||||
dynamic_color: ^1.7.0
|
dynamic_color: ^1.7.0
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
event_bus: ^2.0.0
|
event_bus: ^2.0.0
|
||||||
exifdart:
|
|
||||||
git:
|
|
||||||
url: https://gitlab.com/nc-photos/exifdart.git
|
|
||||||
ref: 1.3.0
|
|
||||||
flex_seed_scheme: ^1.5.0
|
flex_seed_scheme: ^1.5.0
|
||||||
fluttertoast: ^8.2.5
|
fluttertoast: ^8.2.5
|
||||||
flutter_background_service:
|
flutter_background_service:
|
||||||
|
@ -107,6 +103,10 @@ dependencies:
|
||||||
path: ../np_datetime
|
path: ../np_datetime
|
||||||
np_db:
|
np_db:
|
||||||
path: ../np_db
|
path: ../np_db
|
||||||
|
np_exiv2:
|
||||||
|
path: ../np_exiv2
|
||||||
|
np_exiv2_lib:
|
||||||
|
path: ../np_exiv2_lib
|
||||||
np_geocoder:
|
np_geocoder:
|
||||||
path: ../np_geocoder
|
path: ../np_geocoder
|
||||||
np_gps_map:
|
np_gps_map:
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:exifdart/exifdart.dart';
|
|
||||||
import 'package:nc_photos/entity/exif.dart';
|
import 'package:nc_photos/entity/exif.dart';
|
||||||
|
import 'package:np_exiv2/np_exiv2.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -96,7 +96,7 @@ void main() {
|
||||||
|
|
||||||
test("Rational", () {
|
test("Rational", () {
|
||||||
final exif = Exif(<String, dynamic>{
|
final exif = Exif(<String, dynamic>{
|
||||||
"XResolution": Rational(72, 1),
|
"XResolution": const Rational(72, 1),
|
||||||
});
|
});
|
||||||
expect(exif.toJson(), <String, dynamic>{
|
expect(exif.toJson(), <String, dynamic>{
|
||||||
"XResolution": {"n": 72, "d": 1},
|
"XResolution": {"n": 72, "d": 1},
|
||||||
|
@ -114,7 +114,11 @@ void main() {
|
||||||
|
|
||||||
test("List<Rational>", () {
|
test("List<Rational>", () {
|
||||||
final exif = Exif(<String, dynamic>{
|
final exif = Exif(<String, dynamic>{
|
||||||
"GPSLatitude": [Rational(2, 1), Rational(3, 1), Rational(4, 100)],
|
"GPSLatitude": [
|
||||||
|
const Rational(2, 1),
|
||||||
|
const Rational(3, 1),
|
||||||
|
const Rational(4, 100),
|
||||||
|
],
|
||||||
});
|
});
|
||||||
expect(exif.toJson(), <String, dynamic>{
|
expect(exif.toJson(), <String, dynamic>{
|
||||||
"GPSLatitude": [
|
"GPSLatitude": [
|
||||||
|
@ -164,12 +168,20 @@ void main() {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Rational", () {
|
test("Rational (legacy)", () {
|
||||||
final json = <String, dynamic>{
|
final json = <String, dynamic>{
|
||||||
"XResolution": {"numerator": 72, "denominator": 1},
|
"XResolution": {"numerator": 72, "denominator": 1},
|
||||||
};
|
};
|
||||||
final Rational exif = Exif.fromJson(json)["XResolution"];
|
final Rational exif = Exif.fromJson(json)["XResolution"];
|
||||||
expect(exif.makeComparable(), _Rational(72, 1));
|
expect(exif.makeComparable(), const _Rational(72, 1));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Rational", () {
|
||||||
|
final json = <String, dynamic>{
|
||||||
|
"XResolution": {"n": 72, "d": 1},
|
||||||
|
};
|
||||||
|
final Rational exif = Exif.fromJson(json)["XResolution"];
|
||||||
|
expect(exif.makeComparable(), const _Rational(72, 1));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("List<int>", () {
|
test("List<int>", () {
|
||||||
|
@ -193,8 +205,11 @@ void main() {
|
||||||
};
|
};
|
||||||
final List<Rational> exif =
|
final List<Rational> exif =
|
||||||
Exif.fromJson(json)["GPSLatitude"].cast<Rational>();
|
Exif.fromJson(json)["GPSLatitude"].cast<Rational>();
|
||||||
expect(exif.map((e) => e.makeComparable()).toList(),
|
expect(exif.map((e) => e.makeComparable()).toList(), [
|
||||||
[_Rational(2, 1), _Rational(3, 1), _Rational(4, 100)]);
|
const _Rational(2, 1),
|
||||||
|
const _Rational(3, 1),
|
||||||
|
const _Rational(4, 100)
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -212,6 +227,21 @@ void main() {
|
||||||
expect(exif.dateTimeOriginal, null);
|
expect(exif.dateTimeOriginal, null);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group("from Nextcloud", () {
|
||||||
|
test("Rational", () {
|
||||||
|
final exif = Exif({
|
||||||
|
"ExposureTime": "123/456",
|
||||||
|
});
|
||||||
|
expect(exif.exposureTime?.makeComparable(), const _Rational(123, 456));
|
||||||
|
});
|
||||||
|
test("int", () {
|
||||||
|
final exif = Exif({
|
||||||
|
"ISOSpeedRatings": "1234",
|
||||||
|
});
|
||||||
|
expect(exif.isoSpeedRatings, 1234);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +250,7 @@ extension on Rational {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Rational extends Rational with EquatableMixin {
|
class _Rational extends Rational with EquatableMixin {
|
||||||
_Rational(super.numerator, super.denominator);
|
const _Rational(super.numerator, super.denominator);
|
||||||
|
|
||||||
factory _Rational.of(Rational r) {
|
factory _Rational.of(Rational r) {
|
||||||
return _Rational(r.numerator, r.denominator);
|
return _Rational(r.numerator, r.denominator);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import 'package:exifdart/exifdart.dart';
|
|
||||||
import 'package:nc_photos/entity/exif_util.dart';
|
import 'package:nc_photos/entity/exif_util.dart';
|
||||||
|
import 'package:np_exiv2/np_exiv2.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
@ -8,14 +8,14 @@ void main() {
|
||||||
test("United Nations HQ", () {
|
test("United Nations HQ", () {
|
||||||
// 40° 44′ 58″ N, 73° 58′ 5″ W
|
// 40° 44′ 58″ N, 73° 58′ 5″ W
|
||||||
final lat = gpsDmsToDouble([
|
final lat = gpsDmsToDouble([
|
||||||
Rational(40, 1),
|
const Rational(40, 1),
|
||||||
Rational(44, 1),
|
const Rational(44, 1),
|
||||||
Rational(58, 1),
|
const Rational(58, 1),
|
||||||
]);
|
]);
|
||||||
final lng = gpsDmsToDouble([
|
final lng = gpsDmsToDouble([
|
||||||
Rational(73, 1),
|
const Rational(73, 1),
|
||||||
Rational(58, 1),
|
const Rational(58, 1),
|
||||||
Rational(5, 1),
|
const Rational(5, 1),
|
||||||
]);
|
]);
|
||||||
expect(lat, closeTo(40.749444, .00001));
|
expect(lat, closeTo(40.749444, .00001));
|
||||||
expect(lng, closeTo(73.968056, .00001));
|
expect(lng, closeTo(73.968056, .00001));
|
||||||
|
@ -24,14 +24,14 @@ void main() {
|
||||||
test("East Cape Lighthouse", () {
|
test("East Cape Lighthouse", () {
|
||||||
// 37° 41′ 20.2″ S, 178° 32′ 53.3″ E
|
// 37° 41′ 20.2″ S, 178° 32′ 53.3″ E
|
||||||
final lat = gpsDmsToDouble([
|
final lat = gpsDmsToDouble([
|
||||||
Rational(37, 1),
|
const Rational(37, 1),
|
||||||
Rational(41, 1),
|
const Rational(41, 1),
|
||||||
Rational(202, 10),
|
const Rational(202, 10),
|
||||||
]);
|
]);
|
||||||
final lng = gpsDmsToDouble([
|
final lng = gpsDmsToDouble([
|
||||||
Rational(178, 1),
|
const Rational(178, 1),
|
||||||
Rational(32, 1),
|
const Rational(32, 1),
|
||||||
Rational(533, 10),
|
const Rational(533, 10),
|
||||||
]);
|
]);
|
||||||
expect(lat, closeTo(37.688944, .00001));
|
expect(lat, closeTo(37.688944, .00001));
|
||||||
expect(lng, closeTo(178.548139, .00001));
|
expect(lng, closeTo(178.548139, .00001));
|
||||||
|
@ -46,17 +46,17 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
lat.map((e) => e.toString()),
|
lat.map((e) => e.toString()),
|
||||||
[
|
[
|
||||||
Rational(40, 1).toString(),
|
const Rational(40, 1).toString(),
|
||||||
Rational(44, 1).toString(),
|
const Rational(44, 1).toString(),
|
||||||
Rational(5799, 100).toString(),
|
const Rational(5799, 100).toString(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
lng.map((e) => e.toString()),
|
lng.map((e) => e.toString()),
|
||||||
[
|
[
|
||||||
Rational(73, 1).toString(),
|
const Rational(73, 1).toString(),
|
||||||
Rational(58, 1).toString(),
|
const Rational(58, 1).toString(),
|
||||||
Rational(500, 100).toString(),
|
const Rational(500, 100).toString(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -68,17 +68,17 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
lat.map((e) => e.toString()),
|
lat.map((e) => e.toString()),
|
||||||
[
|
[
|
||||||
Rational(37, 1).toString(),
|
const Rational(37, 1).toString(),
|
||||||
Rational(41, 1).toString(),
|
const Rational(41, 1).toString(),
|
||||||
Rational(2019, 100).toString(),
|
const Rational(2019, 100).toString(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
lng.map((e) => e.toString()),
|
lng.map((e) => e.toString()),
|
||||||
[
|
[
|
||||||
Rational(178, 1).toString(),
|
const Rational(178, 1).toString(),
|
||||||
Rational(32, 1).toString(),
|
const Rational(32, 1).toString(),
|
||||||
Rational(5330, 100).toString(),
|
const Rational(5330, 100).toString(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -88,21 +88,21 @@ void main() {
|
||||||
test("<1000", () {
|
test("<1000", () {
|
||||||
expect(
|
expect(
|
||||||
doubleToRational(123.456789123).toString(),
|
doubleToRational(123.456789123).toString(),
|
||||||
Rational(12345678, 100000).toString(),
|
const Rational(12345678, 100000).toString(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(">1000 <100000", () {
|
test(">1000 <100000", () {
|
||||||
expect(
|
expect(
|
||||||
doubleToRational(12345.6789123).toString(),
|
doubleToRational(12345.6789123).toString(),
|
||||||
Rational(12345678, 1000).toString(),
|
const Rational(12345678, 1000).toString(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(">100000", () {
|
test(">100000", () {
|
||||||
expect(
|
expect(
|
||||||
doubleToRational(12345678.9123).toString(),
|
doubleToRational(12345678.9123).toString(),
|
||||||
Rational(12345678, 1).toString(),
|
const Rational(12345678, 1).toString(),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:exifdart/exifdart.dart' hide Metadata;
|
|
||||||
import 'package:flutter/foundation.dart';
|
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';
|
||||||
import 'package:np_common/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
|
import 'package:np_exiv2/np_exiv2.dart';
|
||||||
import 'package:np_string/np_string.dart';
|
import 'package:np_string/np_string.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -1266,11 +1266,19 @@ void _fromApiGpsPlace1() {
|
||||||
expect(
|
expect(
|
||||||
actual?.exif,
|
actual?.exif,
|
||||||
_MetadataGpsMatcher(Exif({
|
_MetadataGpsMatcher(Exif({
|
||||||
"GPSLatitude": [Rational(40, 1), Rational(44, 1), Rational(5799, 100)],
|
"GPSLatitude": [
|
||||||
|
const Rational(40, 1),
|
||||||
|
const Rational(44, 1),
|
||||||
|
const Rational(5799, 100),
|
||||||
|
],
|
||||||
"GPSLatitudeRef": "N",
|
"GPSLatitudeRef": "N",
|
||||||
"GPSLongitude": [Rational(73, 1), Rational(58, 1), Rational(500, 100)],
|
"GPSLongitude": [
|
||||||
|
const Rational(73, 1),
|
||||||
|
const Rational(58, 1),
|
||||||
|
const Rational(500, 100),
|
||||||
|
],
|
||||||
"GPSLongitudeRef": "W",
|
"GPSLongitudeRef": "W",
|
||||||
"GPSAltitude": Rational(1234567, 100000),
|
"GPSAltitude": const Rational(1234567, 100000),
|
||||||
"GPSAltitudeRef": 0,
|
"GPSAltitudeRef": 0,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
@ -1292,11 +1300,19 @@ void _fromApiGpsPlace2() {
|
||||||
expect(
|
expect(
|
||||||
actual?.exif,
|
actual?.exif,
|
||||||
_MetadataGpsMatcher(Exif({
|
_MetadataGpsMatcher(Exif({
|
||||||
"GPSLatitude": [Rational(37, 1), Rational(41, 1), Rational(2019, 100)],
|
"GPSLatitude": [
|
||||||
|
const Rational(37, 1),
|
||||||
|
const Rational(41, 1),
|
||||||
|
const Rational(2019, 100),
|
||||||
|
],
|
||||||
"GPSLatitudeRef": "S",
|
"GPSLatitudeRef": "S",
|
||||||
"GPSLongitude": [Rational(178, 1), Rational(32, 1), Rational(5330, 100)],
|
"GPSLongitude": [
|
||||||
|
const Rational(178, 1),
|
||||||
|
const Rational(32, 1),
|
||||||
|
const Rational(5330, 100),
|
||||||
|
],
|
||||||
"GPSLongitudeRef": "E",
|
"GPSLongitudeRef": "E",
|
||||||
"GPSAltitude": Rational(1234567, 100000),
|
"GPSAltitude": const Rational(1234567, 100000),
|
||||||
"GPSAltitudeRef": 1,
|
"GPSAltitudeRef": 1,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue