diff --git a/android/app/src/main/java/deckers/thibault/aves/model/ImageEntry.java b/android/app/src/main/java/deckers/thibault/aves/model/ImageEntry.java index 82646575b..5b27b979d 100644 --- a/android/app/src/main/java/deckers/thibault/aves/model/ImageEntry.java +++ b/android/app/src/main/java/deckers/thibault/aves/model/ImageEntry.java @@ -17,6 +17,7 @@ import com.drew.metadata.MetadataException; import com.drew.metadata.avi.AviDirectory; import com.drew.metadata.exif.ExifIFD0Directory; import com.drew.metadata.jpeg.JpegDirectory; +import com.drew.metadata.mp4.Mp4Directory; import com.drew.metadata.mp4.media.Mp4VideoDirectory; import java.io.File; @@ -96,11 +97,15 @@ public class ImageEntry { return width != null && width > 0 && height != null && height > 0; } + private boolean hasDuration() { + return durationMillis != null && durationMillis > 0; + } + public String getFilename() { return path == null ? null : new File(path).getName(); } - public boolean isImage() { + private boolean isImage() { return mimeType.startsWith(MimeTypes.IMAGE); } @@ -123,7 +128,7 @@ public class ImageEntry { // finds: width, height, orientation/rotation, date, title, duration public ImageEntry fillPreCatalogMetadata(Context context) { fillByMediaMetadataRetriever(context); - if (hasSize()) return this; + if (hasSize() && (!isVideo() || hasDuration())) return this; fillByMetadataExtractor(context); if (hasSize()) return this; fillByBitmapDecode(context); @@ -218,6 +223,12 @@ public class ImageEntry { height = mp4VideoDir.getInt(Mp4VideoDirectory.TAG_HEIGHT); } } + Mp4Directory mp4Dir = metadata.getFirstDirectoryOfType(Mp4Directory.class); + if (mp4Dir != null) { + if (mp4Dir.containsTag(Mp4Directory.TAG_DURATION)) { + durationMillis = mp4Dir.getLong(Mp4Directory.TAG_DURATION); + } + } } else if (MimeTypes.AVI.equals(mimeType)) { AviDirectory aviDir = metadata.getFirstDirectoryOfType(AviDirectory.class); if (aviDir != null) { @@ -227,6 +238,9 @@ public class ImageEntry { if (aviDir.containsTag(AviDirectory.TAG_HEIGHT)) { height = aviDir.getInt(AviDirectory.TAG_HEIGHT); } + if (aviDir.containsTag(AviDirectory.TAG_DURATION)) { + durationMillis = aviDir.getLong(AviDirectory.TAG_DURATION); + } } } } catch (IOException | ImageProcessingException | MetadataException e) { diff --git a/android/app/src/main/java/deckers/thibault/aves/model/provider/MediaStoreImageProvider.java b/android/app/src/main/java/deckers/thibault/aves/model/provider/MediaStoreImageProvider.java index ce4948663..fa3793cfa 100644 --- a/android/app/src/main/java/deckers/thibault/aves/model/provider/MediaStoreImageProvider.java +++ b/android/app/src/main/java/deckers/thibault/aves/model/provider/MediaStoreImageProvider.java @@ -86,6 +86,8 @@ public class MediaStoreImageProvider extends ImageProvider { String orderBy = MediaStore.MediaColumns.DATE_TAKEN + " DESC"; int entryCount = 0; + final boolean needDuration = projection == VIDEO_PROJECTION; + try { Cursor cursor = context.getContentResolver().query(contentUri, projection, null, null, orderBy); if (cursor != null) { @@ -115,6 +117,7 @@ public class MediaStoreImageProvider extends ImageProvider { final String mimeType = cursor.getString(mimeTypeColumn); int width = cursor.getInt(widthColumn); int height = cursor.getInt(heightColumn); + long durationMillis = durationColumn != -1 ? cursor.getLong(durationColumn) : 0; Map entryMap = new HashMap() {{ put("uri", itemUri.toString()); @@ -126,14 +129,14 @@ public class MediaStoreImageProvider extends ImageProvider { put("dateModifiedSecs", cursor.getLong(dateModifiedColumn)); put("sourceDateTakenMillis", cursor.getLong(dateTakenColumn)); put("bucketDisplayName", cursor.getString(bucketDisplayNameColumn)); - put("durationMillis", durationColumn != -1 ? cursor.getLong(durationColumn) : 0); // only for map export put("contentId", contentId); }}; entryMap.put("width", width); entryMap.put("height", height); + entryMap.put("durationMillis", durationMillis); - if ((width <= 0 || height <= 0) && !MimeTypes.SVG.equals(mimeType)) { + if (((width <= 0 || height <= 0) && needSize(mimeType)) || (durationMillis == 0 && needDuration)) { // some images are incorrectly registered in the Media Store, // they are valid but miss some attributes, such as width, height, orientation ImageEntry entry = new ImageEntry(entryMap).fillPreCatalogMetadata(context); @@ -142,7 +145,7 @@ public class MediaStoreImageProvider extends ImageProvider { height = entry.height != null ? entry.height : 0; } - if ((width <= 0 || height <= 0) && !MimeTypes.SVG.equals(mimeType)) { + if ((width <= 0 || height <= 0) && needSize(mimeType)) { // this is probably not a real image, like "/storage/emulated/0", so we skip it Log.w(LOG_TAG, "failed to get size for uri=" + itemUri + ", path=" + path + ", mimeType=" + mimeType); } else { @@ -158,6 +161,10 @@ public class MediaStoreImageProvider extends ImageProvider { return entryCount; } + private boolean needSize(String mimeType) { + return !MimeTypes.SVG.equals(mimeType); + } + @Override public void delete(final Activity activity, final String path, final Uri uri, final ImageOpCallback callback) { // check write access permission to SD card diff --git a/lib/model/image_entry.dart b/lib/model/image_entry.dart index 48f62c85d..34db4140e 100644 --- a/lib/model/image_entry.dart +++ b/lib/model/image_entry.dart @@ -155,7 +155,12 @@ class ImageEntry { return d == null ? null : DateTime(d.year, d.month, d.day); } - String get durationText => formatDuration(Duration(milliseconds: durationMillis)); + String _durationText; + + String get durationText { + _durationText ??= formatDuration(Duration(milliseconds: durationMillis)); + return _durationText; + } bool get hasGps => isCatalogued && _catalogMetadata.latitude != null;