From 1b6febe0343601850b3d137757dfc8e9a6d4a0e7 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 4 Feb 2021 12:10:17 +0900 Subject: [PATCH] catalog: use PNG last modification time as fallback --- .../aves/channel/calls/MetadataHandler.kt | 26 ++++++++++++++++++- .../aves/metadata/MetadataExtractorHelper.kt | 4 +++ lib/widgets/viewer/info/info_search.dart | 2 +- 3 files changed, 30 insertions(+), 2 deletions(-) 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 505b50f4b..d0e315d08 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 @@ -23,6 +23,7 @@ import com.drew.metadata.file.FileTypeDirectory import com.drew.metadata.gif.GifAnimationDirectory import com.drew.metadata.iptc.IptcDirectory import com.drew.metadata.mp4.media.Mp4UuidBoxDirectory +import com.drew.metadata.png.PngDirectory import com.drew.metadata.webp.WebpDirectory import com.drew.metadata.xmp.XmpDirectory import deckers.thibault.aves.channel.calls.Coresult.Companion.safe @@ -37,6 +38,8 @@ import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeDescri import deckers.thibault.aves.metadata.MediaMetadataRetrieverHelper.getSafeInt import deckers.thibault.aves.metadata.Metadata.getRotationDegreesForExifCode import deckers.thibault.aves.metadata.Metadata.isFlippedForExifCode +import deckers.thibault.aves.metadata.MetadataExtractorHelper.PNG_LAST_MODIFICATION_TIME_FORMAT +import deckers.thibault.aves.metadata.MetadataExtractorHelper.PNG_TIME_DIR_NAME import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeBoolean import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeDateMillis import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeInt @@ -70,6 +73,7 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import org.beyka.tiffbitmapfactory.TiffBitmapFactory import java.io.File +import java.text.ParseException import java.util.* import kotlin.math.roundToLong @@ -217,6 +221,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { return dirMap } + // legend: ME=MetadataExtractor, EI=ExifInterface, MMR=MediaMetadataRetriever // set `KEY_DATE_MILLIS` from these fields (by precedence): // - ME / Exif / DATETIME_ORIGINAL // - ME / Exif / DATETIME @@ -224,6 +229,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { // - EI / Exif / DATETIME // - ME / XMP / xmp:CreateDate // - ME / XMP / photoshop:DateCreated + // - ME / PNG / TIME / LAST_MODIFICATION_TIME // - MMR / METADATA_KEY_DATE // set `KEY_XMP_TITLE_DESCRIPTION` from these fields (by precedence): // - ME / XMP / dc:title @@ -348,12 +354,29 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { } } - // identification of animated GIF & WEBP, GeoTIFF when (mimeType) { + MimeTypes.PNG -> { + // date fallback to PNG time chunk + if (!metadataMap.containsKey(KEY_DATE_MILLIS)) { + for (dir in metadata.getDirectoriesOfType(PngDirectory::class.java).filter { it.name == PNG_TIME_DIR_NAME }) { + dir.getSafeString(PngDirectory.TAG_LAST_MODIFICATION_TIME) { + try { + PNG_LAST_MODIFICATION_TIME_FORMAT.parse(it)?.let { date -> + metadataMap[KEY_DATE_MILLIS] = date.time + } + } catch (e: ParseException) { + Log.w(LOG_TAG, "failed to parse PNG date=$it for uri=$uri", e) + } + } + } + } + } MimeTypes.GIF -> { + // identification of animated GIF if (metadata.containsDirectoryOfType(GifAnimationDirectory::class.java)) flags = flags or MASK_IS_ANIMATED } MimeTypes.WEBP -> { + // identification of animated WEBP for (dir in metadata.getDirectoriesOfType(WebpDirectory::class.java)) { dir.getSafeBoolean(WebpDirectory.TAG_IS_ANIMATION) { if (it) flags = flags or MASK_IS_ANIMATED @@ -361,6 +384,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler { } } MimeTypes.TIFF -> { + // identification of GeoTIFF for (dir in metadata.getDirectoriesOfType(ExifIFD0Directory::class.java)) { if (dir.isGeoTiff()) flags = flags or MASK_IS_GEOTIFF } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MetadataExtractorHelper.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MetadataExtractorHelper.kt index 18b23f6d8..fe05a46b3 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MetadataExtractorHelper.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MetadataExtractorHelper.kt @@ -3,9 +3,13 @@ package deckers.thibault.aves.metadata import com.drew.lang.Rational import com.drew.metadata.Directory import com.drew.metadata.exif.ExifIFD0Directory +import java.text.SimpleDateFormat import java.util.* object MetadataExtractorHelper { + const val PNG_TIME_DIR_NAME = "PNG-tIME" + val PNG_LAST_MODIFICATION_TIME_FORMAT = SimpleDateFormat("yyyy:MM:dd hh:mm:ss", Locale.ROOT) + // extensions fun Directory.getSafeDescription(tag: Int, save: (value: String) -> Unit) { diff --git a/lib/widgets/viewer/info/info_search.dart b/lib/widgets/viewer/info/info_search.dart index 601f6b70c..f8c94fe1b 100644 --- a/lib/widgets/viewer/info/info_search.dart +++ b/lib/widgets/viewer/info/info_search.dart @@ -17,7 +17,7 @@ class InfoSearchDelegate extends SearchDelegate { static const suggestions = { 'Date & time': 'date or time or when -timer -uptime -exposure -timeline', - 'Description': 'abstract or description or comment', + 'Description': 'abstract or description or comment or textual', 'Dimensions': 'width or height or dimension or framesize or imagelength', 'Resolution': 'resolution', 'Rights': 'rights or copyright or artist or creator or by-line or credit -tool',