metadata: use URI when path is unavailable

This commit is contained in:
Thibault Deckers 2020-03-17 12:12:31 +09:00
parent b0699df136
commit 8d798c6f08
5 changed files with 131 additions and 17 deletions

View file

@ -2,6 +2,7 @@ package deckers.thibault.aves.channelhandlers;
import android.content.Context;
import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.text.format.Formatter;
import androidx.annotation.NonNull;
@ -71,9 +72,15 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
return mimeType != null && mimeType.startsWith(Constants.MIME_VIDEO);
}
private InputStream getInputStream(String path, String uri) throws FileNotFoundException {
// FileInputStream is faster than input stream from ContentResolver
return path != null ? new FileInputStream(path) : context.getContentResolver().openInputStream(Uri.parse(uri));
}
private void getAllMetadata(MethodCall call, MethodChannel.Result result) {
String path = call.argument("path");
try (InputStream is = new FileInputStream(path)) {
String uri = call.argument("uri");
try (InputStream is = getInputStream(path, uri)) {
Map<String, Map<String, String>> metadataMap = new HashMap<>();
Metadata metadata = ImageMetadataReader.readMetadata(is);
for (Directory dir : metadata.getDirectories()) {
@ -109,14 +116,15 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
} catch (ImageProcessingException e) {
getAllVideoMetadataFallback(call, result);
} catch (FileNotFoundException e) {
result.error("getAllMetadata-filenotfound", "failed to get metadata for path=" + path, e.getMessage());
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 path=" + path, e.getMessage());
result.error("getAllMetadata-exception", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage());
}
}
private void getAllVideoMetadataFallback(MethodCall call, MethodChannel.Result result) {
String path = call.argument("path");
String uri = call.argument("uri");
try {
Map<String, Map<String, String>> metadataMap = new HashMap<>();
Map<String, String> dirMap = new HashMap<>();
@ -124,7 +132,11 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
metadataMap.put("", dirMap);
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(path);
if (path != null) {
retriever.setDataSource(path);
} else {
retriever.setDataSource(context, Uri.parse(uri));
}
for (Map.Entry<Integer, String> kv : Constants.MEDIA_METADATA_KEYS.entrySet()) {
Integer key = kv.getKey();
String value = retriever.extractMetadata(key);
@ -144,14 +156,15 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
result.success(metadataMap);
} catch (Exception e) {
result.error("getAllVideoMetadataFallback-exception", "failed to get metadata for path=" + path, e.getMessage());
result.error("getAllVideoMetadataFallback-exception", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage());
}
}
private void getCatalogMetadata(MethodCall call, MethodChannel.Result result) {
String path = call.argument("path");
String mimeType = call.argument("mimeType");
try (InputStream is = new FileInputStream(path)) {
String path = call.argument("path");
String uri = call.argument("uri");
try (InputStream is = getInputStream(path, uri)) {
Map<String, Object> metadataMap = new HashMap<>();
if (!Constants.MIME_MP2T.equalsIgnoreCase(mimeType)) {
Metadata metadata = ImageMetadataReader.readMetadata(is);
@ -197,7 +210,11 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
if (isVideo(call.argument("mimeType"))) {
try {
MediaMetadataRetriever retriever = new MediaMetadataRetriever();
retriever.setDataSource(path);
if (path != null) {
retriever.setDataSource(path);
} else {
retriever.setDataSource(context, Uri.parse(uri));
}
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);
@ -233,16 +250,16 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
}
}
} catch (Exception e) {
result.error("getCatalogMetadata-exception", "failed to get video metadata for path=" + path, e.getMessage());
result.error("getCatalogMetadata-exception", "failed to get video metadata for uri=" + uri + ", path=" + path, e.getMessage());
}
}
result.success(metadataMap);
} catch (ImageProcessingException e) {
result.error("getCatalogMetadata-imageprocessing", "failed to get metadata for path=" + path, e.getMessage());
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 path=" + path, e.getMessage());
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 path=" + path, e.getMessage());
result.error("getCatalogMetadata-exception", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage());
}
}
@ -255,7 +272,8 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
}
String path = call.argument("path");
try (InputStream is = new FileInputStream(path)) {
String uri = call.argument("uri");
try (InputStream is = getInputStream(path, uri)) {
Metadata metadata = ImageMetadataReader.readMetadata(is);
ExifSubIFDDirectory directory = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
if (directory != null) {
@ -274,11 +292,11 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
}
result.success(metadataMap);
} catch (ImageProcessingException e) {
result.error("getOverlayMetadata-imageprocessing", "failed to get metadata for path=" + path, e.getMessage());
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 path=" + path, e.getMessage());
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 path=" + path, e.getMessage());
result.error("getOverlayMetadata-exception", "failed to get metadata for uri=" + uri + ", path=" + path, e.getMessage());
}
}
}

View file

@ -21,6 +21,8 @@ public class ImageProviderFactory {
return new MediaStoreImageProvider();
// case Constants.DOWNLOADS_AUTHORITY:
// return new DownloadImageProvider();
default:
return new UnknownContentImageProvider();
}
}
return null;

View file

@ -0,0 +1,87 @@
package deckers.thibault.aves.model.provider;
import android.app.Activity;
import android.graphics.BitmapFactory;
import android.net.Uri;
import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.drew.metadata.jpeg.JpegDirectory;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import static deckers.thibault.aves.utils.MetadataHelper.getOrientationDegreesForExifCode;
class UnknownContentImageProvider extends ImageProvider {
@Override
public void fetchSingle(final Activity activity, final Uri uri, final String mimeType, final ImageOpCallback callback) {
int width = 0, height = 0, orientationDegrees = 0;
Long sourceDateTakenMillis = null;
// check metadata first
try (InputStream is = activity.getContentResolver().openInputStream(uri)) {
Metadata metadata = ImageMetadataReader.readMetadata(is);
JpegDirectory jpegDir = metadata.getFirstDirectoryOfType(JpegDirectory.class);
if (jpegDir != null) {
if (jpegDir.containsTag(JpegDirectory.TAG_IMAGE_WIDTH)) {
width = jpegDir.getInt(JpegDirectory.TAG_IMAGE_WIDTH);
}
if (jpegDir.containsTag(JpegDirectory.TAG_IMAGE_HEIGHT)) {
height = jpegDir.getInt(JpegDirectory.TAG_IMAGE_HEIGHT);
}
}
ExifIFD0Directory exifDir = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
if (exifDir != null) {
if (exifDir.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
orientationDegrees = getOrientationDegreesForExifCode(exifDir.getInt(ExifIFD0Directory.TAG_ORIENTATION));
}
if (exifDir.containsTag(ExifIFD0Directory.TAG_DATETIME)) {
sourceDateTakenMillis = exifDir.getDate(ExifIFD0Directory.TAG_DATETIME, null, TimeZone.getDefault()).getTime();
}
}
} catch (IOException | ImageProcessingException | MetadataException e) {
// ignore
}
// fallback to decoding the image bounds
if (width <= 0 || height <= 0) {
try (InputStream is = activity.getContentResolver().openInputStream(uri)) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
width = options.outWidth;
height = options.outHeight;
} catch (IOException e) {
// ignore
}
}
if (width <= 0 || height <= 0) {
callback.onFailure();
return;
}
Map<String, Object> entry = new HashMap<>();
entry.put("uri", uri.toString());
entry.put("path", null);
entry.put("contentId", null);
entry.put("mimeType", mimeType);
entry.put("width", width);
entry.put("height", height);
entry.put("orientationDegrees", orientationDegrees);
entry.put("sizeBytes", null);
entry.put("title", null);
entry.put("dateModifiedSecs", null);
entry.put("sourceDateTakenMillis", sourceDateTakenMillis);
entry.put("bucketDisplayName", null);
entry.put("durationMillis", null);
callback.onSuccess(entry);
}
}

View file

@ -12,6 +12,7 @@ class MetadataService {
final result = await platform.invokeMethod('getAllMetadata', <String, dynamic>{
'mimeType': entry.mimeType,
'path': entry.path,
'uri': entry.uri,
});
return result as Map;
} on PlatformException catch (e) {
@ -30,6 +31,7 @@ class MetadataService {
final result = await platform.invokeMethod('getCatalogMetadata', <String, dynamic>{
'mimeType': entry.mimeType,
'path': entry.path,
'uri': entry.uri,
}) as Map;
result['contentId'] = entry.contentId;
return CatalogMetadata.fromMap(result);
@ -45,6 +47,7 @@ class MetadataService {
final result = await platform.invokeMethod('getOverlayMetadata', <String, dynamic>{
'mimeType': entry.mimeType,
'path': entry.path,
'uri': entry.uri,
}) as Map;
return OverlayMetadata.fromMap(result);
} on PlatformException catch (e) {

View file

@ -144,6 +144,10 @@ class _FullscreenBottomOverlayContent extends StatelessWidget {
builder: (c, orientation, child) {
final twoColumns = orientation == Orientation.landscape && maxWidth / 2 > _subRowMinWidth;
final subRowWidth = twoColumns ? min(_subRowMinWidth, maxWidth / 2) : maxWidth;
final positionTitle = [
if (position != null) position,
if (entry.title != null) entry.title,
].join(' ');
final hasShootingDetails = details != null && !details.isEmpty;
return Column(
mainAxisSize: MainAxisSize.min,
@ -151,7 +155,7 @@ class _FullscreenBottomOverlayContent extends StatelessWidget {
children: [
SizedBox(
width: maxWidth,
child: Text('${position != null ? '$position ' : ''}${entry.title ?? '?'}', strutStyle: Constants.overflowStrutStyle),
child: Text(positionTitle, strutStyle: Constants.overflowStrutStyle),
),
if (entry.hasGps)
Container(