info: show picture embedded in videos

This commit is contained in:
Thibault Deckers 2020-09-28 14:14:27 +09:00
parent 097a051b37
commit 652a5383ea
6 changed files with 54 additions and 10 deletions

View file

@ -190,6 +190,9 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
case "getContentResolverMetadata": case "getContentResolverMetadata":
new Thread(() -> getContentResolverMetadata(call, new MethodResultWrapper(result))).start(); new Thread(() -> getContentResolverMetadata(call, new MethodResultWrapper(result))).start();
break; break;
case "getEmbeddedPictures":
new Thread(() -> getEmbeddedPictures(call, new MethodResultWrapper(result))).start();
break;
case "getExifThumbnails": case "getExifThumbnails":
new Thread(() -> getExifThumbnails(call, new MethodResultWrapper(result))).start(); new Thread(() -> getExifThumbnails(call, new MethodResultWrapper(result))).start();
break; break;
@ -415,7 +418,7 @@ public class MetadataHandler implements MethodChannel.MethodCallHandler {
metadataMap.put(KEY_LATITUDE, latitude); metadataMap.put(KEY_LATITUDE, latitude);
metadataMap.put(KEY_LONGITUDE, longitude); metadataMap.put(KEY_LONGITUDE, longitude);
} }
} catch (NumberFormatException ex) { } catch (NumberFormatException e) {
// ignore // 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<byte[]> 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) { private void getExifThumbnails(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
Uri uri = Uri.parse(call.argument("uri")); Uri uri = Uri.parse(call.argument("uri"));
List<byte[]> thumbnails = new ArrayList<>(); List<byte[]> thumbnails = new ArrayList<>();

View file

@ -40,8 +40,8 @@ class VideoThumbnailFetcher implements DataFetcher<InputStream> {
} }
callback.onDataReady(new ByteArrayInputStream(bos.toByteArray())); callback.onDataReady(new ByteArrayInputStream(bos.toByteArray()));
} }
} catch (Exception ex) { } catch (Exception e) {
callback.onLoadFailed(ex); callback.onLoadFailed(e);
} finally { } finally {
// cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs // cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs
retriever.release(); retriever.release();

View file

@ -55,7 +55,7 @@ public class MetadataHelper {
DateFormat parser = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.US); DateFormat parser = new SimpleDateFormat("yyyyMMdd'T'HHmmss", Locale.US);
parser.setTimeZone((timeZone != null) ? timeZone : TimeZone.getTimeZone("GMT")); parser.setTimeZone((timeZone != null) ? timeZone : TimeZone.getTimeZone("GMT"));
date = parser.parse(dateString); date = parser.parse(dateString);
} catch (ParseException ex) { } catch (ParseException e) {
// ignore // ignore
} }

View file

@ -89,6 +89,18 @@ class MetadataService {
return {}; return {};
} }
static Future<List<Uint8List>> getEmbeddedPictures(String uri) async {
try {
final result = await platform.invokeMethod('getEmbeddedPictures', <String, dynamic>{
'uri': uri,
});
return (result as List).cast<Uint8List>();
} on PlatformException catch (e) {
debugPrint('getEmbeddedPictures failed with code=${e.code}, exception=${e.message}, details=${e.details}');
}
return [];
}
static Future<List<Uint8List>> getExifThumbnails(String uri) async { static Future<List<Uint8List>> getExifThumbnails(String uri) async {
try { try {
final result = await platform.invokeMethod('getExifThumbnails', <String, dynamic>{ final result = await platform.invokeMethod('getExifThumbnails', <String, dynamic>{

View file

@ -36,8 +36,9 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto
static const int maxValueLength = 140; static const int maxValueLength = 140;
// directory names from metadata-extractor // directory names from metadata-extractor
static const exifThumbnailDirectory = 'Exif Thumbnail'; static const exifThumbnailDirectory = 'Exif Thumbnail'; // from metadata-extractor
static const xmpDirectory = 'XMP'; static const xmpDirectory = 'XMP'; // from metadata-extractor
static const videoDirectory = 'Video'; // additional generic video directory
@override @override
void initState() { void initState() {
@ -106,6 +107,7 @@ class _MetadataSectionSliverState extends State<MetadataSectionSliver> with Auto
SizedBox(height: 4), SizedBox(height: 4),
if (dir.name == exifThumbnailDirectory) MetadataThumbnails(source: MetadataThumbnailSource.exif, entry: entry), if (dir.name == exifThumbnailDirectory) MetadataThumbnails(source: MetadataThumbnailSource.exif, entry: entry),
if (dir.name == xmpDirectory) MetadataThumbnails(source: MetadataThumbnailSource.xmp, entry: entry), if (dir.name == xmpDirectory) MetadataThumbnails(source: MetadataThumbnailSource.xmp, entry: entry),
if (dir.name == videoDirectory) MetadataThumbnails(source: MetadataThumbnailSource.embedded, entry: entry),
Container( Container(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
padding: EdgeInsets.only(left: 8, right: 8, bottom: 8), padding: EdgeInsets.only(left: 8, right: 8, bottom: 8),

View file

@ -5,7 +5,7 @@ import 'package:aves/model/image_entry.dart';
import 'package:aves/services/metadata_service.dart'; import 'package:aves/services/metadata_service.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
enum MetadataThumbnailSource { exif, xmp } enum MetadataThumbnailSource { embedded, exif, xmp }
class MetadataThumbnails extends StatefulWidget { class MetadataThumbnails extends StatefulWidget {
final MetadataThumbnailSource source; final MetadataThumbnailSource source;
@ -24,15 +24,22 @@ class MetadataThumbnails extends StatefulWidget {
class _MetadataThumbnailsState extends State<MetadataThumbnails> { class _MetadataThumbnailsState extends State<MetadataThumbnails> {
Future<List<Uint8List>> _loader; Future<List<Uint8List>> _loader;
ImageEntry get entry => widget.entry;
String get uri => entry.uri;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
switch (widget.source) { switch (widget.source) {
case MetadataThumbnailSource.embedded:
_loader = MetadataService.getEmbeddedPictures(uri);
break;
case MetadataThumbnailSource.exif: case MetadataThumbnailSource.exif:
_loader = MetadataService.getExifThumbnails(widget.entry.uri); _loader = MetadataService.getExifThumbnails(uri);
break; break;
case MetadataThumbnailSource.xmp: case MetadataThumbnailSource.xmp:
_loader = MetadataService.getXmpThumbnails(widget.entry.uri); _loader = MetadataService.getXmpThumbnails(uri);
break; break;
} }
} }
@ -43,7 +50,7 @@ class _MetadataThumbnailsState extends State<MetadataThumbnails> {
future: _loader, future: _loader,
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasError && snapshot.connectionState == ConnectionState.done && snapshot.data.isNotEmpty) { 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; final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
return Container( return Container(
alignment: AlignmentDirectional.topStart, alignment: AlignmentDirectional.topStart,