avoid double extraction for embedded media
This commit is contained in:
parent
5c8cd81ff7
commit
c95b381fad
9 changed files with 70 additions and 36 deletions
|
@ -20,8 +20,8 @@ import deckers.thibault.aves.metadata.XMP.getSafeStructField
|
||||||
import deckers.thibault.aves.metadata.XMPPropName
|
import deckers.thibault.aves.metadata.XMPPropName
|
||||||
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
||||||
import deckers.thibault.aves.model.FieldMap
|
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.ImageProvider
|
||||||
|
import deckers.thibault.aves.model.provider.ImageProviderFactory.getProvider
|
||||||
import deckers.thibault.aves.utils.BitmapUtils
|
import deckers.thibault.aves.utils.BitmapUtils
|
||||||
import deckers.thibault.aves.utils.BitmapUtils.getBytes
|
import deckers.thibault.aves.utils.BitmapUtils.getBytes
|
||||||
import deckers.thibault.aves.utils.FileUtils.transferFrom
|
import deckers.thibault.aves.utils.FileUtils.transferFrom
|
||||||
|
@ -341,8 +341,14 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
|
||||||
"mimeType" to mimeType,
|
"mimeType" to mimeType,
|
||||||
)
|
)
|
||||||
if (isImage(mimeType) || isVideo(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 {
|
ioScope.launch {
|
||||||
ContentImageProvider().fetchSingle(context, uri, mimeType, object : ImageProvider.ImageOpCallback {
|
provider.fetchSingle(context, uri, mimeType, object : ImageProvider.ImageOpCallback {
|
||||||
override fun onSuccess(fields: FieldMap) {
|
override fun onSuccess(fields: FieldMap) {
|
||||||
resultFields.putAll(fields)
|
resultFields.putAll(fields)
|
||||||
result.success(resultFields)
|
result.success(resultFields)
|
||||||
|
|
|
@ -55,7 +55,7 @@ class MediaEditHandler(private val contextWrapper: ContextWrapper) : MethodCallH
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val provider = getProvider(uri)
|
val provider = getProvider(contextWrapper, uri)
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
result.error("captureFrame-provider", "failed to find provider for uri=$uri", null)
|
result.error("captureFrame-provider", "failed to find provider for uri=$uri", null)
|
||||||
return
|
return
|
||||||
|
|
|
@ -34,7 +34,7 @@ class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val provider = getProvider(uri)
|
val provider = getProvider(context, uri)
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
result.error("getEntry-provider", "failed to find provider for uri=$uri", null)
|
result.error("getEntry-provider", "failed to find provider for uri=$uri", null)
|
||||||
return
|
return
|
||||||
|
|
|
@ -62,7 +62,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val provider = getProvider(uri)
|
val provider = getProvider(contextWrapper, uri)
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
result.error("editOrientation-provider", "failed to find provider for uri=$uri", null)
|
result.error("editOrientation-provider", "failed to find provider for uri=$uri", null)
|
||||||
return
|
return
|
||||||
|
@ -90,7 +90,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val provider = getProvider(uri)
|
val provider = getProvider(contextWrapper, uri)
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
result.error("editDate-provider", "failed to find provider for uri=$uri", null)
|
result.error("editDate-provider", "failed to find provider for uri=$uri", null)
|
||||||
return
|
return
|
||||||
|
@ -117,7 +117,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val provider = getProvider(uri)
|
val provider = getProvider(contextWrapper, uri)
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
result.error("editMetadata-provider", "failed to find provider for uri=$uri", null)
|
result.error("editMetadata-provider", "failed to find provider for uri=$uri", null)
|
||||||
return
|
return
|
||||||
|
@ -142,7 +142,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val provider = getProvider(uri)
|
val provider = getProvider(contextWrapper, uri)
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
result.error("removeTrailerVideo-provider", "failed to find provider for uri=$uri", null)
|
result.error("removeTrailerVideo-provider", "failed to find provider for uri=$uri", null)
|
||||||
return
|
return
|
||||||
|
@ -168,7 +168,7 @@ class MetadataEditHandler(private val contextWrapper: ContextWrapper) : MethodCa
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val provider = getProvider(uri)
|
val provider = getProvider(contextWrapper, uri)
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
result.error("removeTypes-provider", "failed to find provider for uri=$uri", null)
|
result.error("removeTypes-provider", "failed to find provider for uri=$uri", null)
|
||||||
return
|
return
|
||||||
|
|
|
@ -107,7 +107,7 @@ class ImageOpStreamHandler(private val activity: FragmentActivity, private val a
|
||||||
result["skipped"] = true
|
result["skipped"] = true
|
||||||
} else {
|
} else {
|
||||||
result["success"] = false
|
result["success"] = false
|
||||||
getProvider(uri)?.let { provider ->
|
getProvider(activity, uri)?.let { provider ->
|
||||||
try {
|
try {
|
||||||
provider.delete(activity, uri, path, mimeType)
|
provider.delete(activity, uri, path, mimeType)
|
||||||
result["success"] = true
|
result["success"] = true
|
||||||
|
@ -142,7 +142,7 @@ class ImageOpStreamHandler(private val activity: FragmentActivity, private val a
|
||||||
|
|
||||||
// assume same provider for all entries
|
// assume same provider for all entries
|
||||||
val firstEntry = entryMapList.first()
|
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) {
|
if (provider == null) {
|
||||||
error("convert-provider", "failed to find provider for entry=$firstEntry", null)
|
error("convert-provider", "failed to find provider for entry=$firstEntry", null)
|
||||||
return
|
return
|
||||||
|
@ -231,7 +231,7 @@ class ImageOpStreamHandler(private val activity: FragmentActivity, private val a
|
||||||
entriesToNewName[AvesEntry(rawEntry)] = newName
|
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) {
|
for ((provider, entryList) in byProvider) {
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
error("rename-provider", "failed to find provider for entry=${entryList.firstOrNull()}", null)
|
error("rename-provider", "failed to find provider for entry=${entryList.firstOrNull()}", null)
|
||||||
|
|
|
@ -10,7 +10,9 @@ import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.text.ParseException
|
import java.text.ParseException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
object Metadata {
|
object Metadata {
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,20 +1,24 @@
|
||||||
package deckers.thibault.aves.model.provider
|
package deckers.thibault.aves.model.provider
|
||||||
|
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import deckers.thibault.aves.utils.StorageUtils
|
import deckers.thibault.aves.utils.StorageUtils
|
||||||
import java.util.*
|
import java.util.Locale
|
||||||
|
|
||||||
object ImageProviderFactory {
|
object ImageProviderFactory {
|
||||||
fun getProvider(uri: Uri): ImageProvider? {
|
fun getProvider(context: Context, uri: Uri): ImageProvider? {
|
||||||
return when (uri.scheme?.lowercase(Locale.ROOT)) {
|
return when (uri.scheme?.lowercase(Locale.ROOT)) {
|
||||||
ContentResolver.SCHEME_CONTENT -> {
|
ContentResolver.SCHEME_CONTENT -> {
|
||||||
if (StorageUtils.isMediaStoreContentUri(uri)) {
|
if (StorageUtils.isMediaStoreContentUri(uri)) {
|
||||||
MediaStoreImageProvider()
|
MediaStoreImageProvider()
|
||||||
|
} else if (AvesEmbeddedMediaProvider.provides(context, uri)) {
|
||||||
|
AvesEmbeddedMediaProvider()
|
||||||
} else {
|
} else {
|
||||||
ContentImageProvider()
|
UnknownContentProvider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentResolver.SCHEME_FILE -> FileImageProvider()
|
ContentResolver.SCHEME_FILE -> FileImageProvider()
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,31 +13,35 @@ import deckers.thibault.aves.utils.LogUtils
|
||||||
import deckers.thibault.aves.utils.MimeTypes
|
import deckers.thibault.aves.utils.MimeTypes
|
||||||
import deckers.thibault.aves.utils.StorageUtils
|
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) {
|
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 mimeType = sourceMimeType
|
||||||
var extractorMimeType: String? = null
|
if (sourceMimeType == null || !reliableProviderMimeType) {
|
||||||
try {
|
// source MIME type may be incorrect, so we get a second opinion if possible
|
||||||
val safeUri = Uri.fromFile(Metadata.createPreviewFile(context, uri))
|
try {
|
||||||
StorageUtils.openInputStream(context, safeUri)?.use { input ->
|
val safeUri = Uri.fromFile(Metadata.createPreviewFile(context, uri))
|
||||||
// `metadata-extractor` is the most reliable, except for `tiff` (false positives, false negatives)
|
StorageUtils.openInputStream(context, safeUri)?.use { input ->
|
||||||
// cf https://github.com/drewnoakes/metadata-extractor/issues/296
|
// `metadata-extractor` is the most reliable, except for `tiff` (false positives, false negatives)
|
||||||
Helper.readMimeType(input)?.takeIf { it != MimeTypes.TIFF }?.let {
|
// cf https://github.com/drewnoakes/metadata-extractor/issues/296
|
||||||
extractorMimeType = it
|
Helper.readMimeType(input)?.takeIf { it != MimeTypes.TIFF }?.let {
|
||||||
if (extractorMimeType != sourceMimeType) {
|
if (it != sourceMimeType) {
|
||||||
Log.d(LOG_TAG, "source MIME type is $sourceMimeType but extracted MIME type is $extractorMimeType for uri=$uri")
|
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(
|
val fields: FieldMap = hashMapOf(
|
||||||
"origin" to SourceEntry.ORIGIN_UNKNOWN_CONTENT,
|
"origin" to SourceEntry.ORIGIN_UNKNOWN_CONTENT,
|
||||||
"uri" to uri.toString(),
|
"uri" to uri.toString(),
|
||||||
|
@ -75,6 +79,6 @@ internal class ContentImageProvider : ImageProvider() {
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOG_TAG = LogUtils.createTag<ContentImageProvider>()
|
private val LOG_TAG = LogUtils.createTag<UnknownContentProvider>()
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue