Maybe fix the JPEG EXIF parsing in VIZ (#16096)

* Maybe fix the JPEG EXIF parsing in VIZ.

* Handle the common HTTP 403 case while getting the image.
This commit is contained in:
Alessandro Jean 2023-04-18 15:19:00 -03:00 committed by GitHub
parent 42abcb20b9
commit 5c6d6a8201
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 29 additions and 25 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'VIZ Shonen Jump' extName = 'VIZ Shonen Jump'
pkgNameSuffix = 'en.vizshonenjump' pkgNameSuffix = 'en.vizshonenjump'
extClass = '.VizShonenJump' extClass = '.VizShonenJump'
extVersionCode = 16 extVersionCode = 17
} }
dependencies { dependencies {

View File

@ -4,7 +4,7 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.Canvas import android.graphics.Canvas
import android.graphics.Rect import android.graphics.Rect
import com.drew.imaging.jpeg.JpegMetadataReader import com.drew.imaging.ImageMetadataReader
import com.drew.metadata.exif.ExifSubIFDDirectory import com.drew.metadata.exif.ExifSubIFDDirectory
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
@ -14,12 +14,12 @@ import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody
import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.ResponseBody.Companion.toResponseBody
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream
class VizImageInterceptor : Interceptor { class VizImageInterceptor : Interceptor {
@ -35,12 +35,15 @@ class VizImageInterceptor : Interceptor {
val imageUrl = imageUrlParse(response) val imageUrl = imageUrlParse(response)
val imageResponse = chain.proceed(imageRequest(imageUrl)) val imageResponse = chain.proceed(imageRequest(imageUrl))
val image = imageResponse.body.byteStream().decodeImage() if (!imageResponse.isSuccessful) {
val body = image.toResponseBody(MEDIA_TYPE) imageResponse.close()
throw IOException(FAILED_TO_FETCH_PAGE_URL)
}
val imageBody = imageResponse.decodeImage()
return imageResponse.newBuilder() return imageResponse.newBuilder()
.body(body) .body(imageBody)
.header("Content-Type", MEDIA_TYPE.toString())
.build() .build()
} }
@ -60,17 +63,18 @@ class VizImageInterceptor : Interceptor {
return GET(url, headers) return GET(url, headers)
} }
private fun InputStream.decodeImage(): ByteArray { private fun Response.decodeImage(): ResponseBody {
// See: https://stackoverflow.com/a/5924132 // See: https://stackoverflow.com/a/5924132
// See: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/2678#issuecomment-645857603 // See: https://github.com/tachiyomiorg/tachiyomi-extensions/issues/2678#issuecomment-645857603
val byteOutputStream = ByteArrayOutputStream() val byteOutputStream = ByteArrayOutputStream()
copyTo(byteOutputStream) .apply { body.byteStream().copyTo(this) }
val contentType = headers["Content-Type"]?.toMediaType()
val byteInputStreamForImage = ByteArrayInputStream(byteOutputStream.toByteArray()) val byteInputStreamForImage = ByteArrayInputStream(byteOutputStream.toByteArray())
val byteInputStreamForMetadata = ByteArrayInputStream(byteOutputStream.toByteArray()) val byteInputStreamForMetadata = ByteArrayInputStream(byteOutputStream.toByteArray())
val imageData = byteInputStreamForMetadata.getImageData() val imageData = byteInputStreamForMetadata.getImageData().getOrNull()
?: return byteOutputStream.toByteArray() ?: return byteOutputStream.toByteArray().toResponseBody(contentType)
val input = BitmapFactory.decodeStream(byteInputStreamForImage) val input = BitmapFactory.decodeStream(byteInputStreamForImage)
val width = input.width val width = input.width
@ -139,9 +143,10 @@ class VizImageInterceptor : Interceptor {
) )
} }
val output = ByteArrayOutputStream() return ByteArrayOutputStream()
result.compress(Bitmap.CompressFormat.JPEG, 95, output) .apply { result.compress(Bitmap.CompressFormat.JPEG, 95, this) }
return output.toByteArray() .toByteArray()
.toResponseBody(JPEG_MEDIA_TYPE)
} }
private fun Canvas.drawImage( private fun Canvas.drawImage(
@ -158,8 +163,13 @@ class VizImageInterceptor : Interceptor {
drawBitmap(from, srcRect, dstRect, null) drawBitmap(from, srcRect, dstRect, null)
} }
private fun ByteArrayInputStream.getImageData(): ImageData? { private fun ByteArrayInputStream.getImageData(): Result<ImageData?> = runCatching {
val metadata = JpegMetadataReader.readMetadata(this) val metadata = ImageMetadataReader.readMetadata(this)
val keyDir = metadata.directories
.firstOrNull { it.containsTag(ExifSubIFDDirectory.TAG_IMAGE_UNIQUE_ID) }
val metaUniqueId = keyDir?.getString(ExifSubIFDDirectory.TAG_IMAGE_UNIQUE_ID)
?: return@runCatching null
val sizeDir = metadata.directories.firstOrNull { val sizeDir = metadata.directories.firstOrNull {
it.containsTag(ExifSubIFDDirectory.TAG_IMAGE_WIDTH) && it.containsTag(ExifSubIFDDirectory.TAG_IMAGE_WIDTH) &&
@ -168,13 +178,7 @@ class VizImageInterceptor : Interceptor {
val metaWidth = sizeDir?.getInt(ExifSubIFDDirectory.TAG_IMAGE_WIDTH) ?: COMMON_WIDTH val metaWidth = sizeDir?.getInt(ExifSubIFDDirectory.TAG_IMAGE_WIDTH) ?: COMMON_WIDTH
val metaHeight = sizeDir?.getInt(ExifSubIFDDirectory.TAG_IMAGE_HEIGHT) ?: COMMON_HEIGHT val metaHeight = sizeDir?.getInt(ExifSubIFDDirectory.TAG_IMAGE_HEIGHT) ?: COMMON_HEIGHT
val keyDir = metadata.directories.firstOrNull { ImageData(metaWidth, metaHeight, metaUniqueId)
it.containsTag(ExifSubIFDDirectory.TAG_IMAGE_UNIQUE_ID)
}
val metaUniqueId = keyDir?.getString(ExifSubIFDDirectory.TAG_IMAGE_UNIQUE_ID)
?: return null
return ImageData(metaWidth, metaHeight, metaUniqueId)
} }
private data class ImageData(val width: Int, val height: Int, val uniqueId: String) { private data class ImageData(val width: Int, val height: Int, val uniqueId: String) {
@ -186,9 +190,9 @@ class VizImageInterceptor : Interceptor {
companion object { companion object {
private const val IMAGE_URL_ENDPOINT = "get_manga_url" private const val IMAGE_URL_ENDPOINT = "get_manga_url"
private val MEDIA_TYPE = "image/jpeg".toMediaType() private val JPEG_MEDIA_TYPE = "image/jpeg".toMediaType()
private const val FAILED_TO_FETCH_PAGE_URL = "Something went wrong while trying to fetch page." private const val FAILED_TO_FETCH_PAGE_URL = "Something went wrong while trying to fetch the page."
private const val CELL_WIDTH_COUNT = 10 private const val CELL_WIDTH_COUNT = 10
private const val CELL_HEIGHT_COUNT = 15 private const val CELL_HEIGHT_COUNT = 15