From 652a5383ea3131072678095e556dcbdb45e9f3c8 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Mon, 28 Sep 2020 14:14:27 +0900 Subject: [PATCH] info: show picture embedded in videos --- .../aves/channel/calls/MetadataHandler.java | 25 ++++++++++++++++++- .../aves/decoder/VideoThumbnailFetcher.java | 4 +-- .../thibault/aves/utils/MetadataHelper.java | 2 +- lib/services/metadata_service.dart | 12 +++++++++ .../fullscreen/info/metadata_section.dart | 6 +++-- .../fullscreen/info/metadata_thumbnail.dart | 15 ++++++++--- 6 files changed, 54 insertions(+), 10 deletions(-) diff --git a/android/app/src/main/java/deckers/thibault/aves/channel/calls/MetadataHandler.java b/android/app/src/main/java/deckers/thibault/aves/channel/calls/MetadataHandler.java index b69a320f3..90e06dc34 100644 --- a/android/app/src/main/java/deckers/thibault/aves/channel/calls/MetadataHandler.java +++ b/android/app/src/main/java/deckers/thibault/aves/channel/calls/MetadataHandler.java @@ -190,6 +190,9 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler { case "getContentResolverMetadata": new Thread(() -> getContentResolverMetadata(call, new MethodResultWrapper(result))).start(); break; + case "getEmbeddedPictures": + new Thread(() -> getEmbeddedPictures(call, new MethodResultWrapper(result))).start(); + break; case "getExifThumbnails": new Thread(() -> getExifThumbnails(call, new MethodResultWrapper(result))).start(); break; @@ -415,7 +418,7 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler { metadataMap.put(KEY_LATITUDE, latitude); metadataMap.put(KEY_LONGITUDE, longitude); } - } catch (NumberFormatException ex) { + } catch (NumberFormatException e) { // ignore } } @@ -530,6 +533,26 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler { } } + private void getEmbeddedPictures(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + Uri uri = Uri.parse(call.argument("uri")); + List pictures = new ArrayList<>(); + MediaMetadataRetriever retriever = StorageUtils.openMetadataRetriever(context, uri); + if (retriever != null) { + try { + byte[] picture = retriever.getEmbeddedPicture(); + if (picture != null) { + pictures.add(picture); + } + } catch (Exception e) { + result.error("getVideoEmbeddedPictures-failure", "failed to get embedded picture for uri=" + uri, e); + } finally { + // cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs + retriever.release(); + } + } + result.success(pictures); + } + private void getExifThumbnails(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { Uri uri = Uri.parse(call.argument("uri")); List thumbnails = new ArrayList<>(); diff --git a/android/app/src/main/java/deckers/thibault/aves/decoder/VideoThumbnailFetcher.java b/android/app/src/main/java/deckers/thibault/aves/decoder/VideoThumbnailFetcher.java index 8ee573f45..9c357eb6f 100644 --- a/android/app/src/main/java/deckers/thibault/aves/decoder/VideoThumbnailFetcher.java +++ b/android/app/src/main/java/deckers/thibault/aves/decoder/VideoThumbnailFetcher.java @@ -40,8 +40,8 @@ class VideoThumbnailFetcher implements DataFetcher { } callback.onDataReady(new ByteArrayInputStream(bos.toByteArray())); } - } catch (Exception ex) { - callback.onLoadFailed(ex); + } catch (Exception e) { + callback.onLoadFailed(e); } finally { // cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs retriever.release(); diff --git a/android/app/src/main/java/deckers/thibault/aves/utils/MetadataHelper.java b/android/app/src/main/java/deckers/thibault/aves/utils/MetadataHelper.java index 5bc895ff7..21c601d99 100644 --- a/android/app/src/main/java/deckers/thibault/aves/utils/MetadataHelper.java +++ b/android/app/src/main/java/deckers/thibault/aves/utils/MetadataHelper.java @@ -55,7 +55,7 @@ public class MetadataHelper { DateFormat parser = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.US); parser.setTimeZone((timeZone != null) ? timeZone : TimeZone.getTimeZone("GMT")); date = parser.parse(dateString); - } catch (ParseException ex) { + } catch (ParseException e) { // ignore } diff --git a/lib/services/metadata_service.dart b/lib/services/metadata_service.dart index 276fc2a34..c728a0704 100644 --- a/lib/services/metadata_service.dart +++ b/lib/services/metadata_service.dart @@ -89,6 +89,18 @@ class MetadataService { return {}; } + static Future> getEmbeddedPictures(String uri) async { + try { + final result = await platform.invokeMethod('getEmbeddedPictures', { + 'uri': uri, + }); + return (result as List).cast(); + } on PlatformException catch (e) { + debugPrint('getEmbeddedPictures failed with code=${e.code}, exception=${e.message}, details=${e.details}'); + } + return []; + } + static Future> getExifThumbnails(String uri) async { try { final result = await platform.invokeMethod('getExifThumbnails', { diff --git a/lib/widgets/fullscreen/info/metadata_section.dart b/lib/widgets/fullscreen/info/metadata_section.dart index 004b6793e..03cdc0bc0 100644 --- a/lib/widgets/fullscreen/info/metadata_section.dart +++ b/lib/widgets/fullscreen/info/metadata_section.dart @@ -36,8 +36,9 @@ class _MetadataSectionSliverState extends State with Auto static const int maxValueLength = 140; // directory names from metadata-extractor - static const exifThumbnailDirectory = 'Exif Thumbnail'; - static const xmpDirectory = 'XMP'; + static const exifThumbnailDirectory = 'Exif Thumbnail'; // from metadata-extractor + static const xmpDirectory = 'XMP'; // from metadata-extractor + static const videoDirectory = 'Video'; // additional generic video directory @override void initState() { @@ -106,6 +107,7 @@ class _MetadataSectionSliverState extends State with Auto SizedBox(height: 4), if (dir.name == exifThumbnailDirectory) MetadataThumbnails(source: MetadataThumbnailSource.exif, entry: entry), if (dir.name == xmpDirectory) MetadataThumbnails(source: MetadataThumbnailSource.xmp, entry: entry), + if (dir.name == videoDirectory) MetadataThumbnails(source: MetadataThumbnailSource.embedded, entry: entry), Container( alignment: Alignment.topLeft, padding: EdgeInsets.only(left: 8, right: 8, bottom: 8), diff --git a/lib/widgets/fullscreen/info/metadata_thumbnail.dart b/lib/widgets/fullscreen/info/metadata_thumbnail.dart index 8599ae9d9..2727d9a0c 100644 --- a/lib/widgets/fullscreen/info/metadata_thumbnail.dart +++ b/lib/widgets/fullscreen/info/metadata_thumbnail.dart @@ -5,7 +5,7 @@ import 'package:aves/model/image_entry.dart'; import 'package:aves/services/metadata_service.dart'; import 'package:flutter/material.dart'; -enum MetadataThumbnailSource { exif, xmp } +enum MetadataThumbnailSource { embedded, exif, xmp } class MetadataThumbnails extends StatefulWidget { final MetadataThumbnailSource source; @@ -24,15 +24,22 @@ class MetadataThumbnails extends StatefulWidget { class _MetadataThumbnailsState extends State { Future> _loader; + ImageEntry get entry => widget.entry; + + String get uri => entry.uri; + @override void initState() { super.initState(); switch (widget.source) { + case MetadataThumbnailSource.embedded: + _loader = MetadataService.getEmbeddedPictures(uri); + break; case MetadataThumbnailSource.exif: - _loader = MetadataService.getExifThumbnails(widget.entry.uri); + _loader = MetadataService.getExifThumbnails(uri); break; case MetadataThumbnailSource.xmp: - _loader = MetadataService.getXmpThumbnails(widget.entry.uri); + _loader = MetadataService.getXmpThumbnails(uri); break; } } @@ -43,7 +50,7 @@ class _MetadataThumbnailsState extends State { future: _loader, builder: (context, snapshot) { if (!snapshot.hasError && snapshot.connectionState == ConnectionState.done && snapshot.data.isNotEmpty) { - final turns = (widget.entry.orientationDegrees / 90).round(); + final turns = (entry.orientationDegrees / 90).round(); final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; return Container( alignment: AlignmentDirectional.topStart,