From b9dd6becaf5ce06451ee689f3d2ccfd6bafb68d6 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Wed, 18 Mar 2020 10:43:46 +0900 Subject: [PATCH] fixed rotate with incorrect reported mime type --- .../aves/channelhandlers/MetadataHandler.java | 22 +++++----- .../aves/model/provider/ImageProvider.java | 41 ++++++++++++++++--- .../provider/UnknownContentImageProvider.java | 5 ++- 3 files changed, 51 insertions(+), 17 deletions(-) diff --git a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/MetadataHandler.java b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/MetadataHandler.java index b5b0946e9..501bc5fa4 100644 --- a/android/app/src/main/java/deckers/thibault/aves/channelhandlers/MetadataHandler.java +++ b/android/app/src/main/java/deckers/thibault/aves/channelhandlers/MetadataHandler.java @@ -125,13 +125,14 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler { private void getAllVideoMetadataFallback(MethodCall call, MethodChannel.Result result) { String path = call.argument("path"); String uri = call.argument("uri"); - try { - Map> metadataMap = new HashMap<>(); - Map dirMap = new HashMap<>(); - // unnamed fallback directory - metadataMap.put("", dirMap); - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + Map> metadataMap = new HashMap<>(); + Map dirMap = new HashMap<>(); + // unnamed fallback directory + metadataMap.put("", dirMap); + + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + try { if (path != null) { retriever.setDataSource(path); } else { @@ -152,11 +153,11 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler { dirMap.put(kv.getValue(), value); } } - retriever.release(); - result.success(metadataMap); } catch (Exception e) { result.error("getAllVideoMetadataFallback-exception", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage()); + } finally { + retriever.release(); } } @@ -208,8 +209,8 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler { } if (isVideo(call.argument("mimeType"))) { + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); try { - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); if (path != null) { retriever.setDataSource(path); } else { @@ -218,7 +219,6 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler { String dateString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE); String rotationString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); String locationString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION); - retriever.release(); if (dateString != null) { long dateMillis = MetadataHelper.parseVideoMetadataDate(dateString); @@ -251,6 +251,8 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler { } } catch (Exception e) { result.error("getCatalogMetadata-exception", "failed to get video metadata for uri=" + uri + ", path=" + path, e.getMessage()); + } finally { + retriever.release(); } } result.success(metadataMap); diff --git a/android/app/src/main/java/deckers/thibault/aves/model/provider/ImageProvider.java b/android/app/src/main/java/deckers/thibault/aves/model/provider/ImageProvider.java index 81f35d661..6292919de 100644 --- a/android/app/src/main/java/deckers/thibault/aves/model/provider/ImageProvider.java +++ b/android/app/src/main/java/deckers/thibault/aves/model/provider/ImageProvider.java @@ -3,6 +3,7 @@ package deckers.thibault.aves.model.provider; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -15,11 +16,17 @@ import android.os.ParcelFileDescriptor; import android.provider.MediaStore; import android.util.Log; +import com.drew.imaging.ImageMetadataReader; +import com.drew.imaging.ImageProcessingException; +import com.drew.metadata.Metadata; +import com.drew.metadata.file.FileTypeDirectory; + import java.io.File; import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; +import java.io.InputStream; import java.util.HashMap; import java.util.Map; @@ -109,20 +116,43 @@ public abstract class ImageProvider { }); } + // file extension is unreliable + // `context.getContentResolver().getType()` sometimes return incorrect value + // `MediaMetadataRetriever.setDataSource()` sometimes fail with `status = 0x80000000` + // so we check with `metadata-extractor` + private String getMimeType(final Context context, final Uri uri) { + try (InputStream is = context.getContentResolver().openInputStream(uri)) { + Metadata metadata = ImageMetadataReader.readMetadata(is); + FileTypeDirectory fileTypeDir = metadata.getFirstDirectoryOfType(FileTypeDirectory.class); + if (fileTypeDir != null) { + if (fileTypeDir.containsTag(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE)) { + return fileTypeDir.getString(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE); + } + } + } catch (IOException | ImageProcessingException e) { + Log.w(LOG_TAG, "failed to get mime type from metadata for uri=" + uri, e); + } + return null; + } + public void rotate(final Activity activity, final String path, final Uri uri, final String mimeType, final boolean clockwise, final ImageOpCallback callback) { - switch (mimeType) { + // the reported `mimeType` (e.g. from Media Store) is sometimes incorrect + // so we retrieve it again from the file metadata + String metadataMimeType = getMimeType(activity, uri); + switch (metadataMimeType != null ? metadataMimeType : mimeType) { case Constants.MIME_JPEG: - rotateJpeg(activity, path, uri, mimeType, clockwise, callback); + rotateJpeg(activity, path, uri, clockwise, callback); break; case Constants.MIME_PNG: - rotatePng(activity, path, uri, mimeType, clockwise, callback); + rotatePng(activity, path, uri, clockwise, callback); break; default: callback.onFailure(); } } - private void rotateJpeg(final Activity activity, final String path, final Uri uri, final String mimeType, boolean clockwise, final ImageOpCallback callback) { + private void rotateJpeg(final Activity activity, final String path, final Uri uri, boolean clockwise, final ImageOpCallback callback) { + final String mimeType = Constants.MIME_JPEG; String editablePath = path; boolean onSdCard = Env.isOnSdCard(activity, path); if (onSdCard) { @@ -197,7 +227,8 @@ public abstract class ImageProvider { } } - private void rotatePng(final Activity activity, final String path, final Uri uri, final String mimeType, boolean clockwise, final ImageOpCallback callback) { + private void rotatePng(final Activity activity, final String path, final Uri uri, boolean clockwise, final ImageOpCallback callback) { + final String mimeType = Constants.MIME_PNG; if (path == null) { callback.onFailure(); return; diff --git a/android/app/src/main/java/deckers/thibault/aves/model/provider/UnknownContentImageProvider.java b/android/app/src/main/java/deckers/thibault/aves/model/provider/UnknownContentImageProvider.java index 4a79ce25c..c4f263170 100644 --- a/android/app/src/main/java/deckers/thibault/aves/model/provider/UnknownContentImageProvider.java +++ b/android/app/src/main/java/deckers/thibault/aves/model/provider/UnknownContentImageProvider.java @@ -34,8 +34,8 @@ class UnknownContentImageProvider extends ImageProvider { // check first metadata with MediaMetadataRetriever + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); try { - MediaMetadataRetriever retriever = new MediaMetadataRetriever(); retriever.setDataSource(activity, uri); title = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); @@ -71,9 +71,10 @@ class UnknownContentImageProvider extends ImageProvider { if (durationMillisString != null) { durationMillis = Long.parseLong(durationMillisString); } - retriever.release(); } catch (Exception e) { // ignore + } finally { + retriever.release(); } // fallback to metadata-extractor for known types