From e57484d912c2316bdc09574dec974bf1b5ef1167 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sun, 4 Feb 2024 19:52:56 +0100 Subject: [PATCH] #894 fixed motion photo detection for xml variant of google container item --- .../thibault/aves/metadata/MultiPage.kt | 2 +- .../thibault/aves/metadata/xmp/GoogleXMP.kt | 23 ++++++++++++------- lib/ref/metadata/xmp.dart | 1 + .../viewer/info/metadata/xmp_namespaces.dart | 8 ++++++- .../viewer/info/metadata/xmp_ns/google.dart | 17 +++++++++++++- 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt index c327565f7..65876c72c 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt @@ -274,7 +274,7 @@ object MultiPage { var foundXmp = false fun processXmp(xmpMeta: XMPMeta) { - offsetFromEnd = GoogleXMP.getTrailingVideoOffsetFromEnd(xmpMeta) + offsetFromEnd = offsetFromEnd ?: GoogleXMP.getTrailingVideoOffsetFromEnd(xmpMeta) } try { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/xmp/GoogleXMP.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/xmp/GoogleXMP.kt index 41550fc04..cf662e1bb 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/xmp/GoogleXMP.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/xmp/GoogleXMP.kt @@ -109,10 +109,11 @@ object GoogleXMP { var hasImage = false var hasVideo = false for (i in 1 until count + 1) { - val mime = meta.getSafeStructField(listOf(GCONTAINER_DIRECTORY_PROP_NAME, i, GCONTAINER_ITEM_PROP_NAME, GCONTAINER_ITEM_MIME_PROP_NAME))?.value - val length = meta.getSafeStructField(listOf(GCONTAINER_DIRECTORY_PROP_NAME, i, GCONTAINER_ITEM_PROP_NAME, GCONTAINER_ITEM_LENGTH_PROP_NAME))?.value - hasImage = hasImage || MimeTypes.isImage(mime) && length != null - hasVideo = hasVideo || MimeTypes.isVideo(mime) && length != null + val mime = getContainerItemAttribute(meta, i, GCONTAINER_ITEM_MIME_PROP_NAME) + val length = getContainerItemAttribute(meta, i, GCONTAINER_ITEM_LENGTH_PROP_NAME) + // `length` is not always provided for the image item + hasImage = hasImage || MimeTypes.isImage(mime) + hasVideo = hasVideo || (MimeTypes.isVideo(mime) && length != null) } if (hasImage && hasVideo) return true } @@ -128,6 +129,13 @@ object GoogleXMP { return false } + private fun getContainerItemAttribute(meta: XMPMeta, i: Int, attribute: XMPPropName): String? { + // variant of `Container:Item` with `` + val mime = meta.getSafeStructField(listOf(GCONTAINER_DIRECTORY_PROP_NAME, i, GCONTAINER_ITEM_PROP_NAME, attribute))?.value + // variant of `Container:Item` with `` + return mime ?: meta.getSafeStructField(listOf(GCONTAINER_DIRECTORY_PROP_NAME, i, attribute))?.value + } + fun isPanorama(meta: XMPMeta): Boolean { try { if (gpanoRequiredProps.all { meta.doesPropExist(it) }) return true @@ -166,10 +174,9 @@ object GoogleXMP { // `Container` motion photo val count = meta.countPropArrayItems(GCONTAINER_DIRECTORY_PROP_NAME) for (i in 1 until count + 1) { - val mime = meta.getSafeStructField(listOf(GCONTAINER_DIRECTORY_PROP_NAME, i, GCONTAINER_ITEM_PROP_NAME, GCONTAINER_ITEM_MIME_PROP_NAME))?.value - val length = meta.getSafeStructField(listOf(GCONTAINER_DIRECTORY_PROP_NAME, i, GCONTAINER_ITEM_PROP_NAME, GCONTAINER_ITEM_LENGTH_PROP_NAME))?.value - if (MimeTypes.isVideo(mime) && length != null) { - offsetFromEnd = length.toLong() + val mime = getContainerItemAttribute(meta, i, GCONTAINER_ITEM_MIME_PROP_NAME) + if (MimeTypes.isVideo(mime)) { + getContainerItemAttribute(meta, i, GCONTAINER_ITEM_LENGTH_PROP_NAME)?.let { offsetFromEnd = it.toLong() } } } } diff --git a/lib/ref/metadata/xmp.dart b/lib/ref/metadata/xmp.dart index fa2dbdb13..2d5ff418b 100644 --- a/lib/ref/metadata/xmp.dart +++ b/lib/ref/metadata/xmp.dart @@ -24,6 +24,7 @@ class XmpNamespaces { static const gAudio = 'http://ns.google.com/photos/1.0/audio/'; static const gCamera = 'http://ns.google.com/photos/1.0/camera/'; static const gContainer = 'http://ns.google.com/photos/1.0/container/'; + static const gContainerItem = 'http://ns.google.com/photos/1.0/container/item/'; static const gCreations = 'http://ns.google.com/photos/1.0/creations/'; static const gDepth = 'http://ns.google.com/photos/1.0/depthmap/'; static const gDevice = 'http://ns.google.com/photos/dd/1.0/device/'; diff --git a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart index 1a33eb4c0..57ae547d2 100644 --- a/lib/widgets/viewer/info/metadata/xmp_namespaces.dart +++ b/lib/widgets/viewer/info/metadata/xmp_namespaces.dart @@ -90,7 +90,11 @@ class XmpNamespace extends Equatable { List buildNamespaceSection(BuildContext context) { final props = rawProps.entries .map((kv) { - final prop = XmpProp(kv.key, kv.value); + final key = kv.key; + if (skippedProps.any((pattern) => pattern.allMatches(key).isNotEmpty)) { + return null; + } + final prop = XmpProp(key, kv.value); var extracted = false; cards.forEach((card) => extracted |= card.extract(prop)); return extracted ? null : prop; @@ -134,6 +138,8 @@ class XmpNamespace extends Equatable { : []; } + Set get skippedProps => {}; + List get cards => []; String formatValue(XmpProp prop) => prop.value; diff --git a/lib/widgets/viewer/info/metadata/xmp_ns/google.dart b/lib/widgets/viewer/info/metadata/xmp_ns/google.dart index e05ab5852..77e740170 100644 --- a/lib/widgets/viewer/info/metadata/xmp_ns/google.dart +++ b/lib/widgets/viewer/info/metadata/xmp_ns/google.dart @@ -73,11 +73,26 @@ class XmpGCameraNamespace extends XmpGoogleNamespace { } class XmpGContainer extends XmpNamespace { - XmpGContainer({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: XmpNamespaces.gContainer); + late final String _gContainerItemNsPrefix; + late final String _rdfNsPrefix; + + XmpGContainer({required super.schemaRegistryPrefixes, required super.rawProps}) : super(nsUri: XmpNamespaces.gContainer) { + _gContainerItemNsPrefix = XmpNamespace.prefixForUri(schemaRegistryPrefixes, XmpNamespaces.gContainerItem); + _rdfNsPrefix = XmpNamespace.prefixForUri(schemaRegistryPrefixes, XmpNamespaces.rdf); + } + + @override + late final Set skippedProps = { + // variant of `Container:Item` with `` + RegExp(nsPrefix + r'Directory\[(\d+)\]/' + _rdfNsPrefix + r'type'), + }; @override late final List cards = [ + // variant of `Container:Item` with `` XmpCardData(RegExp(nsPrefix + r'Directory\[(\d+)\]/' + nsPrefix + r'Item/(.*)'), title: 'Directory Item'), + // variant of `Container:Item` with `` + XmpCardData(RegExp(nsPrefix + r'Directory\[(\d+)\]/(' + _gContainerItemNsPrefix + r'.*)'), title: 'Directory Item'), ]; }