Location tag fixes (#993)

This commit is contained in:
Neeraj Gupta 2023-04-18 14:07:46 +05:30 committed by GitHub
commit d83607b574
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 169 additions and 80 deletions

View file

@ -60,7 +60,7 @@ const publicLinkDeviceLimits = [50, 25, 10, 5, 2, 1];
const kilometersPerDegree = 111.16;
const radiusValues = <int>[2, 10, 20, 40, 80, 200, 400, 1200];
const radiusValues = <int>[1, 2, 10, 20, 40, 80, 200, 400, 1200];
const defaultRadiusValueIndex = 4;

View file

@ -1375,7 +1375,8 @@ class FilesDB {
Future<List<File>> getAllFilesFromDB(Set<int> collectionsToIgnore) async {
final db = await instance.database;
final List<Map<String, dynamic>> result = await db.query(filesTable);
final List<Map<String, dynamic>> result =
await db.query(filesTable, orderBy: '$columnCreationTime DESC');
final List<File> files = convertToFiles(result);
final List<File> deduplicatedFiles =
_deduplicatedAndFilterIgnoredFiles(files, collectionsToIgnore);
@ -1463,11 +1464,16 @@ class FilesDB {
row[columnMMdVisibility] = file.magicMetadata.visibility;
row[columnPubMMdVersion] = file.pubMmdVersion;
row[columnPubMMdEncodedJson] = file.pubMmdEncodedJson ?? '{}';
if (file.pubMagicMetadata != null &&
file.pubMagicMetadata!.editedTime != null) {
// override existing creationTime to avoid re-writing all queries related
// to loading the gallery
row[columnCreationTime] = file.pubMagicMetadata!.editedTime;
// override existing fields to avoid re-writing all queries and logic
if (file.pubMagicMetadata != null) {
if (file.pubMagicMetadata!.editedTime != null) {
row[columnCreationTime] = file.pubMagicMetadata!.editedTime;
}
if (file.pubMagicMetadata!.lat != null &&
file.pubMagicMetadata!.long != null) {
row[columnLatitude] = file.pubMagicMetadata!.lat;
row[columnLongitude] = file.pubMagicMetadata!.long;
}
}
return row;
}

View file

@ -179,12 +179,25 @@ class File extends EnteFile {
}
}
bool hasExifTime = false;
if (fileType == FileType.image && mediaUploadData.sourceFile != null) {
final exifTime =
await getCreationTimeFromEXIF(mediaUploadData.sourceFile!);
if (exifTime != null) {
hasExifTime = true;
creationTime = exifTime.microsecondsSinceEpoch;
if ((fileType == FileType.image || fileType == FileType.video) &&
mediaUploadData.sourceFile != null) {
final exifData = await getExifFromSourceFile(mediaUploadData.sourceFile!);
if (exifData != null) {
if (fileType == FileType.image) {
final exifTime = await getCreationTimeFromEXIF(null, exifData);
if (exifTime != null) {
hasExifTime = true;
creationTime = exifTime.microsecondsSinceEpoch;
}
}
if (Platform.isAndroid) {
//Fix for missing location data in lower android versions.
final Location? exifLocation = locationFromExif(exifData);
if (exifLocation?.latitude != null &&
exifLocation?.longitude != null) {
location = exifLocation;
}
}
}
}
// Try to get the timestamp from fileName. In case of iOS, file names are
@ -289,7 +302,7 @@ class File extends EnteFile {
bool get hasLocation {
return location != null &&
(location!.longitude != 0 || location!.latitude != 0);
((location!.longitude ?? 0) != 0 || (location!.latitude ?? 0) != 0);
}
@override

View file

@ -17,6 +17,8 @@ const pubMagicKeyEditedTime = 'editedTime';
const pubMagicKeyEditedName = 'editedName';
const pubMagicKeyCaption = "caption";
const pubMagicKeyUploaderName = "uploaderName";
const pubMagicKeyLat = "lat";
const pubMagicKeyLong = "long";
class MagicMetadata {
// 0 -> visible
@ -44,12 +46,16 @@ class PubMagicMetadata {
String? editedName;
String? caption;
String? uploaderName;
double? lat;
double? long;
PubMagicMetadata({
this.editedTime,
this.editedName,
this.caption,
this.uploaderName,
this.lat,
this.long,
});
factory PubMagicMetadata.fromEncodedJson(String encodedJson) =>
@ -65,6 +71,8 @@ class PubMagicMetadata {
editedName: map[pubMagicKeyEditedName],
caption: map[pubMagicKeyCaption],
uploaderName: map[pubMagicKeyUploaderName],
lat: map[pubMagicKeyLat],
long: map[pubMagicKeyLong],
);
}
}

View file

@ -198,19 +198,23 @@ class LocationService {
}
class GPSData {
final String latRef;
final List<double> lat;
final String longRef;
final List<double> long;
final String? latRef;
final List<double>? lat;
final String? longRef;
final List<double>? long;
GPSData(this.latRef, this.lat, this.longRef, this.long);
Location toLocationObj() {
final latSign = latRef == "N" ? 1 : -1;
final longSign = longRef == "E" ? 1 : -1;
return Location(
latitude: latSign * lat[0] + lat[1] / 60 + lat[2] / 3600,
longitude: longSign * long[0] + long[1] / 60 + long[2] / 3600,
);
Location? toLocationObj() {
if (lat == null || long == null || latRef == null || longRef == null) {
return null;
} else {
final latSign = (latRef == "N" ? 1 : -1);
final longSign = (longRef == "E" ? 1 : -1);
return Location(
latitude: latSign * lat![0] + lat![1] / 60 + lat![2] / 3600,
longitude: longSign * long![0] + long![1] / 60 + long![2] / 3600,
);
}
}
}

View file

@ -1,10 +1,12 @@
import "package:exif/exif.dart";
import "package:flutter/cupertino.dart";
import "package:flutter/material.dart";
import "package:logging/logging.dart";
import "package:photos/core/configuration.dart";
import "package:photos/models/file.dart";
import "package:photos/models/file_type.dart";
import "package:photos/models/magic_metadata.dart";
import "package:photos/services/feature_flag_service.dart";
import "package:photos/services/file_magic_service.dart";
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/buttons/icon_button_widget.dart';
import "package:photos/ui/components/divider_widget.dart";
@ -50,20 +52,23 @@ class _FileDetailsWidgetState extends State<FileDetailsWidget> {
bool _isImage = false;
late int _currentUserID;
bool showExifListTile = false;
bool hasLocationData = false;
final ValueNotifier<bool> hasLocationData = ValueNotifier(false);
final Logger _logger = Logger("_FileDetailsWidgetState");
@override
void initState() {
debugPrint('file_details_sheet initState');
_currentUserID = Configuration.instance.getUserID()!;
hasLocationData.value = widget.file.hasLocation;
_isImage = widget.file.fileType == FileType.image ||
widget.file.fileType == FileType.livePhoto;
_exifNotifier.addListener(() {
if (_exifNotifier.value != null) {
_generateExifForLocation(_exifNotifier.value!);
hasLocationData = _hasLocationData();
if (_exifNotifier.value != null && !widget.file.hasLocation) {
_updateLocationFromExif(_exifNotifier.value!).ignore();
}
});
if (_isImage) {
_exifNotifier.addListener(() {
if (_exifNotifier.value != null) {
@ -137,25 +142,23 @@ class _FileDetailsWidgetState extends State<FileDetailsWidget> {
},
),
);
if (FeatureFlagService.instance.isInternalUserOrDebugBuild()) {
fileDetailsTiles.addAll([
ValueListenableBuilder(
valueListenable: _exifNotifier,
builder: (context, _, __) {
return hasLocationData
? Column(
children: [
LocationTagsWidget(
widget.file.location!,
),
const FileDetailsDivider(),
],
)
: const SizedBox.shrink();
},
)
]);
}
fileDetailsTiles.addAll([
ValueListenableBuilder(
valueListenable: hasLocationData,
builder: (context, bool value, __) {
return value
? Column(
children: [
LocationTagsWidget(
widget.file.location!,
),
const FileDetailsDivider(),
],
)
: const SizedBox.shrink();
},
)
]);
if (_isImage) {
fileDetailsTiles.addAll([
ValueListenableBuilder(
@ -231,35 +234,30 @@ class _FileDetailsWidgetState extends State<FileDetailsWidget> {
);
}
bool _hasLocationData() {
final fileLocation = widget.file.location;
final hasLocation = (fileLocation != null &&
fileLocation.latitude != null &&
fileLocation.longitude != null) &&
(fileLocation.latitude != 0 || fileLocation.longitude != 0);
return hasLocation;
}
void _generateExifForLocation(Map<String, IfdTag> exif) {
if (exif["GPS GPSLatitude"] != null) {
_exifData["lat"] = exif["GPS GPSLatitude"]!
.values
.toList()
.map((e) => ((e as Ratio).numerator / e.denominator))
.toList();
//This code is for updating the location of files in which location data is
//missing and the EXIF has location data. This is only happens for a
//certain specific minority of devices.
Future<void> _updateLocationFromExif(Map<String, IfdTag> exif) async {
// If the file is not uploaded or the file is not owned by the current user
// then we don't need to update the location.
if (!widget.file.isUploaded || widget.file.ownerID! != _currentUserID) {
return;
}
if (exif["GPS GPSLongitude"] != null) {
_exifData["long"] = exif["GPS GPSLongitude"]!
.values
.toList()
.map((e) => ((e as Ratio).numerator / e.denominator))
.toList();
}
if (exif["GPS GPSLatitudeRef"] != null) {
_exifData["latRef"] = exif["GPS GPSLatitudeRef"].toString();
}
if (exif["GPS GPSLongitudeRef"] != null) {
_exifData["longRef"] = exif["GPS GPSLongitudeRef"].toString();
try {
final locationDataFromExif = locationFromExif(exif);
if (locationDataFromExif?.latitude != null &&
locationDataFromExif?.longitude != null) {
widget.file.location = locationDataFromExif;
await FileMagicService.instance.updatePublicMagicMetadata([
widget.file
], {
pubMagicKeyLat: locationDataFromExif!.latitude,
pubMagicKeyLong: locationDataFromExif.longitude
});
hasLocationData.value = true;
}
} catch (e, s) {
_logger.severe("Error while updating location from EXIF", e, s);
}
}

View file

@ -4,6 +4,8 @@ import 'package:exif/exif.dart';
import 'package:intl/intl.dart';
import 'package:logging/logging.dart';
import 'package:photos/models/file.dart';
import "package:photos/models/location/location.dart";
import "package:photos/services/location_service.dart";
import 'package:photos/utils/file_util.dart';
const kDateTimeOriginal = "EXIF DateTimeOriginal";
@ -30,9 +32,23 @@ Future<Map<String, IfdTag>> getExif(File file) async {
}
}
Future<DateTime?> getCreationTimeFromEXIF(io.File file) async {
Future<Map<String, IfdTag>?> getExifFromSourceFile(io.File originFile) async {
try {
final exif = await readExifFromFile(file);
final exif = await readExifFromFile(originFile);
return exif;
} catch (e, s) {
_logger.severe("failed to get exif from origin file", e, s);
return null;
}
}
Future<DateTime?> getCreationTimeFromEXIF(
io.File? file,
Map<String, IfdTag>? exifData,
) async {
try {
assert(file != null || exifData != null);
final exif = exifData ?? await readExifFromFile(file!);
final exifTime = exif.containsKey(kDateTimeOriginal)
? exif[kDateTimeOriginal]!.printable
: exif.containsKey(kImageDateTime)
@ -46,3 +62,47 @@ Future<DateTime?> getCreationTimeFromEXIF(io.File file) async {
}
return null;
}
Location? locationFromExif(Map<String, IfdTag> exif) {
try {
return _gpsDataFromExif(exif).toLocationObj();
} catch (e, s) {
_logger.severe("failed to get location from exif", e, s);
return null;
}
}
GPSData _gpsDataFromExif(Map<String, IfdTag> exif) {
final Map<String, dynamic> exifLocationData = {
"lat": null,
"long": null,
"latRef": null,
"longRef": null,
};
if (exif["GPS GPSLatitude"] != null) {
exifLocationData["lat"] = exif["GPS GPSLatitude"]!
.values
.toList()
.map((e) => ((e as Ratio).numerator / e.denominator))
.toList();
}
if (exif["GPS GPSLongitude"] != null) {
exifLocationData["long"] = exif["GPS GPSLongitude"]!
.values
.toList()
.map((e) => ((e as Ratio).numerator / e.denominator))
.toList();
}
if (exif["GPS GPSLatitudeRef"] != null) {
exifLocationData["latRef"] = exif["GPS GPSLatitudeRef"].toString();
}
if (exif["GPS GPSLongitudeRef"] != null) {
exifLocationData["longRef"] = exif["GPS GPSLongitudeRef"].toString();
}
return GPSData(
exifLocationData["latRef"],
exifLocationData["lat"],
exifLocationData["longRef"],
exifLocationData["long"],
);
}

View file

@ -114,7 +114,7 @@ Future<List<File>> convertIncomingSharedMediaToFile(
enteFile.fileType =
media.type == SharedMediaType.IMAGE ? FileType.image : FileType.video;
if (enteFile.fileType == FileType.image) {
final exifTime = await getCreationTimeFromEXIF(ioFile);
final exifTime = await getCreationTimeFromEXIF(ioFile, null);
if (exifTime != null) {
enteFile.creationTime = exifTime.microsecondsSinceEpoch;
}