avoid double extraction for embedded media

This commit is contained in:
Thibault Deckers 2023-12-21 13:18:51 +01:00
parent 5c8cd81ff7
commit c95b381fad
9 changed files with 70 additions and 36 deletions

View file

@ -20,8 +20,8 @@ import deckers.thibault.aves.metadata.XMP.getSafeStructField
import deckers.thibault.aves.metadata.XMPPropName
import deckers.thibault.aves.metadata.metadataextractor.Helper
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.model.provider.ContentImageProvider
import deckers.thibault.aves.model.provider.ImageProvider
import deckers.thibault.aves.model.provider.ImageProviderFactory.getProvider
import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.BitmapUtils.getBytes
import deckers.thibault.aves.utils.FileUtils.transferFrom
@ -341,8 +341,14 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
"mimeType" to mimeType,
)
if (isImage(mimeType) || isVideo(mimeType)) {
val provider = getProvider(context, uri)
if (provider == null) {
result.error("copyEmbeddedBytes-provider", "failed to find provider for uri=$uri", null)
return
}
ioScope.launch {
ContentImageProvider().fetchSingle(context, uri, mimeType, object : ImageProvider.ImageOpCallback {
provider.fetchSingle(context, uri, mimeType, object : ImageProvider.ImageOpCallback {
override fun onSuccess(fields: FieldMap) {
resultFields.putAll(fields)
result.success(resultFields)

View file

@ -55,7 +55,7 @@ class MediaEditHandler(private val contextWrapper: ContextWrapper) : MethodCallH
return
}
val provider = getProvider(uri)
val provider = getProvider(contextWrapper, uri)
if (provider == null) {
result.error("captureFrame-provider", "failed to find provider for uri=$uri", null)
return

View file

@ -34,7 +34,7 @@ class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler
return
}
val provider = getProvider(uri)
val provider = getProvider(context, uri)
if (provider == null) {
result.error("getEntry-provider", "failed to find provider for uri=$uri", null)
return

View file

@ -62,7 +62,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
val provider = getProvider(uri)
val provider = getProvider(contextWrapper, uri)
if (provider == null) {
result.error("editOrientation-provider", "failed to find provider for uri=$uri", null)
return
@ -90,7 +90,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
val provider = getProvider(uri)
val provider = getProvider(contextWrapper, uri)
if (provider == null) {
result.error("editDate-provider", "failed to find provider for uri=$uri", null)
return
@ -117,7 +117,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
val provider = getProvider(uri)
val provider = getProvider(contextWrapper, uri)
if (provider == null) {
result.error("editMetadata-provider", "failed to find provider for uri=$uri", null)
return
@ -142,7 +142,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
val provider = getProvider(uri)
val provider = getProvider(contextWrapper, uri)
if (provider == null) {
result.error("removeTrailerVideo-provider", "failed to find provider for uri=$uri", null)
return
@ -168,7 +168,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
return
}
val provider = getProvider(uri)
val provider = getProvider(contextWrapper, uri)
if (provider == null) {
result.error("removeTypes-provider", "failed to find provider for uri=$uri", null)
return

View file

@ -107,7 +107,7 @@ class ImageOpStreamHandler(private val activity: FragmentActivity, private val a
result["skipped"] = true
} else {
result["success"] = false
getProvider(uri)?.let { provider ->
getProvider(activity, uri)?.let { provider ->
try {
provider.delete(activity, uri, path, mimeType)
result["success"] = true
@ -142,7 +142,7 @@ class ImageOpStreamHandler(private val activity: FragmentActivity, private val a
// assume same provider for all entries
val firstEntry = entryMapList.first()
val provider = (firstEntry["uri"] as String?)?.let { Uri.parse(it) }?.let { getProvider(it) }
val provider = (firstEntry["uri"] as String?)?.let { Uri.parse(it) }?.let { getProvider(activity, it) }
if (provider == null) {
error("convert-provider", "failed to find provider for entry=$firstEntry", null)
return
@ -231,7 +231,7 @@ class ImageOpStreamHandler(private val activity: FragmentActivity, private val a
entriesToNewName[AvesEntry(rawEntry)] = newName
}
val byProvider = entriesToNewName.entries.groupBy { kv -> getProvider(kv.key.uri) }
val byProvider = entriesToNewName.entries.groupBy { kv -> getProvider(activity, kv.key.uri) }
for ((provider, entryList) in byProvider) {
if (provider == null) {
error("rename-provider", "failed to find provider for entry=${entryList.firstOrNull()}", null)

View file

@ -10,7 +10,9 @@ import java.io.File
import java.io.InputStream
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*
import java.util.Date
import java.util.Locale
import java.util.TimeZone
import java.util.regex.Pattern
object Metadata {

View file

@ -0,0 +1,18 @@
package deckers.thibault.aves.model.provider
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import java.util.Locale
class AvesEmbeddedMediaProvider : UnknownContentProvider() {
override val reliableProviderMimeType: Boolean
get() = true
companion object {
fun provides(context: Context, uri: Uri): Boolean {
if (uri.scheme?.lowercase(Locale.ROOT) != ContentResolver.SCHEME_CONTENT) return false
return uri.authority == "${context.applicationContext.packageName}.file_provider"
}
}
}

View file

@ -1,20 +1,24 @@
package deckers.thibault.aves.model.provider
import android.content.ContentResolver
import android.content.Context
import android.net.Uri
import deckers.thibault.aves.utils.StorageUtils
import java.util.*
import java.util.Locale
object ImageProviderFactory {
fun getProvider(uri: Uri): ImageProvider? {
fun getProvider(context: Context, uri: Uri): ImageProvider? {
return when (uri.scheme?.lowercase(Locale.ROOT)) {
ContentResolver.SCHEME_CONTENT -> {
if (StorageUtils.isMediaStoreContentUri(uri)) {
MediaStoreImageProvider()
} else if (AvesEmbeddedMediaProvider.provides(context, uri)) {
AvesEmbeddedMediaProvider()
} else {
ContentImageProvider()
UnknownContentProvider()
}
}
ContentResolver.SCHEME_FILE -> FileImageProvider()
else -> null
}

View file

@ -13,31 +13,35 @@ import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.StorageUtils
internal class ContentImageProvider : ImageProvider() {
open class UnknownContentProvider : ImageProvider() {
open val reliableProviderMimeType: Boolean
get() = false
override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, callback: ImageOpCallback) {
// source MIME type may be incorrect, so we get a second opinion if possible
var extractorMimeType: String? = null
try {
val safeUri = Uri.fromFile(Metadata.createPreviewFile(context, uri))
StorageUtils.openInputStream(context, safeUri)?.use { input ->
// `metadata-extractor` is the most reliable, except for `tiff` (false positives, false negatives)
// cf https://github.com/drewnoakes/metadata-extractor/issues/296
Helper.readMimeType(input)?.takeIf { it != MimeTypes.TIFF }?.let {
extractorMimeType = it
if (extractorMimeType != sourceMimeType) {
Log.d(LOG_TAG, "source MIME type is $sourceMimeType but extracted MIME type is $extractorMimeType for uri=$uri")
var mimeType = sourceMimeType
if (sourceMimeType == null || !reliableProviderMimeType) {
// source MIME type may be incorrect, so we get a second opinion if possible
try {
val safeUri = Uri.fromFile(Metadata.createPreviewFile(context, uri))
StorageUtils.openInputStream(context, safeUri)?.use { input ->
// `metadata-extractor` is the most reliable, except for `tiff` (false positives, false negatives)
// cf https://github.com/drewnoakes/metadata-extractor/issues/296
Helper.readMimeType(input)?.takeIf { it != MimeTypes.TIFF }?.let {
if (it != sourceMimeType) {
Log.d(LOG_TAG, "source MIME type is $sourceMimeType but extracted MIME type is $it for uri=$uri")
mimeType = it
}
}
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get MIME type by metadata-extractor for uri=$uri", e)
} catch (e: NoClassDefFoundError) {
Log.w(LOG_TAG, "failed to get MIME type by metadata-extractor for uri=$uri", e)
} catch (e: AssertionError) {
Log.w(LOG_TAG, "failed to get MIME type by metadata-extractor for uri=$uri", e)
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to get MIME type by metadata-extractor for uri=$uri", e)
} catch (e: NoClassDefFoundError) {
Log.w(LOG_TAG, "failed to get MIME type by metadata-extractor for uri=$uri", e)
} catch (e: AssertionError) {
Log.w(LOG_TAG, "failed to get MIME type by metadata-extractor for uri=$uri", e)
}
val mimeType = extractorMimeType ?: sourceMimeType
val fields: FieldMap = hashMapOf(
"origin" to SourceEntry.ORIGIN_UNKNOWN_CONTENT,
"uri" to uri.toString(),
@ -75,6 +79,6 @@ internal class ContentImageProvider : ImageProvider() {
}
companion object {
private val LOG_TAG = LogUtils.createTag<ContentImageProvider>()
private val LOG_TAG = LogUtils.createTag<UnknownContentProvider>()
}
}