diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt index b5f9197cd..ce6ea20ab 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataHandler.kt @@ -44,6 +44,7 @@ import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeInt import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeRational import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeString import deckers.thibault.aves.metadata.XMP +import deckers.thibault.aves.metadata.XMP.getSafeDateMillis import deckers.thibault.aves.metadata.XMP.getSafeLocalizedText import deckers.thibault.aves.utils.BitmapUtils import deckers.thibault.aves.utils.BitmapUtils.getBytes @@ -205,6 +206,13 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { result.success(metadataMap) } + // set `KEY_DATE_MILLIS` from these fields (by precedence): + // - Exif / DATETIME_ORIGINAL + // - Exif / DATETIME + // - XMP / xmp:CreateDate + // set `KEY_XMP_TITLE_DESCRIPTION` from these fields (by precedence): + // - XMP / dc:title + // - XMP / dc:description private fun getCatalogMetadataByMetadataExtractor(uri: Uri, mimeType: String, path: String?): Map { val metadataMap = HashMap() @@ -270,9 +278,12 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { val values = (1 until count + 1).map { xmpMeta.getArrayItem(XMP.DC_SCHEMA_NS, XMP.SUBJECT_PROP_NAME, it).value } metadataMap[KEY_XMP_SUBJECTS] = values.joinToString(separator = ";") } - xmpMeta.getSafeLocalizedText(XMP.TITLE_PROP_NAME) { metadataMap[KEY_XMP_TITLE_DESCRIPTION] = it } + xmpMeta.getSafeLocalizedText(XMP.DC_SCHEMA_NS, XMP.TITLE_PROP_NAME) { metadataMap[KEY_XMP_TITLE_DESCRIPTION] = it } if (!metadataMap.containsKey(KEY_XMP_TITLE_DESCRIPTION)) { - xmpMeta.getSafeLocalizedText(XMP.DESCRIPTION_PROP_NAME) { metadataMap[KEY_XMP_TITLE_DESCRIPTION] = it } + xmpMeta.getSafeLocalizedText(XMP.DC_SCHEMA_NS, XMP.DESCRIPTION_PROP_NAME) { metadataMap[KEY_XMP_TITLE_DESCRIPTION] = it } + } + if (!metadataMap.containsKey(KEY_DATE_MILLIS)) { + xmpMeta.getSafeDateMillis(XMP.XMP_SCHEMA_NS, XMP.CREATE_DATE_PROP_NAME) { metadataMap[KEY_DATE_MILLIS] = it } } } catch (e: XMPException) { Log.w(LOG_TAG, "failed to read XMP directory for uri=$uri", e) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt index dcf422d27..036ac6b18 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Metadata.kt @@ -41,6 +41,8 @@ object Metadata { } } + // not sure which standards are used for the different video formats, + // but looks like some form of ISO 8601 `basic format`: // yyyyMMddTHHmmss(.sss)?(Z|+/-hhmm)? fun parseVideoMetadataDate(metadataDate: String?): Long { var dateString = metadataDate ?: return 0 diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt index e68d08f22..40ed49a7c 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/XMP.kt @@ -4,6 +4,7 @@ import android.util.Log import com.adobe.internal.xmp.XMPException import com.adobe.internal.xmp.XMPMeta import deckers.thibault.aves.utils.LogUtils +import java.util.* object XMP { private val LOG_TAG = LogUtils.createTag(XMP::class.java) @@ -14,20 +15,38 @@ object XMP { const val SUBJECT_PROP_NAME = "dc:subject" const val TITLE_PROP_NAME = "dc:title" const val DESCRIPTION_PROP_NAME = "dc:description" + const val CREATE_DATE_PROP_NAME = "xmp:CreateDate" const val THUMBNAIL_PROP_NAME = "xmp:Thumbnails" const val THUMBNAIL_IMAGE_PROP_NAME = "xmpGImg:image" private const val GENERIC_LANG = "" private const val SPECIFIC_LANG = "en-US" - fun XMPMeta.getSafeLocalizedText(propName: String, save: (value: String) -> Unit) { + fun XMPMeta.getSafeLocalizedText(schema: String, propName: String, save: (value: String) -> Unit) { try { - if (this.doesPropertyExist(DC_SCHEMA_NS, propName)) { - val item = this.getLocalizedText(DC_SCHEMA_NS, propName, GENERIC_LANG, SPECIFIC_LANG) + if (this.doesPropertyExist(schema, propName)) { + val item = this.getLocalizedText(schema, propName, GENERIC_LANG, SPECIFIC_LANG) // double check retrieved items as the property sometimes is reported to exist but it is actually null if (item != null) save(item.value) } } catch (e: XMPException) { - Log.w(LOG_TAG, "failed to get text for XMP propName=$propName", e) + Log.w(LOG_TAG, "failed to get text for XMP schema=$schema, propName=$propName", e) + } + } + + fun XMPMeta.getSafeDateMillis(schema: String, propName: String, save: (value: Long) -> Unit) { + try { + if (this.doesPropertyExist(schema, propName)) { + val item = this.getPropertyDate(schema, propName) + // double check retrieved items as the property sometimes is reported to exist but it is actually null + if (item != null) { + // strip time zone from XMP dates so that we show date/times as local ones + // this aligns with Exif date/times, which are specified without time zones + item.timeZone = TimeZone.getDefault() + save(item.calendar.timeInMillis) + } + } + } catch (e: XMPException) { + Log.w(LOG_TAG, "failed to get text for XMP schema=$schema, propName=$propName", e) } } } \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 82eba64c0..11444be0a 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,6 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.4.10' + ext.kotlin_version = '1.4.20' repositories { google() jcenter() diff --git a/lib/model/image_entry.dart b/lib/model/image_entry.dart index 63b78dde1..d00498597 100644 --- a/lib/model/image_entry.dart +++ b/lib/model/image_entry.dart @@ -299,7 +299,7 @@ class ImageEntry { bool get isLocated => _addressDetails != null; - LatLng get latLng => isCatalogued ? LatLng(_catalogMetadata.latitude, _catalogMetadata.longitude) : null; + LatLng get latLng => hasGps ? LatLng(_catalogMetadata.latitude, _catalogMetadata.longitude) : null; String get geoUri { if (!hasGps) return null;