read files with uri only, and fix to handle unknown MediaMetadataRetriever issues
This commit is contained in:
parent
cbacb923e7
commit
a6eeba7744
6 changed files with 36 additions and 59 deletions
|
@ -8,6 +8,7 @@ import android.content.pm.PackageManager;
|
|||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -29,12 +30,15 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import deckers.thibault.aves.utils.Utils;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
import static com.bumptech.glide.request.RequestOptions.centerCropTransform;
|
||||
|
||||
public class AppAdapterHandler implements MethodChannel.MethodCallHandler {
|
||||
private static final String LOG_TAG = Utils.createLogTag(AppAdapterHandler.class);
|
||||
|
||||
public static final String CHANNEL = "deckers.thibault/aves/app";
|
||||
|
||||
private Context context;
|
||||
|
@ -162,11 +166,11 @@ public class AppAdapterHandler implements MethodChannel.MethodCallHandler {
|
|||
data = stream.toByteArray();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.w(LOG_TAG, "failed to decode app icon for packageName=" + packageName, e);
|
||||
}
|
||||
Glide.with(context).clear(target);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
Log.w(LOG_TAG, "failed to get app info for packageName=" + packageName, e);
|
||||
return;
|
||||
}
|
||||
if (data != null) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.net.Uri;
|
|||
import android.os.Build;
|
||||
import android.provider.MediaStore;
|
||||
import android.text.format.Formatter;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
@ -30,7 +31,6 @@ import com.drew.metadata.gif.GifAnimationDirectory;
|
|||
import com.drew.metadata.webp.WebpDirectory;
|
||||
import com.drew.metadata.xmp.XmpDirectory;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
@ -42,10 +42,13 @@ import deckers.thibault.aves.utils.Constants;
|
|||
import deckers.thibault.aves.utils.MetadataHelper;
|
||||
import deckers.thibault.aves.utils.MimeTypes;
|
||||
import deckers.thibault.aves.utils.StorageUtils;
|
||||
import deckers.thibault.aves.utils.Utils;
|
||||
import io.flutter.plugin.common.MethodCall;
|
||||
import io.flutter.plugin.common.MethodChannel;
|
||||
|
||||
public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
||||
private static final String LOG_TAG = Utils.createLogTag(MetadataHandler.class);
|
||||
|
||||
public static final String CHANNEL = "deckers.thibault/aves/metadata";
|
||||
|
||||
// catalog metadata
|
||||
|
@ -105,12 +108,11 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
|
||||
private void getAllMetadata(MethodCall call, MethodChannel.Result result) {
|
||||
String path = call.argument("path");
|
||||
String uri = call.argument("uri");
|
||||
|
||||
Map<String, Map<String, String>> metadataMap = new HashMap<>();
|
||||
|
||||
try (InputStream is = StorageUtils.openInputStream(context, Uri.parse(uri), path)) {
|
||||
try (InputStream is = StorageUtils.openInputStream(context, Uri.parse(uri))) {
|
||||
Metadata metadata = ImageMetadataReader.readMetadata(is);
|
||||
for (Directory dir : metadata.getDirectories()) {
|
||||
if (dir.getTagCount() > 0) {
|
||||
|
@ -136,7 +138,7 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
}
|
||||
} catch (XMPException e) {
|
||||
e.printStackTrace();
|
||||
Log.w(LOG_TAG, "failed to read XMP directory for uri=" + uri, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,15 +146,12 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
result.success(metadataMap);
|
||||
} catch (ImageProcessingException e) {
|
||||
getAllVideoMetadataFallback(call, result);
|
||||
} catch (FileNotFoundException e) {
|
||||
result.error("getAllMetadata-filenotfound", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
result.error("getAllMetadata-exception", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage());
|
||||
result.error("getAllMetadata-exception", "failed to get metadata for uri=" + uri, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void getAllVideoMetadataFallback(MethodCall call, MethodChannel.Result result) {
|
||||
String path = call.argument("path");
|
||||
String uri = call.argument("uri");
|
||||
|
||||
Map<String, Map<String, String>> metadataMap = new HashMap<>();
|
||||
|
@ -160,7 +159,7 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
// unnamed fallback directory
|
||||
metadataMap.put("", dirMap);
|
||||
|
||||
MediaMetadataRetriever retriever = StorageUtils.openMetadataRetriever(context, Uri.parse(uri), path);
|
||||
MediaMetadataRetriever retriever = StorageUtils.openMetadataRetriever(context, Uri.parse(uri));
|
||||
try {
|
||||
for (Map.Entry<Integer, String> kv : Constants.MEDIA_METADATA_KEYS.entrySet()) {
|
||||
Integer key = kv.getKey();
|
||||
|
@ -179,7 +178,7 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
result.success(metadataMap);
|
||||
} catch (Exception e) {
|
||||
result.error("getAllVideoMetadataFallback-exception", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage());
|
||||
result.error("getAllVideoMetadataFallback-exception", "failed to get metadata for uri=" + uri, e.getMessage());
|
||||
} finally {
|
||||
// cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs
|
||||
retriever.release();
|
||||
|
@ -188,12 +187,11 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
|
||||
private void getCatalogMetadata(MethodCall call, MethodChannel.Result result) {
|
||||
String mimeType = call.argument("mimeType");
|
||||
String path = call.argument("path");
|
||||
String uri = call.argument("uri");
|
||||
|
||||
Map<String, Object> metadataMap = new HashMap<>();
|
||||
|
||||
try (InputStream is = StorageUtils.openInputStream(context, Uri.parse(uri), path)) {
|
||||
try (InputStream is = StorageUtils.openInputStream(context, Uri.parse(uri))) {
|
||||
if (!MimeTypes.MP2T.equals(mimeType)) {
|
||||
Metadata metadata = ImageMetadataReader.readMetadata(is);
|
||||
|
||||
|
@ -233,7 +231,7 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
putLocalizedTextFromXmp(metadataMap, KEY_XMP_TITLE_DESCRIPTION, xmpMeta, XMP_DESCRIPTION_PROP_NAME);
|
||||
}
|
||||
} catch (XMPException e) {
|
||||
e.printStackTrace();
|
||||
Log.w(LOG_TAG, "failed to read XMP directory for uri=" + uri, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,7 +249,7 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
|
||||
if (isVideo(mimeType)) {
|
||||
MediaMetadataRetriever retriever = StorageUtils.openMetadataRetriever(context, Uri.parse(uri), path);
|
||||
MediaMetadataRetriever retriever = StorageUtils.openMetadataRetriever(context, Uri.parse(uri));
|
||||
try {
|
||||
String dateString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE);
|
||||
String rotationString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
|
||||
|
@ -287,25 +285,20 @@ 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());
|
||||
result.error("getCatalogMetadata-exception", "failed to get video metadata for uri=" + uri, e.getMessage());
|
||||
} finally {
|
||||
// cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs
|
||||
retriever.release();
|
||||
}
|
||||
}
|
||||
result.success(metadataMap);
|
||||
} catch (ImageProcessingException e) {
|
||||
result.error("getCatalogMetadata-imageprocessing", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage());
|
||||
} catch (FileNotFoundException e) {
|
||||
result.error("getCatalogMetadata-filenotfound", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
result.error("getCatalogMetadata-exception", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage());
|
||||
result.error("getCatalogMetadata-exception", "failed to get metadata for uri=" + uri, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void getOverlayMetadata(MethodCall call, MethodChannel.Result result) {
|
||||
String mimeType = call.argument("mimeType");
|
||||
String path = call.argument("path");
|
||||
String uri = call.argument("uri");
|
||||
|
||||
Map<String, Object> metadataMap = new HashMap<>();
|
||||
|
@ -315,7 +308,7 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
return;
|
||||
}
|
||||
|
||||
try (InputStream is = StorageUtils.openInputStream(context, Uri.parse(uri), path)) {
|
||||
try (InputStream is = StorageUtils.openInputStream(context, Uri.parse(uri))) {
|
||||
Metadata metadata = ImageMetadataReader.readMetadata(is);
|
||||
ExifSubIFDDirectory directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
|
||||
if (directory != null) {
|
||||
|
@ -327,12 +320,8 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
|
|||
}
|
||||
}
|
||||
result.success(metadataMap);
|
||||
} catch (ImageProcessingException e) {
|
||||
result.error("getOverlayMetadata-imageprocessing", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage());
|
||||
} catch (FileNotFoundException e) {
|
||||
result.error("getOverlayMetadata-filenotfound", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage());
|
||||
} catch (Exception e) {
|
||||
result.error("getOverlayMetadata-exception", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage());
|
||||
result.error("getOverlayMetadata-exception", "failed to get metadata for uri=" + uri, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ class VideoThumbnailFetcher implements DataFetcher<InputStream> {
|
|||
|
||||
@Override
|
||||
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
|
||||
MediaMetadataRetriever retriever = StorageUtils.openMetadataRetriever(model.getContext(), model.getUri(), null);
|
||||
MediaMetadataRetriever retriever = StorageUtils.openMetadataRetriever(model.getContext(), model.getUri());
|
||||
try {
|
||||
byte[] picture = retriever.getEmbeddedPicture();
|
||||
if (picture != null) {
|
||||
|
|
|
@ -119,7 +119,7 @@ public class ImageEntry {
|
|||
|
||||
// metadata retrieval
|
||||
|
||||
// expects entry with: uri/path, mimeType
|
||||
// expects entry with: uri, mimeType
|
||||
// finds: width, height, orientation/rotation, date, title, duration
|
||||
public ImageEntry fillPreCatalogMetadata(Context context) {
|
||||
fillByMediaMetadataRetriever(context);
|
||||
|
@ -130,10 +130,10 @@ public class ImageEntry {
|
|||
return this;
|
||||
}
|
||||
|
||||
// expects entry with: uri/path, mimeType
|
||||
// expects entry with: uri, mimeType
|
||||
// finds: width, height, orientation/rotation, date, title, duration
|
||||
private void fillByMediaMetadataRetriever(Context context) {
|
||||
MediaMetadataRetriever retriever = StorageUtils.openMetadataRetriever(context, uri, path);
|
||||
MediaMetadataRetriever retriever = StorageUtils.openMetadataRetriever(context, uri);
|
||||
try {
|
||||
String width = null, height = null, rotation = null, durationMillis = null;
|
||||
if (isImage()) {
|
||||
|
@ -180,12 +180,12 @@ public class ImageEntry {
|
|||
}
|
||||
}
|
||||
|
||||
// expects entry with: uri/path, mimeType
|
||||
// expects entry with: uri, mimeType
|
||||
// finds: width, height, orientation, date
|
||||
private void fillByMetadataExtractor(Context context) {
|
||||
if (MimeTypes.SVG.equals(mimeType)) return;
|
||||
|
||||
try (InputStream is = StorageUtils.openInputStream(context, uri, path)) {
|
||||
try (InputStream is = StorageUtils.openInputStream(context, uri)) {
|
||||
Metadata metadata = ImageMetadataReader.readMetadata(is);
|
||||
|
||||
if (MimeTypes.JPEG.equals(mimeType)) {
|
||||
|
@ -242,12 +242,12 @@ public class ImageEntry {
|
|||
}
|
||||
}
|
||||
|
||||
// expects entry with: uri/path
|
||||
// expects entry with: uri
|
||||
// finds: width, height
|
||||
private void fillByBitmapDecode(Context context) {
|
||||
if (MimeTypes.SVG.equals(mimeType)) return;
|
||||
|
||||
try (InputStream is = StorageUtils.openInputStream(context, uri, path)) {
|
||||
try (InputStream is = StorageUtils.openInputStream(context, uri)) {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inJustDecodeBounds = true;
|
||||
BitmapFactory.decodeStream(is, null, options);
|
||||
|
|
|
@ -42,41 +42,28 @@ public class StorageUtils {
|
|||
return uri != null && ContentResolver.SCHEME_CONTENT.equalsIgnoreCase(uri.getScheme()) && MediaStore.AUTHORITY.equalsIgnoreCase(uri.getHost());
|
||||
}
|
||||
|
||||
public static InputStream openInputStream(Context context, Uri uri, String path) throws FileNotFoundException {
|
||||
public static InputStream openInputStream(Context context, Uri uri) throws FileNotFoundException {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// we get a permission denial if we require original from a provider other than the media store
|
||||
if (isMediaStoreContentUri(uri)) {
|
||||
uri = MediaStore.setRequireOriginal(uri);
|
||||
}
|
||||
return context.getContentResolver().openInputStream(uri);
|
||||
}
|
||||
|
||||
// on Android <Q, we directly work with file paths if possible,
|
||||
// as `FileInputStream` is faster than input stream from `ContentResolver`
|
||||
return path != null ? new FileInputStream(path) : context.getContentResolver().openInputStream(uri);
|
||||
return context.getContentResolver().openInputStream(uri);
|
||||
}
|
||||
|
||||
public static MediaMetadataRetriever openMetadataRetriever(Context context, Uri uri, String path) {
|
||||
public static MediaMetadataRetriever openMetadataRetriever(Context context, Uri uri) {
|
||||
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
|
||||
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// we get a permission denial if we require original from a provider other than the media store
|
||||
if (isMediaStoreContentUri(uri)) {
|
||||
uri = MediaStore.setRequireOriginal(uri);
|
||||
}
|
||||
retriever.setDataSource(context, uri);
|
||||
return retriever;
|
||||
}
|
||||
|
||||
// on Android <Q, we directly work with file paths if possible
|
||||
if (path != null) {
|
||||
retriever.setDataSource(path);
|
||||
} else {
|
||||
retriever.setDataSource(context, uri);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(LOG_TAG, "failed to open MediaMetadataRetriever for uri=" + uri + ", path=" + path);
|
||||
retriever.setDataSource(context, uri);
|
||||
} catch (Exception e) {
|
||||
Log.w(LOG_TAG, "failed to open MediaMetadataRetriever for uri=" + uri, e);
|
||||
}
|
||||
return retriever;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ class MetadataService {
|
|||
try {
|
||||
final result = await platform.invokeMethod('getAllMetadata', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'path': entry.path,
|
||||
'uri': entry.uri,
|
||||
});
|
||||
return result as Map;
|
||||
|
@ -40,7 +39,6 @@ class MetadataService {
|
|||
// 'xmpTitleDescription': XMP title or XMP description (string)
|
||||
final result = await platform.invokeMethod('getCatalogMetadata', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'path': entry.path,
|
||||
'uri': entry.uri,
|
||||
}) as Map;
|
||||
result['contentId'] = entry.contentId;
|
||||
|
@ -61,7 +59,6 @@ class MetadataService {
|
|||
// return map with string descriptions for: 'aperture' 'exposureTime' 'focalLength' 'iso'
|
||||
final result = await platform.invokeMethod('getOverlayMetadata', <String, dynamic>{
|
||||
'mimeType': entry.mimeType,
|
||||
'path': entry.path,
|
||||
'uri': entry.uri,
|
||||
}) as Map;
|
||||
return OverlayMetadata.fromMap(result);
|
||||
|
|
Loading…
Reference in a new issue