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
This commit is contained in:
FooIbar 2024-07-08 18:02:50 +08:00 committed by GitHub
parent cbcd8bd668
commit daa47e0493
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 20 additions and 32 deletions

View file

@ -17,6 +17,7 @@ import logcat.LogPriority
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.storage.extension import tachiyomi.core.common.storage.extension
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
@ -160,7 +161,7 @@ class DownloadManager(
fun buildPageList(source: Source, manga: Manga, chapter: Chapter): List<Page> { fun buildPageList(source: Source, manga: Manga, chapter: Chapter): List<Page> {
val chapterDir = provider.findChapterDir(chapter.name, chapter.scanlator, manga.title, source) val chapterDir = provider.findChapterDir(chapter.name, chapter.scanlator, manga.title, source)
val files = chapterDir?.listFiles().orEmpty() val files = chapterDir?.listFiles().orEmpty()
.filter { "image" in it.type.orEmpty() } .filter { it.isFile && ImageUtil.isImage(it.name) { it.openInputStream() } }
if (files.isEmpty()) { if (files.isEmpty()) {
throw Exception(context.stringResource(MR.strings.page_list_empty_error)) throw Exception(context.stringResource(MR.strings.page_list_empty_error))

View file

@ -523,14 +523,8 @@ class Downloader(
* @param file the file where the image is already downloaded. * @param file the file where the image is already downloaded.
*/ */
private fun getImageExtension(response: Response, file: UniFile): String { 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 } val mime = response.body.contentType()?.run { if (type == "image") "image/$subtype" else null }
// Else guess from the uri. return ImageUtil.getExtensionFromMimeType(mime) { file.openInputStream() }
?: context.contentResolver.getType(file.uri)
// Else read magic numbers.
?: ImageUtil.findImageType { file.openInputStream() }?.mime
return ImageUtil.getExtensionFromMimeType(mime)
} }
private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) { private fun splitTallImageIfNeeded(page: Page, tmpDir: UniFile) {

View file

@ -7,6 +7,7 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import android.provider.MediaStore import android.provider.MediaStore
import android.webkit.MimeTypeMap
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.contentValuesOf import androidx.core.content.contentValuesOf
import androidx.core.net.toUri import androidx.core.net.toUri
@ -65,21 +66,26 @@ class ImageSaver(
filename: String, filename: String,
data: () -> InputStream, data: () -> InputStream,
): Uri { ): Uri {
val pictureDir = val isMimeTypeSupported = MimeTypeMap.getSingleton().hasMimeType(type.mime)
val pictureDir = if (isMimeTypeSupported) {
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) 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 imageLocation = (image.location as Location.Pictures).relativePath
val relativePath = listOf( val relativePath = listOf(
Environment.DIRECTORY_PICTURES, if (isMimeTypeSupported) Environment.DIRECTORY_PICTURES else Environment.DIRECTORY_DOCUMENTS,
context.stringResource(MR.strings.app_name), context.stringResource(MR.strings.app_name),
imageLocation, imageLocation,
).joinToString(File.separator) ).joinToString(File.separator)
val contentValues = contentValuesOf( val contentValues = contentValuesOf(
MediaStore.Images.Media.RELATIVE_PATH to relativePath, MediaStore.MediaColumns.RELATIVE_PATH to relativePath,
MediaStore.Images.Media.DISPLAY_NAME to image.name, MediaStore.MediaColumns.DISPLAY_NAME to if (isMimeTypeSupported) image.name else filename,
MediaStore.Images.Media.MIME_TYPE to type.mime, MediaStore.MediaColumns.MIME_TYPE to type.mime,
MediaStore.Images.Media.DATE_MODIFIED to Instant.now().epochSecond, MediaStore.MediaColumns.DATE_MODIFIED to Instant.now().epochSecond,
) )
val picture = findUriOrDefault(relativePath, filename) { val picture = findUriOrDefault(relativePath, filename) {

View file

@ -13,7 +13,6 @@ import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.os.Build import android.os.Build
import android.webkit.MimeTypeMap
import androidx.annotation.ColorInt import androidx.annotation.ColorInt
import androidx.core.graphics.alpha import androidx.core.graphics.alpha
import androidx.core.graphics.applyCanvas import androidx.core.graphics.applyCanvas
@ -29,7 +28,6 @@ import okio.BufferedSource
import tachiyomi.decoder.Format import tachiyomi.decoder.Format
import tachiyomi.decoder.ImageDecoder import tachiyomi.decoder.ImageDecoder
import java.io.InputStream import java.io.InputStream
import java.net.URLConnection
import java.util.Locale import java.util.Locale
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.max import kotlin.math.max
@ -40,12 +38,8 @@ object ImageUtil {
fun isImage(name: String?, openStream: (() -> InputStream)? = null): Boolean { fun isImage(name: String?, openStream: (() -> InputStream)? = null): Boolean {
if (name == null) return false if (name == null) return false
val contentType = try { val extension = name.substringAfterLast('.')
URLConnection.guessContentTypeFromName(name) return ImageType.entries.any { it.extension == extension } || openStream?.let { findImageType(it) } != null
} catch (e: Exception) {
null
} ?: openStream?.let { findImageType(it)?.mime }
return contentType?.startsWith("image/") ?: false
} }
fun findImageType(openStream: () -> InputStream): ImageType? { fun findImageType(openStream: () -> InputStream): ImageType? {
@ -69,10 +63,9 @@ object ImageUtil {
} }
} }
fun getExtensionFromMimeType(mime: String?): String { fun getExtensionFromMimeType(mime: String?, openStream: () -> InputStream): String {
return MimeTypeMap.getSingleton().getExtensionFromMimeType(mime) val type = mime?.let { ImageType.entries.find { it.mime == mime } } ?: findImageType(openStream)
?: SUPPLEMENTARY_MIMETYPE_MAPPING[mime] return type?.extension ?: "jpg"
?: "jpg"
} }
fun isAnimatedAndSupported(source: BufferedSource): Boolean { fun isAnimatedAndSupported(source: BufferedSource): Boolean {
@ -558,12 +551,6 @@ object ImageUtil {
} }
private val optimalImageHeight = getDisplayMaxHeightInPx * 2 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 val getDisplayMaxHeightInPx: Int