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)