From daa47e049327c4d8b1fe4724ed1b84897d81fcf2 Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Mon, 8 Jul 2024 18:02:50 +0800 Subject: [PATCH] Fix some issues when reading/saving images (#993) * Fix unsupported mime type error when saving images Avoid using platform mime type map to get extensions as it may not have all mime types we support. * Fix jxl images downloading/reading --- .../data/download/DownloadManager.kt | 3 ++- .../tachiyomi/data/download/Downloader.kt | 8 +------ .../kanade/tachiyomi/data/saver/ImageSaver.kt | 18 ++++++++++----- .../core/common/util/system/ImageUtil.kt | 23 ++++--------------- 4 files changed, 20 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index 01a342859d..5fba899ca3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -17,6 +17,7 @@ import logcat.LogPriority import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.storage.extension import tachiyomi.core.common.util.lang.launchIO +import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.logcat import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.chapter.model.Chapter @@ -160,7 +161,7 @@ class DownloadManager( fun buildPageList(source: Source, manga: Manga, chapter: Chapter): List { val chapterDir = provider.findChapterDir(chapter.name, chapter.scanlator, manga.title, source) val files = chapterDir?.listFiles().orEmpty() - .filter { "image" in it.type.orEmpty() } + .filter { it.isFile && ImageUtil.isImage(it.name) { it.openInputStream() } } if (files.isEmpty()) { throw Exception(context.stringResource(MR.strings.page_list_empty_error)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index ebf9df65ef..57c3a98246 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -523,14 +523,8 @@ class Downloader( * @param file the file where the image is already downloaded. */ private fun getImageExtension(response: Response, file: UniFile): String { - // Read content type if available. val mime = response.body.contentType()?.run { if (type == "image") "image/$subtype" else null } - // Else guess from the uri. - ?: context.contentResolver.getType(file.uri) - // Else read magic numbers. - ?: ImageUtil.findImageType { file.openInputStream() }?.mime - - return ImageUtil.getExtensionFromMimeType(mime) + return ImageUtil.getExtensionFromMimeType(mime) { file.openInputStream() } } private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt index b7b53d835e..0cc0ebc04d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt @@ -7,6 +7,7 @@ import android.net.Uri import android.os.Build import android.os.Environment import android.provider.MediaStore +import android.webkit.MimeTypeMap import androidx.annotation.RequiresApi import androidx.core.content.contentValuesOf import androidx.core.net.toUri @@ -65,21 +66,26 @@ class ImageSaver( filename: String, data: () -> InputStream, ): Uri { - val pictureDir = + val isMimeTypeSupported = MimeTypeMap.getSingleton().hasMimeType(type.mime) + + val pictureDir = if (isMimeTypeSupported) { MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) + } else { + MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) + } val imageLocation = (image.location as Location.Pictures).relativePath val relativePath = listOf( - Environment.DIRECTORY_PICTURES, + if (isMimeTypeSupported) Environment.DIRECTORY_PICTURES else Environment.DIRECTORY_DOCUMENTS, context.stringResource(MR.strings.app_name), imageLocation, ).joinToString(File.separator) val contentValues = contentValuesOf( - MediaStore.Images.Media.RELATIVE_PATH to relativePath, - MediaStore.Images.Media.DISPLAY_NAME to image.name, - MediaStore.Images.Media.MIME_TYPE to type.mime, - MediaStore.Images.Media.DATE_MODIFIED to Instant.now().epochSecond, + MediaStore.MediaColumns.RELATIVE_PATH to relativePath, + MediaStore.MediaColumns.DISPLAY_NAME to if (isMimeTypeSupported) image.name else filename, + MediaStore.MediaColumns.MIME_TYPE to type.mime, + MediaStore.MediaColumns.DATE_MODIFIED to Instant.now().epochSecond, ) val picture = findUriOrDefault(relativePath, filename) { diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt b/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt index f5e9a80983..2cdd2a306e 100644 --- a/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt +++ b/core/common/src/main/kotlin/tachiyomi/core/common/util/system/ImageUtil.kt @@ -13,7 +13,6 @@ import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.os.Build -import android.webkit.MimeTypeMap import androidx.annotation.ColorInt import androidx.core.graphics.alpha import androidx.core.graphics.applyCanvas @@ -29,7 +28,6 @@ import okio.BufferedSource import tachiyomi.decoder.Format import tachiyomi.decoder.ImageDecoder import java.io.InputStream -import java.net.URLConnection import java.util.Locale import kotlin.math.abs import kotlin.math.max @@ -40,12 +38,8 @@ object ImageUtil { fun isImage(name: String?, openStream: (() -> InputStream)? = null): Boolean { if (name == null) return false - val contentType = try { - URLConnection.guessContentTypeFromName(name) - } catch (e: Exception) { - null - } ?: openStream?.let { findImageType(it)?.mime } - return contentType?.startsWith("image/") ?: false + val extension = name.substringAfterLast('.') + return ImageType.entries.any { it.extension == extension } || openStream?.let { findImageType(it) } != null } fun findImageType(openStream: () -> InputStream): ImageType? { @@ -69,10 +63,9 @@ object ImageUtil { } } - fun getExtensionFromMimeType(mime: String?): String { - return MimeTypeMap.getSingleton().getExtensionFromMimeType(mime) - ?: SUPPLEMENTARY_MIMETYPE_MAPPING[mime] - ?: "jpg" + fun getExtensionFromMimeType(mime: String?, openStream: () -> InputStream): String { + val type = mime?.let { ImageType.entries.find { it.mime == mime } } ?: findImageType(openStream) + return type?.extension ?: "jpg" } fun isAnimatedAndSupported(source: BufferedSource): Boolean { @@ -558,12 +551,6 @@ object ImageUtil { } private val optimalImageHeight = getDisplayMaxHeightInPx * 2 - - // Android doesn't include some mappings - private val SUPPLEMENTARY_MIMETYPE_MAPPING = mapOf( - // https://issuetracker.google.com/issues/182703810 - "image/jxl" to "jxl", - ) } val getDisplayMaxHeightInPx: Int