diff --git a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/MetadataHandler.java b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/MetadataHandler.java index 1bab8b566..0009b7907 100644 --- a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/MetadataHandler.java +++ b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/MetadataHandler.java @@ -147,44 +147,47 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler { private void getCatalogMetadata(MethodCall call, MethodChannel.Result result) { String path = call.argument("path"); + String mimeType = call.argument("mimeType"); try (InputStream is = new FileInputStream(path)) { - Metadata metadata = ImageMetadataReader.readMetadata(is); Map metadataMap = new HashMap<>(); + if (!Constants.MIME_MP2TS.equalsIgnoreCase(mimeType)) { + Metadata metadata = ImageMetadataReader.readMetadata(is); - // EXIF Sub-IFD - ExifSubIFDDirectory exifSubDir = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class); - if (exifSubDir != null) { - if (exifSubDir.containsTag(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL)) { - metadataMap.put("dateMillis", exifSubDir.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL, null, TimeZone.getDefault()).getTime()); - } - } - - // GPS - GpsDirectory gpsDir = metadata.getFirstDirectoryOfType(GpsDirectory.class); - if (gpsDir != null) { - GeoLocation geoLocation = gpsDir.getGeoLocation(); - if (geoLocation != null) { - metadataMap.put("latitude", geoLocation.getLatitude()); - metadataMap.put("longitude", geoLocation.getLongitude()); - } - } - - // XMP - XmpDirectory xmpDir = metadata.getFirstDirectoryOfType(XmpDirectory.class); - if (xmpDir != null) { - XMPMeta xmpMeta = xmpDir.getXMPMeta(); - try { - if (xmpMeta.doesPropertyExist(XMP_DC_SCHEMA_NS, XMP_SUBJECT_PROP_NAME)) { - StringBuilder sb = new StringBuilder(); - int count = xmpMeta.countArrayItems(XMP_DC_SCHEMA_NS, XMP_SUBJECT_PROP_NAME); - for (int i = 1; i < count + 1; i++) { - XMPProperty item = xmpMeta.getArrayItem(XMP_DC_SCHEMA_NS, XMP_SUBJECT_PROP_NAME, i); - sb.append(";").append(item.getValue()); - } - metadataMap.put("xmpSubjects", sb.toString()); + // EXIF Sub-IFD + ExifSubIFDDirectory exifSubDir = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class); + if (exifSubDir != null) { + if (exifSubDir.containsTag(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL)) { + metadataMap.put("dateMillis", exifSubDir.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL, null, TimeZone.getDefault()).getTime()); + } + } + + // GPS + GpsDirectory gpsDir = metadata.getFirstDirectoryOfType(GpsDirectory.class); + if (gpsDir != null) { + GeoLocation geoLocation = gpsDir.getGeoLocation(); + if (geoLocation != null) { + metadataMap.put("latitude", geoLocation.getLatitude()); + metadataMap.put("longitude", geoLocation.getLongitude()); + } + } + + // XMP + XmpDirectory xmpDir = metadata.getFirstDirectoryOfType(XmpDirectory.class); + if (xmpDir != null) { + XMPMeta xmpMeta = xmpDir.getXMPMeta(); + try { + if (xmpMeta.doesPropertyExist(XMP_DC_SCHEMA_NS, XMP_SUBJECT_PROP_NAME)) { + StringBuilder sb = new StringBuilder(); + int count = xmpMeta.countArrayItems(XMP_DC_SCHEMA_NS, XMP_SUBJECT_PROP_NAME); + for (int i = 1; i < count + 1; i++) { + XMPProperty item = xmpMeta.getArrayItem(XMP_DC_SCHEMA_NS, XMP_SUBJECT_PROP_NAME, i); + sb.append(";").append(item.getValue()); + } + metadataMap.put("xmpSubjects", sb.toString()); + } + } catch (XMPException e) { + e.printStackTrace(); } - } catch (XMPException e) { - e.printStackTrace(); } } diff --git a/android/app/src/main/java/deckers/thibault/aves/utils/Constants.java b/android/app/src/main/java/deckers/thibault/aves/utils/Constants.java index 3b520803a..2c6cc251a 100644 --- a/android/app/src/main/java/deckers/thibault/aves/utils/Constants.java +++ b/android/app/src/main/java/deckers/thibault/aves/utils/Constants.java @@ -10,6 +10,7 @@ public class Constants { public static final String MIME_VIDEO = "video"; public static final String MIME_GIF = "image/gif"; + public static final String MIME_MP2TS = "video/mp2ts"; // video metadata keys, from android.media.MediaMetadataRetriever diff --git a/lib/main.dart b/lib/main.dart index 4e5835e0c..c9fbe9537 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'package:aves/model/image_decode_service.dart'; import 'package:aves/model/image_entry.dart'; +import 'package:aves/model/image_metadata.dart'; import 'package:aves/model/metadata_db.dart'; import 'package:aves/widgets/album/all_collection_page.dart'; import 'package:aves/widgets/common/fake_app_bar.dart'; @@ -47,10 +48,11 @@ class _HomePageState extends State { eventChannel.receiveBroadcastStream().cast().listen( (entryMap) => setState(() => entries.add(ImageEntry.fromMap(entryMap))), - onDone: () { + onDone: () async { debugPrint('mediastore stream done'); setState(() {}); - catalogEntries(); + await catalogEntries(); + await locateEntries(); }, onError: (error) => debugPrint('mediastore stream error=$error'), ); @@ -68,20 +70,36 @@ class _HomePageState extends State { } catalogEntries() async { - debugPrint('$runtimeType catalogEntries cataloging start'); - await Future.forEach(entries, (entry) async { + debugPrint('$runtimeType catalogEntries start'); + final start = DateTime.now(); + final uncataloguedEntries = entries.where((entry) => !entry.isCataloged); + final newMetadata = List(); + await Future.forEach(uncataloguedEntries, (entry) async { await entry.catalog(); + newMetadata.add(entry.catalogMetadata); }); - debugPrint('$runtimeType catalogEntries cataloging complete'); + debugPrint('$runtimeType catalogEntries complete in ${DateTime.now().difference(start).inSeconds}s with ${newMetadata.length} new entries'); // sort with more accurate date entries.sort((a, b) => b.bestDate.compareTo(a.bestDate)); setState(() {}); - debugPrint('$runtimeType catalogEntries locating start'); - await Future.forEach(entries, (entry) async { + metadataDb.saveMetadata(List.unmodifiable(newMetadata)); + } + + locateEntries() async { + debugPrint('$runtimeType locateEntries start'); + final start = DateTime.now(); + final unlocatedEntries = entries.where((entry) => !entry.isLocated); + final newAddresses = List(); + await Future.forEach(unlocatedEntries, (entry) async { await entry.locate(); + newAddresses.add(entry.addressDetails); + if (newAddresses.length >= 50) { + metadataDb.saveAddresses(List.unmodifiable(newAddresses)); + newAddresses.clear(); + } }); - debugPrint('$runtimeType catalogEntries locating done'); + debugPrint('$runtimeType locateEntries complete in ${DateTime.now().difference(start).inSeconds}s with ${newAddresses.length} new addresses'); } } diff --git a/lib/model/metadata_db.dart b/lib/model/metadata_db.dart index 4b853d951..8faeee0f8 100644 --- a/lib/model/metadata_db.dart +++ b/lib/model/metadata_db.dart @@ -51,14 +51,18 @@ class MetadataDb { return null; } - insertMetadata(CatalogMetadata metadata) async { -// debugPrint('$runtimeType insertMetadata metadata=$metadata'); + saveMetadata(Iterable metadataEntries) async { + if (metadataEntries == null || metadataEntries.isEmpty) return; + final start = DateTime.now(); final db = await _database; - await db.insert( - metadataTable, - metadata.toMap(), - conflictAlgorithm: ConflictAlgorithm.replace, - ); + final batch = db.batch(); + metadataEntries.where((metadata) => metadata != null).forEach((metadata) => batch.insert( + metadataTable, + metadata.toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, + )); + await batch.commit(noResult: true); + debugPrint('$runtimeType saveMetadata complete in ${DateTime.now().difference(start).inMilliseconds}ms with ${metadataEntries.length} entries'); } Future> getAllAddresses() async { @@ -78,13 +82,17 @@ class MetadataDb { return null; } - insertAddress(AddressDetails metadata) async { -// debugPrint('$runtimeType insertAddress metadata=$metadata'); + saveAddresses(Iterable addresses) async { + if (addresses == null || addresses.isEmpty) return; + final start = DateTime.now(); final db = await _database; - await db.insert( - addressTable, - metadata.toMap(), - conflictAlgorithm: ConflictAlgorithm.replace, - ); + final batch = db.batch(); + addresses.where((address) => address != null).forEach((address) => batch.insert( + addressTable, + address.toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, + )); + await batch.commit(noResult: true); + debugPrint('$runtimeType saveAddresses complete in ${DateTime.now().difference(start).inMilliseconds}ms with ${addresses.length} entries'); } } diff --git a/lib/model/metadata_service.dart b/lib/model/metadata_service.dart index e683ebfba..bf145c173 100644 --- a/lib/model/metadata_service.dart +++ b/lib/model/metadata_service.dart @@ -1,6 +1,5 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_metadata.dart'; -import 'package:aves/model/metadata_db.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -22,7 +21,6 @@ class MetadataService { } static Future getCatalogMetadata(ImageEntry entry) async { - CatalogMetadata metadata; try { // return map with: // 'dateMillis': date taken in milliseconds since Epoch (long) @@ -34,9 +32,7 @@ class MetadataService { 'path': entry.path, }) as Map; result['contentId'] = entry.contentId; - metadata = CatalogMetadata.fromMap(result); - metadataDb.insertMetadata(metadata); - return metadata; + return CatalogMetadata.fromMap(result); } on PlatformException catch (e) { debugPrint('getCatalogMetadata failed with exception=${e.message}'); }