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 377435503..b5b0946e9 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 @@ -222,7 +222,7 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler { if (dateString != null) { long dateMillis = MetadataHelper.parseVideoMetadataDate(dateString); - // some videos have an invalid default date (19040101T000000.000Z) that is before Epoch time + // some entries have an invalid default date (19040101T000000.000Z) that is before Epoch time if (dateMillis > 0) { metadataMap.put("dateMillis", dateMillis); } 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 4c1a46c38..4a79ce25c 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 @@ -2,7 +2,9 @@ package deckers.thibault.aves.model.provider; import android.app.Activity; import android.graphics.BitmapFactory; +import android.media.MediaMetadataRetriever; import android.net.Uri; +import android.os.Build; import com.drew.imaging.ImageMetadataReader; import com.drew.imaging.ImageProcessingException; @@ -17,39 +19,92 @@ import java.util.HashMap; import java.util.Map; import java.util.TimeZone; +import deckers.thibault.aves.utils.Constants; +import deckers.thibault.aves.utils.MetadataHelper; + 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; + int width = 0, height = 0; + Integer orientationDegrees = null; + Long sourceDateTakenMillis = null, durationMillis = null; + String title = 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); - } + // check first metadata with MediaMetadataRetriever + + try { + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + retriever.setDataSource(activity, uri); + + title = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE); + String dateString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE); + long dateMillis = MetadataHelper.parseVideoMetadataDate(dateString); + // some entries have an invalid default date (19040101T000000.000Z) that is before Epoch time + if (dateMillis > 0) { + sourceDateTakenMillis = dateMillis; } - 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(); + + String widthString = null, heightString = null, rotationString = null, durationMillisString = null; + if (mimeType.startsWith(Constants.MIME_IMAGE)) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + widthString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH); + heightString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT); + rotationString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION); } + } else if (mimeType.startsWith(Constants.MIME_VIDEO)) { + widthString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); + heightString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); + rotationString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); + durationMillisString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); } - } catch (IOException | ImageProcessingException | MetadataException e) { + if (widthString != null) { + width = Integer.parseInt(widthString); + } + if (heightString != null) { + height = Integer.parseInt(heightString); + } + if (rotationString != null) { + orientationDegrees = Integer.parseInt(rotationString); + } + if (durationMillisString != null) { + durationMillis = Long.parseLong(durationMillisString); + } + retriever.release(); + } catch (Exception e) { // ignore } + // fallback to metadata-extractor for known types + if (width <= 0 || height <= 0 || orientationDegrees == null || sourceDateTakenMillis == null) { + if (Constants.MIME_JPEG.equals(mimeType)) { + 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)) { @@ -75,13 +130,13 @@ class UnknownContentImageProvider extends ImageProvider { entry.put("mimeType", mimeType); entry.put("width", width); entry.put("height", height); - entry.put("orientationDegrees", orientationDegrees); + entry.put("orientationDegrees", orientationDegrees != null ? orientationDegrees : 0); entry.put("sizeBytes", null); - entry.put("title", null); + entry.put("title", title); entry.put("dateModifiedSecs", null); entry.put("sourceDateTakenMillis", sourceDateTakenMillis); entry.put("bucketDisplayName", null); - entry.put("durationMillis", null); + entry.put("durationMillis", durationMillis); callback.onSuccess(entry); } } diff --git a/lib/widgets/fullscreen/fullscreen_body.dart b/lib/widgets/fullscreen/fullscreen_body.dart index 6cb743817..b266aa856 100644 --- a/lib/widgets/fullscreen/fullscreen_body.dart +++ b/lib/widgets/fullscreen/fullscreen_body.dart @@ -244,6 +244,8 @@ class FullscreenBodyState extends State with SingleTickerProvide if (entry == null || !entry.isVideo) return; final path = entry.path; + if (path == null) return; + var controllerEntry = _videoControllers.firstWhere((kv) => kv.item1 == entry.path, orElse: () => null); if (controllerEntry != null) { _videoControllers.remove(controllerEntry); diff --git a/lib/widgets/fullscreen/info/basic_section.dart b/lib/widgets/fullscreen/info/basic_section.dart index 40f007f4d..1c115bd0f 100644 --- a/lib/widgets/fullscreen/info/basic_section.dart +++ b/lib/widgets/fullscreen/info/basic_section.dart @@ -28,7 +28,7 @@ class BasicSection extends StatelessWidget { InfoRow('Resolution', resolutionText), InfoRow('Size', entry.sizeBytes != null ? formatFilesize(entry.sizeBytes) : '?'), InfoRow('URI', entry.uri ?? '?'), - InfoRow('Path', entry.path ?? '?'), + if (entry.path != null) InfoRow('Path', entry.path), ], ); } diff --git a/lib/widgets/fullscreen/info/location_section.dart b/lib/widgets/fullscreen/info/location_section.dart index b25020f9f..d0803b77c 100644 --- a/lib/widgets/fullscreen/info/location_section.dart +++ b/lib/widgets/fullscreen/info/location_section.dart @@ -79,7 +79,7 @@ class _LocationSectionState extends State { child: SectionRow('Location'), ), ImageMap( - markerId: entry.path, + markerId: entry.uri ?? entry.path, latLng: LatLng( entry.latLng.item1, entry.latLng.item2,