From 1b0d481b457fb42972d4dbadc4862151fc027a39 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 7 Feb 2025 12:26:52 +0530 Subject: [PATCH 1/9] [mob] Fix creationTime parsing --- .../viewer/file_details/creation_time_item_widget.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/mobile/lib/ui/viewer/file_details/creation_time_item_widget.dart b/mobile/lib/ui/viewer/file_details/creation_time_item_widget.dart index 90b6ab9d58..13c8800fa8 100644 --- a/mobile/lib/ui/viewer/file_details/creation_time_item_widget.dart +++ b/mobile/lib/ui/viewer/file_details/creation_time_item_widget.dart @@ -21,15 +21,15 @@ class CreationTimeItem extends StatefulWidget { class _CreationTimeItemState extends State { @override Widget build(BuildContext context) { - final dateTime = - DateTime.fromMicrosecondsSinceEpoch(widget.file.creationTime!); + final dateTime = DateTime.fromMicrosecondsSinceEpoch( + widget.file.creationTime!, + isUtc: true, + ).toLocal(); return InfoItemWidget( key: const ValueKey("Creation time"), leadingIcon: Icons.calendar_today_outlined, title: DateFormat.yMMMEd(Localizations.localeOf(context).languageCode) - .format( - DateTime.fromMicrosecondsSinceEpoch(widget.file.creationTime!), - ), + .format(dateTime), subtitleSection: Future.value([ Text( getTimeIn12hrFormat(dateTime) + " " + dateTime.timeZoneName, From be7cbc2ba0acae688067978c9b42911cac51c85d Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 7 Feb 2025 12:35:15 +0530 Subject: [PATCH 2/9] [mob] Fix handling of timezone --- mobile/lib/utils/exif_util.dart | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/mobile/lib/utils/exif_util.dart b/mobile/lib/utils/exif_util.dart index 652336ebee..45741f07b6 100644 --- a/mobile/lib/utils/exif_util.dart +++ b/mobile/lib/utils/exif_util.dart @@ -154,24 +154,22 @@ Future getCreationTimeFromEXIF( } DateTime getDateTimeInDeviceTimezone(String exifTime, String? offsetString) { - final DateTime result = DateFormat(kExifDateTimePattern).parse(exifTime); + final shouldParseDateInUTCTimeZone = offsetString != null; + final DateTime result = DateFormat(kExifDateTimePattern) + .parse(exifTime, shouldParseDateInUTCTimeZone); if (offsetString == null) { return result; } try { final List splitHHMM = offsetString.split(":"); - // Parse the offset from the photo's time zone final int offsetHours = int.parse(splitHHMM[0]); final int offsetMinutes = int.parse(splitHHMM[1]) * (offsetHours.isNegative ? -1 : 1); // Adjust the date for the offset to get the photo's correct UTC time final photoUtcDate = result.add(Duration(hours: -offsetHours, minutes: -offsetMinutes)); - // Getting the current device's time zone offset from UTC - final now = DateTime.now(); - final localOffset = now.timeZoneOffset; - // Adjusting the photo's UTC time to the device's local time - final deviceLocalTime = photoUtcDate.add(localOffset); + // Convert the UTC time to the device's local time + final deviceLocalTime = photoUtcDate.toLocal(); return deviceLocalTime; } catch (e, s) { _logger.severe("tz offset adjust failed $offsetString", e, s); From ff72dae40808b9d59721836843417fb9808ff8a1 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 7 Feb 2025 13:54:44 +0530 Subject: [PATCH 3/9] [mob] Refactor --- mobile/lib/models/file/file.dart | 6 ++-- mobile/lib/utils/exif_util.dart | 55 +++++++++++++++++++++----------- mobile/lib/utils/share_util.dart | 6 ++-- 3 files changed, 42 insertions(+), 25 deletions(-) diff --git a/mobile/lib/models/file/file.dart b/mobile/lib/models/file/file.dart index 937bee0cb1..9f3d561315 100644 --- a/mobile/lib/models/file/file.dart +++ b/mobile/lib/models/file/file.dart @@ -175,10 +175,10 @@ class EnteFile { final exifData = await getExifFromSourceFile(mediaUploadData.sourceFile!); if (exifData != null) { if (fileType == FileType.image) { - final exifTime = await getCreationTimeFromEXIF(null, exifData); - if (exifTime != null) { + final dateResult = await tryParseExifDateTime(null, exifData); + if (dateResult != null && dateResult.time != null) { hasExifTime = true; - creationTime = exifTime.microsecondsSinceEpoch; + creationTime = dateResult.time!.microsecondsSinceEpoch; } mediaUploadData.isPanorama = checkPanoramaFromEXIF(null, exifData); diff --git a/mobile/lib/utils/exif_util.dart b/mobile/lib/utils/exif_util.dart index 45741f07b6..940ffed644 100644 --- a/mobile/lib/utils/exif_util.dart +++ b/mobile/lib/utils/exif_util.dart @@ -125,7 +125,14 @@ bool? checkPanoramaFromEXIF(File? file, Map? exifData) { return element?.printable == "6"; } -Future getCreationTimeFromEXIF( +class ParsedExifDateTime { + final DateTime? time; + final String? dateTime; + final String? offsetTime; + ParsedExifDateTime(this.time, this.dateTime, this.offsetTime); +} + +Future tryParseExifDateTime( File? file, Map? exifData, ) async { @@ -153,28 +160,38 @@ Future getCreationTimeFromEXIF( return null; } -DateTime getDateTimeInDeviceTimezone(String exifTime, String? offsetString) { +ParsedExifDateTime getDateTimeInDeviceTimezone( + String exifTime, + String? offsetString, +) { final shouldParseDateInUTCTimeZone = offsetString != null; final DateTime result = DateFormat(kExifDateTimePattern) .parse(exifTime, shouldParseDateInUTCTimeZone); - if (offsetString == null) { - return result; + if (offsetString != null && (offsetString ?? '').toUpperCase() != "Z") { + try { + final List splitHHMM = offsetString.split(":"); + final int offsetHours = int.parse(splitHHMM[0]); + final int offsetMinutes = + int.parse(splitHHMM[1]) * (offsetHours.isNegative ? -1 : 1); + // Adjust the date for the offset to get the photo's correct UTC time + final photoUtcDate = + result.add(Duration(hours: -offsetHours, minutes: -offsetMinutes)); + // Convert the UTC time to the device's local time + final deviceLocalTime = photoUtcDate.toLocal(); + return ParsedExifDateTime( + deviceLocalTime, + result.toIso8601String(), + offsetString, + ); + } catch (e, s) { + _logger.severe("tz offset adjust failed $offsetString", e, s); + } } - try { - final List splitHHMM = offsetString.split(":"); - final int offsetHours = int.parse(splitHHMM[0]); - final int offsetMinutes = - int.parse(splitHHMM[1]) * (offsetHours.isNegative ? -1 : 1); - // Adjust the date for the offset to get the photo's correct UTC time - final photoUtcDate = - result.add(Duration(hours: -offsetHours, minutes: -offsetMinutes)); - // Convert the UTC time to the device's local time - final deviceLocalTime = photoUtcDate.toLocal(); - return deviceLocalTime; - } catch (e, s) { - _logger.severe("tz offset adjust failed $offsetString", e, s); - } - return result; + return ParsedExifDateTime( + result, + result.toIso8601String(), + (offsetString ?? '').toUpperCase() == 'Z' ? 'Z' : null, + ); } Location? locationFromExif(Map exif) { diff --git a/mobile/lib/utils/share_util.dart b/mobile/lib/utils/share_util.dart index c39d1733b9..3ec147798d 100644 --- a/mobile/lib/utils/share_util.dart +++ b/mobile/lib/utils/share_util.dart @@ -165,9 +165,9 @@ Future> convertIncomingSharedMediaToFile( enteFile.fileType = media.type == SharedMediaType.image ? FileType.image : FileType.video; if (enteFile.fileType == FileType.image) { - final exifTime = await getCreationTimeFromEXIF(ioFile, null); - if (exifTime != null) { - enteFile.creationTime = exifTime.microsecondsSinceEpoch; + final dateResult = await tryParseExifDateTime(ioFile, null); + if (dateResult != null && dateResult.time != null) { + enteFile.creationTime = dateResult.time!.microsecondsSinceEpoch; } } else if (enteFile.fileType == FileType.video) { enteFile.duration = (media.duration ?? 0) ~/ 1000; From 0380a30705a59659a1ec82e23b8f70a4b3544170 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 7 Feb 2025 14:36:16 +0530 Subject: [PATCH 4/9] [mob] Refactor --- mobile/lib/utils/exif_util.dart | 48 ++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/mobile/lib/utils/exif_util.dart b/mobile/lib/utils/exif_util.dart index 940ffed644..beee7f61ef 100644 --- a/mobile/lib/utils/exif_util.dart +++ b/mobile/lib/utils/exif_util.dart @@ -126,10 +126,21 @@ bool? checkPanoramaFromEXIF(File? file, Map? exifData) { } class ParsedExifDateTime { - final DateTime? time; - final String? dateTime; - final String? offsetTime; - ParsedExifDateTime(this.time, this.dateTime, this.offsetTime); + late final DateTime? time; + late final String? dateTime; + late final String? offsetTime; + ParsedExifDateTime(DateTime this.time, String? dateTime, this.offsetTime) { + if (dateTime != null && dateTime.endsWith('Z')) { + this.dateTime = dateTime.substring(0, dateTime.length - 1); + } else { + this.dateTime = dateTime; + } + } + + @override + String toString() { + return "ParsedExifDateTime{time: $time, dateTime: $dateTime, offsetTime: $offsetTime}"; + } } Future tryParseExifDateTime( @@ -144,16 +155,17 @@ Future tryParseExifDateTime( : exif.containsKey(kImageDateTime) ? exif[kImageDateTime]!.printable : null; - if (exifTime != null && exifTime != kEmptyExifDateTime) { - String? exifOffsetTime; - for (final key in kExifOffSetKeys) { - if (exif.containsKey(key)) { - exifOffsetTime = exif[key]!.printable; - break; - } - } - return getDateTimeInDeviceTimezone(exifTime, exifOffsetTime); + if (exifTime == null || exifTime == kEmptyExifDateTime) { + return null; } + String? exifOffsetTime; + for (final key in kExifOffSetKeys) { + if (exif.containsKey(key)) { + exifOffsetTime = exif[key]!.printable; + break; + } + } + return getDateTimeInDeviceTimezone(exifTime, exifOffsetTime); } catch (e) { _logger.severe("failed to getCreationTimeFromEXIF", e); } @@ -164,10 +176,10 @@ ParsedExifDateTime getDateTimeInDeviceTimezone( String exifTime, String? offsetString, ) { - final shouldParseDateInUTCTimeZone = offsetString != null; - final DateTime result = DateFormat(kExifDateTimePattern) - .parse(exifTime, shouldParseDateInUTCTimeZone); - if (offsetString != null && (offsetString ?? '').toUpperCase() != "Z") { + final hasOffset = (offsetString ?? '') != ''; + final DateTime result = + DateFormat(kExifDateTimePattern).parse(exifTime, hasOffset); + if (hasOffset && offsetString!.toUpperCase() != "Z") { try { final List splitHHMM = offsetString.split(":"); final int offsetHours = int.parse(splitHHMM[0]); @@ -184,7 +196,7 @@ ParsedExifDateTime getDateTimeInDeviceTimezone( offsetString, ); } catch (e, s) { - _logger.severe("tz offset adjust failed $offsetString", e, s); + _logger.severe("offset parsing failed $exifTime && $offsetString", e, s); } } return ParsedExifDateTime( From 0b7b4b72f36d31f589b8984db453a6a352713a3d Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 7 Feb 2025 14:59:52 +0530 Subject: [PATCH 5/9] [mob] Add support for parsing dateTime & offsetTime from pubMagicMetadata --- mobile/lib/models/metadata/file_magic.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mobile/lib/models/metadata/file_magic.dart b/mobile/lib/models/metadata/file_magic.dart index 7599b7c82f..02f6188a9d 100644 --- a/mobile/lib/models/metadata/file_magic.dart +++ b/mobile/lib/models/metadata/file_magic.dart @@ -14,6 +14,8 @@ const latKey = "lat"; const longKey = "long"; const motionVideoIndexKey = "mvi"; const noThumbKey = "noThumb"; +const dateTimeKey = 'dateTime'; +const offsetTimeKey = 'offsetTime'; class MagicMetadata { // 0 -> visible @@ -46,6 +48,11 @@ class PubMagicMetadata { double? lat; double? long; + // ISO 8601 datetime without timezone. This contains the date and time of the photo in the original tz + // where the photo was taken. + String? dateTime; + String? offsetTime; + // Motion Video Index. Positive value (>0) indicates that the file is a motion // photo int? mvi; @@ -74,6 +81,8 @@ class PubMagicMetadata { this.mvi, this.noThumb, this.mediaType, + this.dateTime, + this.offsetTime, }); factory PubMagicMetadata.fromEncodedJson(String encodedJson) => @@ -96,6 +105,8 @@ class PubMagicMetadata { mvi: map[motionVideoIndexKey], noThumb: map[noThumbKey], mediaType: map[mediaTypeKey], + dateTime: map[dateTimeKey], + offsetTime: map[offsetTimeKey], ); } From f3feb4cddacdbee5b1fd6553fd2f9a048b6eb6ba Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 7 Feb 2025 15:38:49 +0530 Subject: [PATCH 6/9] [mob] Parse exif as part of MediaUploadData --- mobile/lib/models/file/file.dart | 2 +- mobile/lib/utils/exif_util.dart | 2 +- mobile/lib/utils/file_uploader.dart | 2 +- mobile/lib/utils/file_uploader_util.dart | 28 +++++++++++++++++++----- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/mobile/lib/models/file/file.dart b/mobile/lib/models/file/file.dart index 9f3d561315..44572632e1 100644 --- a/mobile/lib/models/file/file.dart +++ b/mobile/lib/models/file/file.dart @@ -172,7 +172,7 @@ class EnteFile { bool hasExifTime = false; if ((fileType == FileType.image || fileType == FileType.video) && mediaUploadData.sourceFile != null) { - final exifData = await getExifFromSourceFile(mediaUploadData.sourceFile!); + final exifData = await tryExifFromFile(mediaUploadData.sourceFile!); if (exifData != null) { if (fileType == FileType.image) { final dateResult = await tryParseExifDateTime(null, exifData); diff --git a/mobile/lib/utils/exif_util.dart b/mobile/lib/utils/exif_util.dart index beee7f61ef..a94e02bece 100644 --- a/mobile/lib/utils/exif_util.dart +++ b/mobile/lib/utils/exif_util.dart @@ -47,7 +47,7 @@ Future> getExif(EnteFile file) async { } } -Future?> getExifFromSourceFile(File originFile) async { +Future?> tryExifFromFile(File originFile) async { try { final exif = await readExifAsync(originFile); return exif; diff --git a/mobile/lib/utils/file_uploader.dart b/mobile/lib/utils/file_uploader.dart index 9290772910..5d47f159af 100644 --- a/mobile/lib/utils/file_uploader.dart +++ b/mobile/lib/utils/file_uploader.dart @@ -536,7 +536,7 @@ class FileUploader { MediaUploadData? mediaUploadData; try { - mediaUploadData = await getUploadDataFromEnteFile(file); + mediaUploadData = await getUploadDataFromEnteFile(file, parseExif: true); } catch (e) { // This additional try catch block is added because for resumable upload, // we need to compute the hash before the next step. Previously, this diff --git a/mobile/lib/utils/file_uploader_util.dart b/mobile/lib/utils/file_uploader_util.dart index 6ad6622f5f..63764a3066 100644 --- a/mobile/lib/utils/file_uploader_util.dart +++ b/mobile/lib/utils/file_uploader_util.dart @@ -6,6 +6,7 @@ import 'dart:ui' as ui; import "package:archive/archive_io.dart"; import "package:computer/computer.dart"; +import "package:exif/exif.dart"; import 'package:logging/logging.dart'; import "package:motion_photos/motion_photos.dart"; import 'package:motionphoto/motionphoto.dart'; @@ -44,6 +45,8 @@ class MediaUploadData { // For iOS, this value will be always null. final int? motionPhotoStartIndex; + final Map? exifData; + bool? isPanorama; MediaUploadData( @@ -55,6 +58,7 @@ class MediaUploadData { this.width, this.motionPhotoStartIndex, this.isPanorama, + this.exifData, }); } @@ -69,20 +73,27 @@ class FileHashData { FileHashData(this.fileHash, {this.zipHash}); } -Future getUploadDataFromEnteFile(EnteFile file) async { +Future getUploadDataFromEnteFile( + EnteFile file, { + bool parseExif = false, +}) async { if (file.isSharedMediaToAppSandbox) { - return await _getMediaUploadDataFromAppCache(file); + return await _getMediaUploadDataFromAppCache(file, parseExif); } else { - return await _getMediaUploadDataFromAssetFile(file); + return await _getMediaUploadDataFromAssetFile(file, parseExif); } } -Future _getMediaUploadDataFromAssetFile(EnteFile file) async { +Future _getMediaUploadDataFromAssetFile( + EnteFile file, + bool parseExif, +) async { File? sourceFile; Uint8List? thumbnailData; bool isDeleted; String? zipHash; String fileHash; + Map? exifData; // The timeouts are to safeguard against https://github.com/CaiJingLong/flutter_photo_manager/issues/467 final asset = await file.getAsset @@ -115,6 +126,9 @@ Future _getMediaUploadDataFromAssetFile(EnteFile file) async { InvalidReason.sourceFileMissing, ); } + if (parseExif) { + exifData = await tryExifFromFile(sourceFile); + } // h4ck to fetch location data if missing (thank you Android Q+) lazily only during uploads await _decorateEnteFileData(file, asset, sourceFile); fileHash = CryptoUtil.bin2base64(await CryptoUtil.getHash(sourceFile)); @@ -177,6 +191,7 @@ Future _getMediaUploadDataFromAssetFile(EnteFile file) async { height: h, width: w, motionPhotoStartIndex: motionPhotoStartingIndex, + exifData: exifData, ); } @@ -330,9 +345,11 @@ Future getPubMetadataRequest( ); } -Future _getMediaUploadDataFromAppCache(EnteFile file) async { +Future _getMediaUploadDataFromAppCache( + EnteFile file, bool parseExif) async { File sourceFile; Uint8List? thumbnailData; + Map? exifData; const bool isDeleted = false; final localPath = getSharedMediaFilePath(file); sourceFile = File(localPath); @@ -350,6 +367,7 @@ Future _getMediaUploadDataFromAppCache(EnteFile file) async { Map? dimensions; if (file.fileType == FileType.image) { dimensions = await getImageHeightAndWith(imagePath: localPath); + exifData = await tryExifFromFile(sourceFile); } else if (thumbnailData != null) { // the thumbnail null check is to ensure that we are able to generate thum // for video, we need to use the thumbnail data with any max width/height From 9f1b4fc23c10943f71e4d8b73af176227d5a7c57 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 7 Feb 2025 16:09:57 +0530 Subject: [PATCH 7/9] [mob] Refactor --- mobile/lib/models/file/file.dart | 47 +++++++++--------------- mobile/lib/utils/file_uploader.dart | 8 +++- mobile/lib/utils/file_uploader_util.dart | 10 ++++- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/mobile/lib/models/file/file.dart b/mobile/lib/models/file/file.dart index 44572632e1..705d740e82 100644 --- a/mobile/lib/models/file/file.dart +++ b/mobile/lib/models/file/file.dart @@ -160,6 +160,7 @@ class EnteFile { Future> getMetadataForUpload( MediaUploadData mediaUploadData, + ParsedExifDateTime? exifTime, ) async { final asset = await getAsset; // asset can be null for files shared to app @@ -170,36 +171,24 @@ class EnteFile { } } bool hasExifTime = false; - if ((fileType == FileType.image || fileType == FileType.video) && - mediaUploadData.sourceFile != null) { - final exifData = await tryExifFromFile(mediaUploadData.sourceFile!); - if (exifData != null) { - if (fileType == FileType.image) { - final dateResult = await tryParseExifDateTime(null, exifData); - if (dateResult != null && dateResult.time != null) { - hasExifTime = true; - creationTime = dateResult.time!.microsecondsSinceEpoch; - } - mediaUploadData.isPanorama = checkPanoramaFromEXIF(null, exifData); - - if (mediaUploadData.isPanorama != true) { - try { - final xmpData = await getXmp(mediaUploadData.sourceFile!); - mediaUploadData.isPanorama = checkPanoramaFromXMP(xmpData); - } catch (_) {} - - mediaUploadData.isPanorama ??= false; - } - } - if (Platform.isAndroid) { - //Fix for missing location data in lower android versions. - final Location? exifLocation = locationFromExif(exifData); - if (Location.isValidLocation(exifLocation)) { - location = exifLocation; - } - } - } + if (exifTime != null && exifTime.time != null) { + hasExifTime = true; + creationTime = exifTime.time!.microsecondsSinceEpoch; } + if (mediaUploadData.exifData != null) { + mediaUploadData.isPanorama = + checkPanoramaFromEXIF(null, mediaUploadData.exifData); + } + if (mediaUploadData.isPanorama != true && + fileType == FileType.image && + mediaUploadData.sourceFile != null) { + try { + final xmpData = await getXmp(mediaUploadData.sourceFile!); + mediaUploadData.isPanorama = checkPanoramaFromXMP(xmpData); + } catch (_) {} + mediaUploadData.isPanorama ??= false; + } + // Try to get the timestamp from fileName. In case of iOS, file names are // generic IMG_XXXX, so only parse it on Android devices if (!hasExifTime && Platform.isAndroid && title != null) { diff --git a/mobile/lib/utils/file_uploader.dart b/mobile/lib/utils/file_uploader.dart index 5d47f159af..8f6dbe2e1b 100644 --- a/mobile/lib/utils/file_uploader.dart +++ b/mobile/lib/utils/file_uploader.dart @@ -41,6 +41,7 @@ import 'package:photos/services/sync_service.dart'; import "package:photos/services/user_service.dart"; import 'package:photos/utils/crypto_util.dart'; import 'package:photos/utils/data_util.dart'; +import "package:photos/utils/exif_util.dart"; import "package:photos/utils/file_key.dart"; import 'package:photos/utils/file_uploader_util.dart'; import "package:photos/utils/file_util.dart"; @@ -728,8 +729,13 @@ class FileUploader { encThumbSize, ); } + final ParsedExifDateTime? exifTime = await tryParseExifDateTime( + null, + mediaUploadData.exifData, + ); + final metadata = + await file.getMetadataForUpload(mediaUploadData, exifTime); - final metadata = await file.getMetadataForUpload(mediaUploadData); final encryptedMetadataResult = await CryptoUtil.encryptChaCha( utf8.encode(jsonEncode(metadata)), fileAttributes.key!, diff --git a/mobile/lib/utils/file_uploader_util.dart b/mobile/lib/utils/file_uploader_util.dart index 63764a3066..4c9abab322 100644 --- a/mobile/lib/utils/file_uploader_util.dart +++ b/mobile/lib/utils/file_uploader_util.dart @@ -130,7 +130,7 @@ Future _getMediaUploadDataFromAssetFile( exifData = await tryExifFromFile(sourceFile); } // h4ck to fetch location data if missing (thank you Android Q+) lazily only during uploads - await _decorateEnteFileData(file, asset, sourceFile); + await _decorateEnteFileData(file, asset, sourceFile, exifData); fileHash = CryptoUtil.bin2base64(await CryptoUtil.getHash(sourceFile)); if (file.fileType == FileType.livePhoto && Platform.isIOS) { @@ -299,6 +299,7 @@ Future _decorateEnteFileData( EnteFile file, AssetEntity asset, File sourceFile, + Map? exifData, ) async { // h4ck to fetch location data if missing (thank you Android Q+) lazily only during uploads if (file.location == null || @@ -313,6 +314,13 @@ Future _decorateEnteFileData( file.location = props.location; } } + if (Platform.isAndroid && exifData != null) { + //Fix for missing location data in lower android versions. + final Location? exifLocation = locationFromExif(exifData); + if (Location.isValidLocation(exifLocation)) { + file.location = exifLocation; + } + } if (file.title == null || file.title!.isEmpty) { _logger.warning("Title was missing ${file.tag}"); file.title = await asset.titleAsync; From bf89a0ca9e5df7350b99ac6af74015519f02f6a7 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 7 Feb 2025 16:32:02 +0530 Subject: [PATCH 8/9] [mob] Fill dateTime and offsetTime during upload --- mobile/lib/utils/file_uploader.dart | 45 +++++++++++++++++++---------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/mobile/lib/utils/file_uploader.dart b/mobile/lib/utils/file_uploader.dart index 8f6dbe2e1b..e36779ead1 100644 --- a/mobile/lib/utils/file_uploader.dart +++ b/mobile/lib/utils/file_uploader.dart @@ -777,22 +777,9 @@ class FileUploader { CryptoUtil.bin2base64(encryptedFileKeyData.encryptedData!); final keyDecryptionNonce = CryptoUtil.bin2base64(encryptedFileKeyData.nonce!); - final Map pubMetadata = {}; + final Map pubMetadata = + _buildPublicMagicData(mediaUploadData, exifTime); MetadataRequest? pubMetadataRequest; - if ((mediaUploadData.height ?? 0) != 0 && - (mediaUploadData.width ?? 0) != 0) { - pubMetadata[heightKey] = mediaUploadData.height; - pubMetadata[widthKey] = mediaUploadData.width; - pubMetadata[mediaTypeKey] = - mediaUploadData.isPanorama == true ? 1 : 0; - } - if (mediaUploadData.motionPhotoStartIndex != null) { - pubMetadata[motionVideoIndexKey] = - mediaUploadData.motionPhotoStartIndex; - } - if (mediaUploadData.thumbnail == null) { - pubMetadata[noThumbKey] = true; - } if (pubMetadata.isNotEmpty) { pubMetadataRequest = await getPubMetadataRequest( file, @@ -874,6 +861,34 @@ class FileUploader { } } + Map _buildPublicMagicData( + MediaUploadData mediaUploadData, + ParsedExifDateTime? exifTime, + ) { + final Map pubMetadata = {}; + if ((mediaUploadData.height ?? 0) != 0 && + (mediaUploadData.width ?? 0) != 0) { + pubMetadata[heightKey] = mediaUploadData.height; + pubMetadata[widthKey] = mediaUploadData.width; + pubMetadata[mediaTypeKey] = mediaUploadData.isPanorama == true ? 1 : 0; + } + if (mediaUploadData.motionPhotoStartIndex != null) { + pubMetadata[motionVideoIndexKey] = mediaUploadData.motionPhotoStartIndex; + } + if (mediaUploadData.thumbnail == null) { + pubMetadata[noThumbKey] = true; + } + if (exifTime != null) { + if (exifTime.dateTime != null) { + pubMetadata[dateTimeKey] = exifTime.dateTime; + } + if (exifTime.offsetTime != null) { + pubMetadata[offsetTimeKey] = exifTime.offsetTime; + } + } + return pubMetadata; + } + bool isPutOrUpdateFileError(Object e) { if (e is DioException) { return e.requestOptions.path.contains("/files") || From d7ee9615b7b01bd0a3615d11737860d4ebc71f7e Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Mon, 10 Feb 2025 11:34:45 +0530 Subject: [PATCH 9/9] [mob] Fix lint & missing exif for files shared to ente --- mobile/lib/utils/file_uploader_util.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mobile/lib/utils/file_uploader_util.dart b/mobile/lib/utils/file_uploader_util.dart index 4c9abab322..6f37236b8e 100644 --- a/mobile/lib/utils/file_uploader_util.dart +++ b/mobile/lib/utils/file_uploader_util.dart @@ -354,7 +354,9 @@ Future getPubMetadataRequest( } Future _getMediaUploadDataFromAppCache( - EnteFile file, bool parseExif) async { + EnteFile file, + bool parseExif, +) async { File sourceFile; Uint8List? thumbnailData; Map? exifData; @@ -394,6 +396,7 @@ Future _getMediaUploadDataFromAppCache( FileHashData(fileHash), height: dimensions?['height'], width: dimensions?['width'], + exifData: exifData, ); } catch (e, s) { _logger.warning("failed to generate thumbnail", e, s);