From 1346e8867b41409851b5ff991185917a6e712af2 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Sat, 8 Jun 2024 01:53:10 +0200 Subject: [PATCH] #1037 use truncated preview when opening large DNG with metadata extractor --- .../aves/channel/calls/MetadataFetchHandler.kt | 2 +- .../deckers/thibault/aves/metadata/Metadata.kt | 4 +++- .../aves/metadata/metadataextractor/Helper.kt | 15 ++++++++------- .../deckers/thibault/aves/utils/MimeTypes.kt | 9 +++++---- 4 files changed, 17 insertions(+), 13 deletions(-) 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 7719d796b..06c64cd50 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 @@ -296,7 +296,7 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler { byGeoTiff[false]?.map { exifTagMapper(it) }?.let { dirMap.putAll(it) } } - mimeType == MimeTypes.DNG -> { + mimeType == MimeTypes.DNG || mimeType == MimeTypes.DNG_ADOBE -> { // split DNG tags in their own directory val dngDirMap = metadataMap[DIR_DNG] ?: HashMap() metadataMap[DIR_DNG] = dngDirMap 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 4553b2c4f..4c578493b 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 @@ -120,7 +120,7 @@ object Metadata { return date.time + parseSubSecond(subSecond) } - // Opening large PSD/TIFF files yields an OOM (both with `metadata-extractor` v2.15.0 and `ExifInterface` v1.3.1), + // Opening some large files yields an OOM (both with `metadata-extractor` v2.15.0 and `ExifInterface` v1.3.1), // so we define an arbitrary threshold to avoid a crash on launch. // It is not clear whether it is because of the file itself or its metadata. private const val FILE_SIZE_MAX = 100 * (1 shl 20) // MB @@ -136,6 +136,8 @@ object Metadata { private fun getSafeUri(context: Context, uri: Uri, mimeType: String, sizeBytes: Long?): Uri { return when (mimeType) { // formats known to yield OOM for large files + MimeTypes.DNG, + MimeTypes.DNG_ADOBE, MimeTypes.HEIC, MimeTypes.HEIF, MimeTypes.MP4, diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/Helper.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/Helper.kt index 1858962ae..48bc3bdf5 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/Helper.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/metadataextractor/Helper.kt @@ -60,19 +60,20 @@ object Helper { // e.g. "exif [...] 134 [...] 4578696600004949[...]" private val PNG_RAW_PROFILE_PATTERN = Regex("^\\n(.*?)\\n\\s*(\\d+)\\n(.*)", RegexOption.DOT_MATCHES_ALL) - // providing the stream length is risky, as it may crash if it is incorrect - private const val SAFE_READ_STREAM_LENGTH = -1L - fun readMimeType(input: InputStream): String? { val bufferedInputStream = if (input is BufferedInputStream) input else BufferedInputStream(input) return FileTypeDetector.detectFileType(bufferedInputStream).mimeType } @Throws(IOException::class, ImageProcessingException::class) - fun safeRead(input: InputStream, sizeBytes: Long?): com.drew.metadata.Metadata { + fun safeRead(input: InputStream, @Suppress("unused_parameter") sizeBytes: Long?): com.drew.metadata.Metadata { val inputStream = if (input is BufferedInputStream) input else BufferedInputStream(input) val fileType = FileTypeDetector.detectFileType(inputStream) - val streamLength = sizeBytes ?: SAFE_READ_STREAM_LENGTH + + // Providing the stream length is risky, as it may crash if it is incorrect. + // Not providing the stream length is also risky, as it may lead to OOM + // when `RandomAccessStreamReader` reads the entire stream to validate offsets. + val undefinedStreamLength = -1L val metadata = when (fileType) { FileType.Jpeg -> safeReadJpeg(inputStream) @@ -84,9 +85,9 @@ object Helper { FileType.Cr2, FileType.Nef, FileType.Orf, - FileType.Rw2 -> safeReadTiff(inputStream, streamLength) + FileType.Rw2 -> safeReadTiff(inputStream, undefinedStreamLength) - else -> ImageMetadataReader.readMetadata(inputStream, streamLength, fileType) + else -> ImageMetadataReader.readMetadata(inputStream, undefinedStreamLength, fileType) } metadata.addDirectory(FileTypeDirectory(fileType)) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt index 645fa47a1..1430472b0 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt @@ -28,7 +28,8 @@ object MimeTypes { private const val CR2 = "image/x-canon-cr2" private const val CRW = "image/x-canon-crw" private const val DCR = "image/x-kodak-dcr" - const val DNG = "image/x-adobe-dng" + const val DNG = "image/dng" + const val DNG_ADOBE = "image/x-adobe-dng" private const val ERF = "image/x-epson-erf" private const val K25 = "image/x-kodak-k25" private const val KDC = "image/x-kodak-kdc" @@ -71,7 +72,7 @@ object MimeTypes { fun isRaw(mimeType: String): Boolean { return when (mimeType) { - ARW, CR2, CRW, DCR, DNG, ERF, K25, KDC, MRW, NEF, NRW, ORF, PEF, RAF, RAW, RW2, SR2, SRF, SRW, X3F -> true + ARW, CR2, CRW, DCR, DNG, DNG_ADOBE, ERF, K25, KDC, MRW, NEF, NRW, ORF, PEF, RAF, RAW, RW2, SR2, SRF, SRW, X3F -> true else -> false } } @@ -142,7 +143,7 @@ object MimeTypes { return if (pageId != null && MultiPageImage.isSupported(mimeType)) { true } else when (mimeType) { - DNG, HEIC, HEIF, PNG, WEBP -> true + DNG, DNG_ADOBE, HEIC, HEIF, PNG, WEBP -> true else -> false } } @@ -151,7 +152,7 @@ object MimeTypes { // according to EXIF orientation when decoding images of known formats // but we need to rotate the decoded bitmap for the other formats fun needRotationAfterContentResolverThumbnail(mimeType: String) = when (mimeType) { - DNG, PNG -> true + DNG, DNG_ADOBE, PNG -> true else -> false }