From d7e14cd84be2b7190aede72b43293691a633d496 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 16 Aug 2021 11:32:53 +0900 Subject: [PATCH] improved thumbnail loading error reporting --- .../channel/calls/fetchers/ThumbnailFetcher.kt | 2 +- .../channel/streams/ImageByteStreamHandler.kt | 18 +++++++++--------- .../aves/model/provider/ImageProvider.kt | 2 +- .../deckers/thibault/aves/utils/MimeTypes.kt | 3 ++- lib/model/entry.dart | 11 +---------- lib/ref/mime_types.dart | 15 +++++++++++++-- lib/services/image_file_service.dart | 5 ++++- 7 files changed, 31 insertions(+), 25 deletions(-) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt index 2a32f93ba..aae2b6da4 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt @@ -81,7 +81,7 @@ class ThumbnailFetcher internal constructor( if (errorDetails?.isNotEmpty() == true) { errorDetails = errorDetails.split("\n".toRegex(), 2).first() } - result.error("getThumbnail-null", "failed to get thumbnail for uri=$uri", errorDetails) + result.error("getThumbnail-null", "failed to get thumbnail for mimeType=$mimeType uri=$uri", errorDetails) } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt index 8e9ae3001..8fc0b0f9c 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/ImageByteStreamHandler.kt @@ -95,22 +95,22 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen } if (isVideo(mimeType)) { - streamVideoByGlide(uri) + streamVideoByGlide(uri, mimeType) } else if (!isSupportedByFlutter(mimeType, rotationDegrees, isFlipped)) { // decode exotic format on platform side, then encode it in portable format for Flutter streamImageByGlide(uri, pageId, mimeType, rotationDegrees, isFlipped) } else { // to be decoded by Flutter - streamImageAsIs(uri) + streamImageAsIs(uri, mimeType) } endOfStream() } - private fun streamImageAsIs(uri: Uri) { + private fun streamImageAsIs(uri: Uri, mimeType: String) { try { StorageUtils.openInputStream(activity, uri)?.use { input -> streamBytes(input) } } catch (e: IOException) { - error("streamImage-image-read-exception", "failed to get image from uri=$uri", e.message) + error("streamImage-image-read-exception", "failed to get image for mimeType=$mimeType uri=$uri", e.message) } } @@ -137,16 +137,16 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen if (bitmap != null) { success(bitmap.getBytes(MimeTypes.canHaveAlpha(mimeType), recycle = false)) } else { - error("streamImage-image-decode-null", "failed to get image from uri=$uri", null) + error("streamImage-image-decode-null", "failed to get image for mimeType=$mimeType uri=$uri", null) } } catch (e: Exception) { - error("streamImage-image-decode-exception", "failed to get image from uri=$uri model=$model", toErrorDetails(e)) + error("streamImage-image-decode-exception", "failed to get image for mimeType=$mimeType uri=$uri model=$model", toErrorDetails(e)) } finally { Glide.with(activity).clear(target) } } - private suspend fun streamVideoByGlide(uri: Uri) { + private suspend fun streamVideoByGlide(uri: Uri, mimeType: String) { val target = Glide.with(activity) .asBitmap() .apply(glideOptions) @@ -158,10 +158,10 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen if (bitmap != null) { success(bitmap.getBytes(canHaveAlpha = false, recycle = false)) } else { - error("streamImage-video-null", "failed to get image from uri=$uri", null) + error("streamImage-video-null", "failed to get image for mimeType=$mimeType uri=$uri", null) } } catch (e: Exception) { - error("streamImage-video-exception", "failed to get image from uri=$uri", e.message) + error("streamImage-video-exception", "failed to get image for mimeType=$mimeType uri=$uri", e.message) } finally { Glide.with(activity).clear(target) } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt index e546681c8..5af94b640 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt @@ -161,7 +161,7 @@ abstract class ImageProvider { if (MimeTypes.needRotationAfterGlide(sourceMimeType)) { bitmap = BitmapUtils.applyExifOrientation(context, bitmap, sourceEntry.rotationDegrees, sourceEntry.isFlipped) } - bitmap ?: throw Exception("failed to get image from uri=$sourceUri page=$pageId") + bitmap ?: throw Exception("failed to get image for mimeType=$sourceMimeType uri=$sourceUri page=$pageId") destinationDocFile.openOutputStream().use { output -> if (exportMimeType == MimeTypes.BMP) { 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 64b605da8..c0426c55c 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 @@ -37,6 +37,7 @@ object MimeTypes { private const val VIDEO = "video" + private const val MKV = "video/x-matroska" private const val MP2T = "video/mp2t" private const val MP2TS = "video/mp2ts" const val MP4 = "video/mp4" @@ -72,7 +73,7 @@ object MimeTypes { // as of `metadata-extractor` v2.14.0 fun isSupportedByMetadataExtractor(mimeType: String) = when (mimeType) { - DJVU, WBMP, MP2T, MP2TS, OGV, WEBM -> false + DJVU, WBMP, MKV, MP2T, MP2TS, OGV, WEBM -> false else -> true } diff --git a/lib/model/entry.dart b/lib/model/entry.dart index 89c12f1cc..9c036e9a6 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry.dart @@ -43,15 +43,6 @@ class AvesEntry { final AChangeNotifier imageChangeNotifier = AChangeNotifier(), metadataChangeNotifier = AChangeNotifier(), addressChangeNotifier = AChangeNotifier(); - // TODO TLAD make it dynamic if it depends on OS/lib versions - static const List undecodable = [ - MimeTypes.art, - MimeTypes.crw, - MimeTypes.djvu, - MimeTypes.psdVnd, - MimeTypes.psdX, - ]; - AvesEntry({ required this.uri, required String? path, @@ -74,7 +65,7 @@ class AvesEntry { this.durationMillis = durationMillis; } - bool get canDecode => !undecodable.contains(mimeType); + bool get canDecode => !MimeTypes.undecodableImages.contains(mimeType); bool get canHaveAlpha => MimeTypes.alphaImages.contains(mimeType); diff --git a/lib/ref/mime_types.dart b/lib/ref/mime_types.dart index c593865d0..302e3ddcf 100644 --- a/lib/ref/mime_types.dart +++ b/lib/ref/mime_types.dart @@ -41,18 +41,29 @@ class MimeTypes { static const anyVideo = 'video/*'; static const avi = 'video/avi'; + static const mkv = 'video/x-matroska'; static const mov = 'video/quicktime'; static const mp2t = 'video/mp2t'; // .m2ts static const mp4 = 'video/mp4'; + static const ogg = 'video/ogg'; static const json = 'application/json'; // groups // formats that support transparency - static const List alphaImages = [bmp, gif, ico, png, svg, tiff, webp]; + static const Set alphaImages = {bmp, gif, ico, png, svg, tiff, webp}; - static const List rawImages = [arw, cr2, crw, dcr, dng, erf, k25, kdc, mrw, nef, nrw, orf, pef, raf, raw, rw2, sr2, srf, srw, x3f]; + static const Set rawImages = {arw, cr2, crw, dcr, dng, erf, k25, kdc, mrw, nef, nrw, orf, pef, raf, raw, rw2, sr2, srf, srw, x3f}; + + // TODO TLAD make it dynamic if it depends on OS/lib versions + static const Set undecodableImages = {art, crw, djvu, psdVnd, psdX}; + + static const Set _knownOpaqueImages = {heic, heif, jpeg}; + + static const Set _knownVideos = {avi, mkv, mov, mp2t, mp4, ogg}; + + static final Set knownMediaTypes = {..._knownOpaqueImages, ...alphaImages, ...rawImages, ...undecodableImages, ..._knownVideos}; static bool isImage(String mimeType) => mimeType.startsWith('image'); diff --git a/lib/services/image_file_service.dart b/lib/services/image_file_service.dart index be7749814..c7ebb2c79 100644 --- a/lib/services/image_file_service.dart +++ b/lib/services/image_file_service.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'dart:ui'; import 'package:aves/model/entry.dart'; +import 'package:aves/ref/mime_types.dart'; import 'package:aves/services/image_op_events.dart'; import 'package:aves/services/output_buffer.dart'; import 'package:aves/services/service_policy.dart'; @@ -261,7 +262,9 @@ class PlatformImageFileService implements ImageFileService { }); if (result != null) return result as Uint8List; } on PlatformException catch (e, stack) { - await reportService.recordError(e, stack); + if (!MimeTypes.knownMediaTypes.contains(mimeType)) { + await reportService.recordError(e, stack); + } } return Uint8List(0); },