guard against large tiff

This commit is contained in:
Thibault Deckers 2020-11-30 19:23:27 +09:00
parent 8c5a600151
commit 0d946b5a43
8 changed files with 142 additions and 78 deletions

View file

@ -52,6 +52,7 @@ import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.MimeTypes.isImage import deckers.thibault.aves.utils.MimeTypes.isImage
import deckers.thibault.aves.utils.MimeTypes.isMultimedia import deckers.thibault.aves.utils.MimeTypes.isMultimedia
import deckers.thibault.aves.utils.MimeTypes.isSupportedByExifInterface
import deckers.thibault.aves.utils.MimeTypes.isSupportedByMetadataExtractor import deckers.thibault.aves.utils.MimeTypes.isSupportedByMetadataExtractor
import deckers.thibault.aves.utils.MimeTypes.isVideo import deckers.thibault.aves.utils.MimeTypes.isVideo
import deckers.thibault.aves.utils.MimeTypes.tiffExtensionPattern import deckers.thibault.aves.utils.MimeTypes.tiffExtensionPattern
@ -86,6 +87,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
private fun getAllMetadata(call: MethodCall, result: MethodChannel.Result) { private fun getAllMetadata(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType") val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) } val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) { if (mimeType == null || uri == null) {
result.error("getAllMetadata-args", "failed because of missing arguments", null) result.error("getAllMetadata-args", "failed because of missing arguments", null)
return return
@ -95,10 +97,10 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
var foundExif = false var foundExif = false
var foundXmp = false var foundXmp = false
if (isSupportedByMetadataExtractor(mimeType)) { if (isSupportedByMetadataExtractor(mimeType, sizeBytes)) {
try { try {
StorageUtils.openInputStream(context, uri)?.use { input -> StorageUtils.openInputStream(context, uri)?.use { input ->
val metadata = ImageMetadataReader.readMetadata(input) val metadata = ImageMetadataReader.readMetadata(input, sizeBytes ?: -1)
foundExif = metadata.containsDirectoryOfType(ExifDirectoryBase::class.java) foundExif = metadata.containsDirectoryOfType(ExifDirectoryBase::class.java)
foundXmp = metadata.containsDirectoryOfType(XmpDirectory::class.java) foundXmp = metadata.containsDirectoryOfType(XmpDirectory::class.java)
@ -138,7 +140,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
} }
} }
if (!foundExif) { if (!foundExif && isSupportedByExifInterface(mimeType, sizeBytes)) {
// fallback to read EXIF via ExifInterface // fallback to read EXIF via ExifInterface
try { try {
StorageUtils.openInputStream(context, uri)?.use { input -> StorageUtils.openInputStream(context, uri)?.use { input ->
@ -192,12 +194,13 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
val mimeType = call.argument<String>("mimeType") val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) } val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val path = call.argument<String>("path") val path = call.argument<String>("path")
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) { if (mimeType == null || uri == null) {
result.error("getCatalogMetadata-args", "failed because of missing arguments", null) result.error("getCatalogMetadata-args", "failed because of missing arguments", null)
return return
} }
val metadataMap = HashMap(getCatalogMetadataByMetadataExtractor(uri, mimeType, path)) val metadataMap = HashMap(getCatalogMetadataByMetadataExtractor(uri, mimeType, path, sizeBytes))
if (isVideo(mimeType)) { if (isVideo(mimeType)) {
metadataMap.putAll(getVideoCatalogMetadataByMediaMetadataRetriever(uri)) metadataMap.putAll(getVideoCatalogMetadataByMediaMetadataRetriever(uri))
} }
@ -213,15 +216,15 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
// set `KEY_XMP_TITLE_DESCRIPTION` from these fields (by precedence): // set `KEY_XMP_TITLE_DESCRIPTION` from these fields (by precedence):
// - XMP / dc:title // - XMP / dc:title
// - XMP / dc:description // - XMP / dc:description
private fun getCatalogMetadataByMetadataExtractor(uri: Uri, mimeType: String, path: String?): Map<String, Any> { private fun getCatalogMetadataByMetadataExtractor(uri: Uri, mimeType: String, path: String?, sizeBytes: Long?): Map<String, Any> {
val metadataMap = HashMap<String, Any>() val metadataMap = HashMap<String, Any>()
var foundExif = false var foundExif = false
if (isSupportedByMetadataExtractor(mimeType)) { if (isSupportedByMetadataExtractor(mimeType, sizeBytes)) {
try { try {
StorageUtils.openInputStream(context, uri)?.use { input -> StorageUtils.openInputStream(context, uri)?.use { input ->
val metadata = ImageMetadataReader.readMetadata(input) val metadata = ImageMetadataReader.readMetadata(input, sizeBytes ?: -1)
foundExif = metadata.containsDirectoryOfType(ExifDirectoryBase::class.java) foundExif = metadata.containsDirectoryOfType(ExifDirectoryBase::class.java)
// File type // File type
@ -311,7 +314,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
} }
} }
if (!foundExif) { if (!foundExif && isSupportedByExifInterface(mimeType, sizeBytes)) {
// fallback to read EXIF via ExifInterface // fallback to read EXIF via ExifInterface
try { try {
StorageUtils.openInputStream(context, uri)?.use { input -> StorageUtils.openInputStream(context, uri)?.use { input ->
@ -371,6 +374,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
private fun getOverlayMetadata(call: MethodCall, result: MethodChannel.Result) { private fun getOverlayMetadata(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType") val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) } val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) { if (mimeType == null || uri == null) {
result.error("getOverlayMetadata-args", "failed because of missing arguments", null) result.error("getOverlayMetadata-args", "failed because of missing arguments", null)
return return
@ -396,10 +400,10 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
} }
var foundExif = false var foundExif = false
if (isSupportedByMetadataExtractor(mimeType)) { if (isSupportedByMetadataExtractor(mimeType, sizeBytes)) {
try { try {
StorageUtils.openInputStream(context, uri)?.use { input -> StorageUtils.openInputStream(context, uri)?.use { input ->
val metadata = ImageMetadataReader.readMetadata(input) val metadata = ImageMetadataReader.readMetadata(input, sizeBytes ?: -1)
for (dir in metadata.getDirectoriesOfType(ExifSubIFDDirectory::class.java)) { for (dir in metadata.getDirectoriesOfType(ExifSubIFDDirectory::class.java)) {
foundExif = true foundExif = true
dir.getSafeRational(ExifSubIFDDirectory.TAG_FNUMBER) { metadataMap[KEY_APERTURE] = it.numerator.toDouble() / it.denominator } dir.getSafeRational(ExifSubIFDDirectory.TAG_FNUMBER) { metadataMap[KEY_APERTURE] = it.numerator.toDouble() / it.denominator }
@ -415,7 +419,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
} }
} }
if (!foundExif) { if (!foundExif && isSupportedByExifInterface(mimeType, sizeBytes)) {
// fallback to read EXIF via ExifInterface // fallback to read EXIF via ExifInterface
try { try {
StorageUtils.openInputStream(context, uri)?.use { input -> StorageUtils.openInputStream(context, uri)?.use { input ->
@ -488,27 +492,32 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
} }
private fun getExifInterfaceMetadata(call: MethodCall, result: MethodChannel.Result) { private fun getExifInterfaceMetadata(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) } val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
if (uri == null) { val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) {
result.error("getExifInterfaceMetadata-args", "failed because of missing arguments", null) result.error("getExifInterfaceMetadata-args", "failed because of missing arguments", null)
return return
} }
val metadataMap = HashMap<String, String?>()
if (isSupportedByExifInterface(mimeType, sizeBytes, strict = false)) {
try { try {
StorageUtils.openInputStream(context, uri)?.use { input -> StorageUtils.openInputStream(context, uri)?.use { input ->
val exif = ExifInterface(input) val exif = ExifInterface(input)
val metadataMap = HashMap<String, String?>()
for (tag in ExifInterfaceHelper.allTags.keys.filter { exif.hasAttribute(it) }) { for (tag in ExifInterfaceHelper.allTags.keys.filter { exif.hasAttribute(it) }) {
metadataMap[tag] = exif.getAttribute(tag) metadataMap[tag] = exif.getAttribute(tag)
} }
result.success(metadataMap) }
} ?: result.error("getExifInterfaceMetadata-noinput", "failed to get exif for uri=$uri", null)
} catch (e: Exception) { } catch (e: Exception) {
// ExifInterface initialization can fail with a RuntimeException // ExifInterface initialization can fail with a RuntimeException
// caused by an internal MediaMetadataRetriever failure // caused by an internal MediaMetadataRetriever failure
result.error("getExifInterfaceMetadata-failure", "failed to get exif for uri=$uri", e.message) result.error("getExifInterfaceMetadata-failure", "failed to get exif for uri=$uri", e.message)
return
} }
} }
result.success(metadataMap)
}
private fun getMediaMetadataRetrieverMetadata(call: MethodCall, result: MethodChannel.Result) { private fun getMediaMetadataRetrieverMetadata(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument<String>("uri")?.let { Uri.parse(it) } val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
@ -563,16 +572,19 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
} }
private fun getMetadataExtractorSummary(call: MethodCall, result: MethodChannel.Result) { private fun getMetadataExtractorSummary(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) } val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
if (uri == null) { val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) {
result.error("getMetadataExtractorSummary-args", "failed because of missing arguments", null) result.error("getMetadataExtractorSummary-args", "failed because of missing arguments", null)
return return
} }
val metadataMap = HashMap<String, String>() val metadataMap = HashMap<String, String>()
if (isSupportedByMetadataExtractor(mimeType, sizeBytes)) {
try { try {
StorageUtils.openInputStream(context, uri)?.use { input -> StorageUtils.openInputStream(context, uri)?.use { input ->
val metadata = ImageMetadataReader.readMetadata(input) val metadata = ImageMetadataReader.readMetadata(input, sizeBytes ?: -1)
metadataMap["mimeType"] = metadata.getDirectoriesOfType(FileTypeDirectory::class.java).joinToString { dir -> metadataMap["mimeType"] = metadata.getDirectoriesOfType(FileTypeDirectory::class.java).joinToString { dir ->
if (dir.containsTag(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE)) { if (dir.containsTag(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE)) {
dir.getString(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE) dir.getString(FileTypeDirectory.TAG_DETECTED_FILE_MIME_TYPE)
@ -597,12 +609,8 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
} catch (e: NoClassDefFoundError) { } catch (e: NoClassDefFoundError) {
Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for uri=$uri", e) Log.w(LOG_TAG, "failed to get metadata by metadata-extractor for uri=$uri", e)
} }
if (metadataMap.isNotEmpty()) {
result.success(metadataMap)
} else {
result.error("getMetadataExtractorSummary-failure", "failed to get metadata for uri=$uri", null)
} }
result.success(metadataMap)
} }
private fun getEmbeddedPictures(call: MethodCall, result: MethodChannel.Result) { private fun getEmbeddedPictures(call: MethodCall, result: MethodChannel.Result) {
@ -628,13 +636,16 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
} }
private fun getExifThumbnails(call: MethodCall, result: MethodChannel.Result) { private fun getExifThumbnails(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) } val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
if (uri == null) { val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) {
result.error("getExifThumbnails-args", "failed because of missing arguments", null) result.error("getExifThumbnails-args", "failed because of missing arguments", null)
return return
} }
val thumbnails = ArrayList<ByteArray>() val thumbnails = ArrayList<ByteArray>()
if (isSupportedByExifInterface(mimeType, sizeBytes)) {
try { try {
StorageUtils.openInputStream(context, uri)?.use { input -> StorageUtils.openInputStream(context, uri)?.use { input ->
val exif = ExifInterface(input) val exif = ExifInterface(input)
@ -649,22 +660,24 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
// ExifInterface initialization can fail with a RuntimeException // ExifInterface initialization can fail with a RuntimeException
// caused by an internal MediaMetadataRetriever failure // caused by an internal MediaMetadataRetriever failure
} }
}
result.success(thumbnails) result.success(thumbnails)
} }
private fun getXmpThumbnails(call: MethodCall, result: MethodChannel.Result) { private fun getXmpThumbnails(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType") val mimeType = call.argument<String>("mimeType")
val uri = call.argument<String>("uri")?.let { Uri.parse(it) } val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
if (mimeType == null || uri == null) { if (mimeType == null || uri == null) {
result.error("getXmpThumbnails-args", "failed because of missing arguments", null) result.error("getXmpThumbnails-args", "failed because of missing arguments", null)
return return
} }
val thumbnails = ArrayList<ByteArray>() val thumbnails = ArrayList<ByteArray>()
if (isSupportedByMetadataExtractor(mimeType)) { if (isSupportedByMetadataExtractor(mimeType, sizeBytes)) {
try { try {
StorageUtils.openInputStream(context, uri)?.use { input -> StorageUtils.openInputStream(context, uri)?.use { input ->
val metadata = ImageMetadataReader.readMetadata(input) val metadata = ImageMetadataReader.readMetadata(input, sizeBytes ?: -1)
for (dir in metadata.getDirectoriesOfType(XmpDirectory::class.java)) { for (dir in metadata.getDirectoriesOfType(XmpDirectory::class.java)) {
val xmpMeta = dir.xmpMeta val xmpMeta = dir.xmpMeta
try { try {

View file

@ -27,6 +27,7 @@ import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeLong
import deckers.thibault.aves.model.provider.FieldMap import deckers.thibault.aves.model.provider.FieldMap
import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.StorageUtils import deckers.thibault.aves.utils.StorageUtils
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
import java.io.IOException import java.io.IOException
class SourceImageEntry { class SourceImageEntry {
@ -129,7 +130,10 @@ class SourceImageEntry {
fillByExifInterface(context) fillByExifInterface(context)
} }
if (!isSized) { if (!isSized) {
fillByBitmapDecode(context) when (sourceMimeType) {
MimeTypes.TIFF -> fillByTiffDecode(context)
else -> fillByBitmapDecode(context)
}
} }
return this return this
} }
@ -155,11 +159,13 @@ class SourceImageEntry {
// finds: width, height, orientation, date, duration // finds: width, height, orientation, date, duration
private fun fillByMetadataExtractor(context: Context) { private fun fillByMetadataExtractor(context: Context) {
// skip raw images because `metadata-extractor` reports the decoded dimensions instead of the raw dimensions // skip raw images because `metadata-extractor` reports the decoded dimensions instead of the raw dimensions
if (!MimeTypes.isSupportedByMetadataExtractor(sourceMimeType) || MimeTypes.isRaw(sourceMimeType)) return if (!MimeTypes.isSupportedByMetadataExtractor(sourceMimeType, sizeBytes)
|| MimeTypes.isRaw(sourceMimeType)
) return
try { try {
StorageUtils.openInputStream(context, uri)?.use { input -> StorageUtils.openInputStream(context, uri)?.use { input ->
val metadata = ImageMetadataReader.readMetadata(input) val metadata = ImageMetadataReader.readMetadata(input, sizeBytes ?: -1)
// do not switch on specific mime types, as the reported mime type could be wrong // do not switch on specific mime types, as the reported mime type could be wrong
// (e.g. PNG registered as JPG) // (e.g. PNG registered as JPG)
@ -207,7 +213,7 @@ class SourceImageEntry {
// finds: width, height, orientation, date // finds: width, height, orientation, date
private fun fillByExifInterface(context: Context) { private fun fillByExifInterface(context: Context) {
if (!ExifInterface.isSupportedMimeType(sourceMimeType)) return; if (!MimeTypes.isSupportedByExifInterface(sourceMimeType, sizeBytes)) return;
try { try {
StorageUtils.openInputStream(context, uri)?.use { input -> StorageUtils.openInputStream(context, uri)?.use { input ->
@ -240,6 +246,22 @@ class SourceImageEntry {
} }
} }
private fun fillByTiffDecode(context: Context) {
try {
context.contentResolver.openFileDescriptor(uri, "r")?.use { descriptor ->
val options = TiffBitmapFactory.Options().apply {
inJustDecodeBounds = true
}
TiffBitmapFactory.decodeFileDescriptor(descriptor.fd, options)
width = options.outWidth
height = options.outHeight
}
} catch (e: Exception) {
// ignore
}
}
companion object { companion object {
// convenience method // convenience method
private fun toLong(o: Any?): Long? = when (o) { private fun toLong(o: Any?): Long? = when (o) {

View file

@ -1,5 +1,7 @@
package deckers.thibault.aves.utils package deckers.thibault.aves.utils
import androidx.exifinterface.media.ExifInterface
object MimeTypes { object MimeTypes {
private const val IMAGE = "image" private const val IMAGE = "image"
@ -65,12 +67,25 @@ object MimeTypes {
else -> false else -> false
} }
// as of metadata-extractor v2.14.0 // opening large TIFF files yields an OOM (both with `metadata-extractor` v2.15.0 and `ExifInterface` v1.3.1),
fun isSupportedByMetadataExtractor(mimeType: String) = when (mimeType) { // so we define an arbitrary threshold to avoid a crash on launch.
// It is not clear whether it is because of the file itself or its metadata.
private const val tiffSizeBytesMax = 128 * (1 shl 20) // MB
// as of `metadata-extractor` v2.14.0
fun isSupportedByMetadataExtractor(mimeType: String, sizeBytes: Long?) = when (mimeType) {
WBMP, MP2T, WEBM -> false WBMP, MP2T, WEBM -> false
TIFF -> sizeBytes != null && sizeBytes < tiffSizeBytesMax
else -> true else -> true
} }
// as of `ExifInterface` v1.3.1, `isSupportedMimeType` reports
// no support for TIFF images, but it can actually open them (maybe other formats too)
fun isSupportedByExifInterface(mimeType: String, sizeBytes: Long?, strict: Boolean = true) = when (mimeType) {
TIFF -> sizeBytes != null && sizeBytes < tiffSizeBytesMax
else -> ExifInterface.isSupportedMimeType(mimeType) || !strict
}
// Glide automatically applies EXIF orientation when decoding images of known formats // Glide automatically applies EXIF orientation when decoding images of known formats
// but we need to rotate the decoded bitmap for the other formats // but we need to rotate the decoded bitmap for the other formats
// maybe related to ExifInterface version used by Glide: // maybe related to ExifInterface version used by Glide:

View file

@ -173,7 +173,7 @@ class ImageEntry {
bool get isSvg => mimeType == MimeTypes.svg; bool get isSvg => mimeType == MimeTypes.svg;
// guess whether this is a photo, according to file type (used as a hint to e.g. display megapixels) // guess whether this is a photo, according to file type (used as a hint to e.g. display megapixels)
bool get isPhoto => [MimeTypes.heic, MimeTypes.heif, MimeTypes.jpeg].contains(mimeType) || isRaw; bool get isPhoto => [MimeTypes.heic, MimeTypes.heif, MimeTypes.jpeg, MimeTypes.tiff].contains(mimeType) || isRaw;
// Android's `BitmapRegionDecoder` documentation states that "only the JPEG and PNG formats are supported" // Android's `BitmapRegionDecoder` documentation states that "only the JPEG and PNG formats are supported"
// but in practice (tested on API 25, 27, 29), it successfully decodes the formats listed below, // but in practice (tested on API 25, 27, 29), it successfully decodes the formats listed below,

View file

@ -1,6 +1,7 @@
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
class BrandColors { class BrandColors {
static const Color adobeAfterEffects = Color(0xFF9A9AFF);
static const Color adobeIllustrator = Color(0xFFFF9B00); static const Color adobeIllustrator = Color(0xFFFF9B00);
static const Color adobePhotoshop = Color(0xFF2DAAFF); static const Color adobePhotoshop = Color(0xFF2DAAFF);
static const Color android = Color(0xFF3DDC84); static const Color android = Color(0xFF3DDC84);
@ -9,6 +10,8 @@ class BrandColors {
static Color get(String text) { static Color get(String text) {
if (text != null) { if (text != null) {
switch (text.toLowerCase()) { switch (text.toLowerCase()) {
case 'after effects':
return adobeAfterEffects;
case 'illustrator': case 'illustrator':
return adobeIllustrator; return adobeIllustrator;
case 'photoshop': case 'photoshop':

View file

@ -7,6 +7,7 @@ class XMP {
'adsml-at': 'AdsML', 'adsml-at': 'AdsML',
'aux': 'Exif Aux', 'aux': 'Exif Aux',
'Camera': 'Camera', 'Camera': 'Camera',
'creatorAtom': 'After Effects',
'crs': 'Camera Raw Settings', 'crs': 'Camera Raw Settings',
'dc': 'Dublin Core', 'dc': 'Dublin Core',
'drone-dji': 'DJI Drone', 'drone-dji': 'DJI Drone',

View file

@ -17,6 +17,7 @@ class MetadataService {
final result = await platform.invokeMethod('getAllMetadata', <String, dynamic>{ final result = await platform.invokeMethod('getAllMetadata', <String, dynamic>{
'mimeType': entry.mimeType, 'mimeType': entry.mimeType,
'uri': entry.uri, 'uri': entry.uri,
'sizeBytes': entry.sizeBytes,
}); });
return result as Map; return result as Map;
} on PlatformException catch (e) { } on PlatformException catch (e) {
@ -44,6 +45,7 @@ class MetadataService {
'mimeType': entry.mimeType, 'mimeType': entry.mimeType,
'uri': entry.uri, 'uri': entry.uri,
'path': entry.path, 'path': entry.path,
'sizeBytes': entry.sizeBytes,
}) as Map; }) as Map;
result['contentId'] = entry.contentId; result['contentId'] = entry.contentId;
return CatalogMetadata.fromMap(result); return CatalogMetadata.fromMap(result);
@ -69,6 +71,7 @@ class MetadataService {
final result = await platform.invokeMethod('getOverlayMetadata', <String, dynamic>{ final result = await platform.invokeMethod('getOverlayMetadata', <String, dynamic>{
'mimeType': entry.mimeType, 'mimeType': entry.mimeType,
'uri': entry.uri, 'uri': entry.uri,
'sizeBytes': entry.sizeBytes,
}) as Map; }) as Map;
return OverlayMetadata.fromMap(result); return OverlayMetadata.fromMap(result);
} on PlatformException catch (e) { } on PlatformException catch (e) {
@ -108,7 +111,9 @@ class MetadataService {
try { try {
// return map with all data available from the `ExifInterface` library // return map with all data available from the `ExifInterface` library
final result = await platform.invokeMethod('getExifInterfaceMetadata', <String, dynamic>{ final result = await platform.invokeMethod('getExifInterfaceMetadata', <String, dynamic>{
'mimeType': entry.mimeType,
'uri': entry.uri, 'uri': entry.uri,
'sizeBytes': entry.sizeBytes,
}) as Map; }) as Map;
return result; return result;
} on PlatformException catch (e) { } on PlatformException catch (e) {
@ -134,7 +139,9 @@ class MetadataService {
try { try {
// return map with the mime type and tag count for each directory found by `metadata-extractor` // return map with the mime type and tag count for each directory found by `metadata-extractor`
final result = await platform.invokeMethod('getMetadataExtractorSummary', <String, dynamic>{ final result = await platform.invokeMethod('getMetadataExtractorSummary', <String, dynamic>{
'mimeType': entry.mimeType,
'uri': entry.uri, 'uri': entry.uri,
'sizeBytes': entry.sizeBytes,
}) as Map; }) as Map;
return result; return result;
} on PlatformException catch (e) { } on PlatformException catch (e) {
@ -155,10 +162,12 @@ class MetadataService {
return []; return [];
} }
static Future<List<Uint8List>> getExifThumbnails(String uri) async { static Future<List<Uint8List>> getExifThumbnails(ImageEntry entry) async {
try { try {
final result = await platform.invokeMethod('getExifThumbnails', <String, dynamic>{ final result = await platform.invokeMethod('getExifThumbnails', <String, dynamic>{
'uri': uri, 'mimeType': entry.mimeType,
'uri': entry.uri,
'sizeBytes': entry.sizeBytes,
}); });
return (result as List).cast<Uint8List>(); return (result as List).cast<Uint8List>();
} on PlatformException catch (e) { } on PlatformException catch (e) {
@ -172,6 +181,7 @@ class MetadataService {
final result = await platform.invokeMethod('getXmpThumbnails', <String, dynamic>{ final result = await platform.invokeMethod('getXmpThumbnails', <String, dynamic>{
'mimeType': entry.mimeType, 'mimeType': entry.mimeType,
'uri': entry.uri, 'uri': entry.uri,
'sizeBytes': entry.sizeBytes,
}); });
return (result as List).cast<Uint8List>(); return (result as List).cast<Uint8List>();
} on PlatformException catch (e) { } on PlatformException catch (e) {

View file

@ -36,7 +36,7 @@ class _MetadataThumbnailsState extends State<MetadataThumbnails> {
_loader = MetadataService.getEmbeddedPictures(uri); _loader = MetadataService.getEmbeddedPictures(uri);
break; break;
case MetadataThumbnailSource.exif: case MetadataThumbnailSource.exif:
_loader = MetadataService.getExifThumbnails(uri); _loader = MetadataService.getExifThumbnails(entry);
break; break;
case MetadataThumbnailSource.xmp: case MetadataThumbnailSource.xmp:
_loader = MetadataService.getXmpThumbnails(entry); _loader = MetadataService.getXmpThumbnails(entry);