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:np_api/np_api.dart' as api;
|
||||
import 'package:np_codegen/np_codegen.dart';
|
||||
import 'package:np_common/object_util.dart';
|
||||
import 'package:np_string/np_string.dart';
|
||||
|
||||
part 'entity_converter.g.dart';
|
||||
|
@ -49,9 +50,18 @@ class ApiFavoriteConverter {
|
|||
}
|
||||
|
||||
class ApiFileConverter {
|
||||
static File fromApi(api.File file) {
|
||||
var metadata = file.customProperties?["com.nkming.nc_photos:metadata"]
|
||||
?.run((obj) => Metadata.fromJson(
|
||||
static Metadata? _metadataFromApi(api.File file) {
|
||||
if (file.metadataPhotosSize != null) {
|
||||
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),
|
||||
upgraderV1: MetadataUpgraderV1(
|
||||
fileContentType: file.contentType,
|
||||
|
@ -66,15 +76,10 @@ class ApiFileConverter {
|
|||
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(
|
||||
path: _hrefToPath(file.href),
|
||||
contentLength: file.contentLength,
|
||||
|
@ -90,7 +95,7 @@ class ApiFileConverter {
|
|||
trashbinFilename: file.trashbinFilename,
|
||||
trashbinOriginalLocation: file.trashbinOriginalLocation,
|
||||
trashbinDeletionTime: file.trashbinDeletionTime,
|
||||
metadata: metadata,
|
||||
metadata: _metadataFromApi(file),
|
||||
isArchived: file.customProperties?["com.nkming.nc_photos:is-archived"]
|
||||
?.run((obj) => obj == "true"),
|
||||
overrideDateTime: file
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:exifdart/exifdart.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:nc_photos/entity/exif.dart';
|
||||
|
||||
extension ExifExtension on Exif {
|
||||
|
@ -12,7 +13,7 @@ extension ExifExtension on Exif {
|
|||
// invalid value
|
||||
return null;
|
||||
} 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
|
||||
return null;
|
||||
} 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();
|
||||
if (dms.length > 1) {
|
||||
product += dms[1].toDouble() / 60;
|
||||
|
@ -41,3 +53,14 @@ double _gpsDmsToDouble(List<Rational> dms) {
|
|||
}
|
||||
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:nc_photos/account.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_util.dart' as file_util;
|
||||
import 'package:nc_photos/json_util.dart' as json_util;
|
||||
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/type.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(
|
||||
lastUpdated: null,
|
||||
fileEtag: null,
|
||||
imageWidth: null,
|
||||
imageHeight: null,
|
||||
exif: new Exif(metadataPhotosIfd0),
|
||||
lastUpdated: clock.now().toUtc(),
|
||||
fileEtag: etag,
|
||||
imageWidth: int.parse(size["width"]!),
|
||||
imageHeight: int.parse(size["height"]!),
|
||||
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,
|
||||
trashbinDeletionTime: 1,
|
||||
metadataPhotosIfd0: 1,
|
||||
metadataPhotosExif: 1,
|
||||
metadataPhotosGps: 1,
|
||||
metadataPhotosSize: 1,
|
||||
customNamespaces: {
|
||||
"com.nkming.nc_photos": "app",
|
||||
},
|
||||
|
@ -277,6 +280,9 @@ class FileWebdavDataSource implements FileDataSource {
|
|||
trashbinOriginalLocation,
|
||||
trashbinDeletionTime,
|
||||
metadataPhotosIfd0,
|
||||
metadataPhotosExif,
|
||||
metadataPhotosGps,
|
||||
metadataPhotosSize,
|
||||
Map<String, String>? customNamespaces,
|
||||
List<String>? customProperties,
|
||||
}) async {
|
||||
|
@ -305,6 +311,9 @@ class FileWebdavDataSource implements FileDataSource {
|
|||
trashbinOriginalLocation: trashbinOriginalLocation,
|
||||
trashbinDeletionTime: trashbinDeletionTime,
|
||||
metadataPhotosIfd0: metadataPhotosIfd0,
|
||||
metadataPhotosExif: metadataPhotosExif,
|
||||
metadataPhotosGps: metadataPhotosGps,
|
||||
metadataPhotosSize: metadataPhotosSize,
|
||||
customNamespaces: customNamespaces,
|
||||
customProperties: customProperties,
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@ import 'package:logging/logging.dart';
|
|||
import 'package:nc_photos/account.dart';
|
||||
import 'package:nc_photos/connectivity_util.dart' as connectivity_util;
|
||||
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/event/event.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/adapter.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_descriptor.dart';
|
||||
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:exifdart/exifdart.dart' hide Metadata;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:nc_photos/entity/exif.dart';
|
||||
import 'package:nc_photos/entity/file.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", () {
|
||||
|
@ -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