diff --git a/CHANGELOG.md b/CHANGELOG.md index ab2f65147..3408d0272 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ All notable changes to this project will be documented in this file. ### Added - option to set the Tags page as home -- support for animated PNG +- support for animated PNG (requires rescan) - Info: added day filter with item date - Widget: option to update image on tap - Slideshow / Screen saver: option for random transition diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 014a84516..28d61c137 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -301,7 +301,7 @@ This change eventually prevents building the app with Flutter v3.7.11. - + diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt index 8145c71cc..e04dc7241 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt @@ -75,6 +75,7 @@ import deckers.thibault.aves.metadata.metadataextractor.Helper.getSafeInt import deckers.thibault.aves.metadata.metadataextractor.Helper.getSafeRational import deckers.thibault.aves.metadata.metadataextractor.Helper.getSafeString import deckers.thibault.aves.metadata.metadataextractor.Helper.isPngTextDir +import deckers.thibault.aves.metadata.metadataextractor.PngActlDirectory import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.utils.ContextUtils.queryContentPropValue import deckers.thibault.aves.utils.LogUtils @@ -657,6 +658,11 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { } } } + + // identification of animated PNG + if (metadata.containsDirectoryOfType(PngActlDirectory::class.java)) { + flags = flags or MASK_IS_ANIMATED + } } MimeTypes.GIF -> { diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/PngActlDirectory.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/PngActlDirectory.kt new file mode 100644 index 000000000..93138826a --- /dev/null +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/PngActlDirectory.kt @@ -0,0 +1,22 @@ +package deckers.thibault.aves.metadata.metadataextractor +import com.drew.imaging.png.PngChunkType +import com.drew.metadata.png.PngDirectory + +class PngActlDirectory : PngDirectory(chunkType) { + override fun getTagNameMap(): HashMap { + return tagNames + } + + companion object { + val chunkType = PngChunkType("acTL") + + // tags should be distinct from those already defined in `PngDirectory` + const val TAG_NUM_FRAMES = 101 + const val TAG_NUM_PLAYS = 102 + + private val tagNames = hashMapOf( + TAG_NUM_FRAMES to "Number Of Frames", + TAG_NUM_PLAYS to "Number Of Plays", + ) + } +} diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/SafePngMetadataReader.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/SafePngMetadataReader.kt index d8b5452a4..c438068b5 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/SafePngMetadataReader.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/SafePngMetadataReader.kt @@ -34,9 +34,10 @@ import java.io.InputStream import java.util.zip.InflaterInputStream import java.util.zip.ZipException -// adapted from `PngMetadataReader` to prevent OOM from reading large chunks -// as of `metadata-extractor` v2.18.0, there is no way to customize the reader -// without copying `desiredChunkTypes` and the whole `processChunk` function +// adapted from `PngMetadataReader` to: +// - prevent OOM from reading large chunks. As of `metadata-extractor` v2.18.0, there is no way to customize the reader +// without copying `desiredChunkTypes` and the whole `processChunk` function. +// - parse `acTL` chunk to identify animated PNGs. object SafePngMetadataReader { private val LOG_TAG = LogUtils.createTag() @@ -60,6 +61,7 @@ object SafePngMetadataReader { PngChunkType.pHYs, PngChunkType.sBIT, PngChunkType.eXIf, + PngActlDirectory.chunkType, ) @Throws(IOException::class, PngProcessingException::class) @@ -99,6 +101,21 @@ object SafePngMetadataReader { directory.setInt(PngDirectory.TAG_FILTER_METHOD, header.filterMethod.toInt()) directory.setInt(PngDirectory.TAG_INTERLACE_METHOD, header.interlaceMethod.toInt()) metadata.addDirectory(directory) + // TLAD insert start + } else if (chunkType == PngActlDirectory.chunkType) { + if (bytes.size != 8) { + throw PngProcessingException("Invalid number of bytes") + } + val reader = SequentialByteArrayReader(bytes) + try { + metadata.addDirectory(PngActlDirectory().apply { + setInt(PngActlDirectory.TAG_NUM_FRAMES, reader.int32) + setInt(PngActlDirectory.TAG_NUM_PLAYS, reader.int32) + }) + } catch (ex: IOException) { + throw PngProcessingException(ex) + } + // TLAD insert end } else if (chunkType == PngChunkType.PLTE) { val directory = PngDirectory(PngChunkType.PLTE) directory.setInt(PngDirectory.TAG_PALETTE_SIZE, bytes.size / 3)