prevent editing item when Exif editing changes mime type
This commit is contained in:
parent
806f57785c
commit
7f9229a227
4 changed files with 44 additions and 2 deletions
|
@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file.
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Viewer: multi-page context update when removing burst entries
|
- Viewer: multi-page context update when removing burst entries
|
||||||
|
- prevent editing item when Exif editing changes mime type
|
||||||
|
|
||||||
## <a id="v1.8.5"></a>[v1.8.5] - 2023-04-18
|
## <a id="v1.8.5"></a>[v1.8.5] - 2023-04-18
|
||||||
|
|
||||||
|
|
|
@ -336,7 +336,7 @@ open class MainActivity : FlutterFragmentActivity() {
|
||||||
|
|
||||||
private fun submitPickedItems(call: MethodCall) {
|
private fun submitPickedItems(call: MethodCall) {
|
||||||
val pickedUris = call.argument<List<String>>("uris")
|
val pickedUris = call.argument<List<String>>("uris")
|
||||||
if (pickedUris != null && pickedUris.isNotEmpty()) {
|
if (!pickedUris.isNullOrEmpty()) {
|
||||||
val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this, Uri.parse(uriString)) }
|
val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this, Uri.parse(uriString)) }
|
||||||
val intent = Intent().apply {
|
val intent = Intent().apply {
|
||||||
val firstUri = toUri(pickedUris.first())
|
val firstUri = toUri(pickedUris.first())
|
||||||
|
|
|
@ -31,6 +31,7 @@ import deckers.thibault.aves.metadata.Mp4ParserHelper.updateRotation
|
||||||
import deckers.thibault.aves.metadata.Mp4ParserHelper.updateXmp
|
import deckers.thibault.aves.metadata.Mp4ParserHelper.updateXmp
|
||||||
import deckers.thibault.aves.metadata.PixyMetaHelper.extendedXmpDocString
|
import deckers.thibault.aves.metadata.PixyMetaHelper.extendedXmpDocString
|
||||||
import deckers.thibault.aves.metadata.PixyMetaHelper.xmpDocString
|
import deckers.thibault.aves.metadata.PixyMetaHelper.xmpDocString
|
||||||
|
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
||||||
import deckers.thibault.aves.model.*
|
import deckers.thibault.aves.model.*
|
||||||
import deckers.thibault.aves.utils.*
|
import deckers.thibault.aves.utils.*
|
||||||
import deckers.thibault.aves.utils.FileUtils.transferFrom
|
import deckers.thibault.aves.utils.FileUtils.transferFrom
|
||||||
|
@ -330,6 +331,7 @@ abstract class ImageProvider {
|
||||||
@Suppress("deprecation")
|
@Suppress("deprecation")
|
||||||
Bitmap.CompressFormat.WEBP
|
Bitmap.CompressFormat.WEBP
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> throw Exception("unsupported export MIME type=$exportMimeType")
|
else -> throw Exception("unsupported export MIME type=$exportMimeType")
|
||||||
}
|
}
|
||||||
bitmap.compress(format, quality, output)
|
bitmap.compress(format, quality, output)
|
||||||
|
@ -592,12 +594,14 @@ abstract class ImageProvider {
|
||||||
}
|
}
|
||||||
nameWithoutExtension
|
nameWithoutExtension
|
||||||
}
|
}
|
||||||
|
|
||||||
NameConflictStrategy.REPLACE -> {
|
NameConflictStrategy.REPLACE -> {
|
||||||
if (targetFile.exists()) {
|
if (targetFile.exists()) {
|
||||||
deletePath(contextWrapper, targetFile.path, mimeType)
|
deletePath(contextWrapper, targetFile.path, mimeType)
|
||||||
}
|
}
|
||||||
desiredNameWithoutExtension
|
desiredNameWithoutExtension
|
||||||
}
|
}
|
||||||
|
|
||||||
NameConflictStrategy.SKIP -> {
|
NameConflictStrategy.SKIP -> {
|
||||||
if (targetFile.exists()) {
|
if (targetFile.exists()) {
|
||||||
null
|
null
|
||||||
|
@ -608,6 +612,25 @@ abstract class ImageProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// cf `MetadataFetchHandler.getCatalogMetadataByMetadataExtractor()` for a more thorough check
|
||||||
|
private fun detectMimeType(context: Context, uri: Uri, mimeType: String): String? {
|
||||||
|
var detectedMimeType: String? = null
|
||||||
|
if (MimeTypes.canReadWithMetadataExtractor(mimeType)) {
|
||||||
|
try {
|
||||||
|
StorageUtils.openInputStream(context, uri)?.use { input ->
|
||||||
|
detectedMimeType = Helper.readMimeType(input)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||||
|
} catch (e: NoClassDefFoundError) {
|
||||||
|
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||||
|
} catch (e: AssertionError) {
|
||||||
|
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return detectedMimeType
|
||||||
|
}
|
||||||
|
|
||||||
private fun editExif(
|
private fun editExif(
|
||||||
context: Context,
|
context: Context,
|
||||||
path: String,
|
path: String,
|
||||||
|
@ -657,6 +680,11 @@ abstract class ImageProvider {
|
||||||
try {
|
try {
|
||||||
edit(ExifInterface(editableFile))
|
edit(ExifInterface(editableFile))
|
||||||
|
|
||||||
|
val editedMimeType = detectMimeType(context, Uri.fromFile(editableFile), mimeType)
|
||||||
|
if (editedMimeType != mimeType) {
|
||||||
|
throw Exception("editing Exif changes mimeType=$mimeType -> $editedMimeType for uri=$uri path=$path")
|
||||||
|
}
|
||||||
|
|
||||||
if (videoBytes != null) {
|
if (videoBytes != null) {
|
||||||
// append trailer video, if any
|
// append trailer video, if any
|
||||||
editableFile.appendBytes(videoBytes!!)
|
editableFile.appendBytes(videoBytes!!)
|
||||||
|
@ -730,8 +758,10 @@ abstract class ImageProvider {
|
||||||
when {
|
when {
|
||||||
iptc != null ->
|
iptc != null ->
|
||||||
PixyMetaHelper.setIptc(input, output, iptc)
|
PixyMetaHelper.setIptc(input, output, iptc)
|
||||||
|
|
||||||
canRemoveMetadata(mimeType) ->
|
canRemoveMetadata(mimeType) ->
|
||||||
PixyMetaHelper.removeMetadata(input, output, setOf(TYPE_IPTC))
|
PixyMetaHelper.removeMetadata(input, output, setOf(TYPE_IPTC))
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
Log.w(LOG_TAG, "setting empty IPTC for mimeType=$mimeType")
|
Log.w(LOG_TAG, "setting empty IPTC for mimeType=$mimeType")
|
||||||
PixyMetaHelper.setIptc(input, output, null)
|
PixyMetaHelper.setIptc(input, output, null)
|
||||||
|
@ -787,6 +817,7 @@ abstract class ImageProvider {
|
||||||
newFields["rotationDegrees"] = degrees
|
newFields["rotationDegrees"] = degrees
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"xmp" -> isoFile.updateXmp(value)
|
"xmp" -> isoFile.updateXmp(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1039,6 +1070,7 @@ abstract class ImageProvider {
|
||||||
exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, ExifInterfaceHelper.GPS_TIME_FORMAT.format(date))
|
exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, ExifInterfaceHelper.GPS_TIME_FORMAT.format(date))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
shiftMinutes != null -> {
|
shiftMinutes != null -> {
|
||||||
// shift
|
// shift
|
||||||
val shiftMillis = shiftMinutes * 60000
|
val shiftMillis = shiftMinutes * 60000
|
||||||
|
@ -1067,6 +1099,7 @@ abstract class ImageProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
// clear
|
// clear
|
||||||
if (fields.contains(ExifInterface.TAG_DATETIME)) {
|
if (fields.contains(ExifInterface.TAG_DATETIME)) {
|
||||||
|
@ -1135,6 +1168,7 @@ abstract class ImageProvider {
|
||||||
ExifInterface.TAG_GPS_LONGITUDE_REF -> {
|
ExifInterface.TAG_GPS_LONGITUDE_REF -> {
|
||||||
setLocation = true
|
setLocation = true
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
if (value is String) {
|
if (value is String) {
|
||||||
exif.setAttribute(tag, value)
|
exif.setAttribute(tag, value)
|
||||||
|
|
|
@ -25,6 +25,7 @@ import deckers.thibault.aves.utils.MimeTypes.isVideo
|
||||||
import deckers.thibault.aves.utils.PermissionManager.getGrantedDirForPath
|
import deckers.thibault.aves.utils.PermissionManager.getGrantedDirForPath
|
||||||
import deckers.thibault.aves.utils.UriUtils.tryParseId
|
import deckers.thibault.aves.utils.UriUtils.tryParseId
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -588,6 +589,7 @@ object StorageUtils {
|
||||||
// e.g. `content://media/external_primary/downloads/...`
|
// e.g. `content://media/external_primary/downloads/...`
|
||||||
getMediaUriImageVideoUri(uri, mimeType)?.let { imageVideUri -> return imageVideUri }
|
getMediaUriImageVideoUri(uri, mimeType)?.let { imageVideUri -> return imageVideUri }
|
||||||
}
|
}
|
||||||
|
|
||||||
uriPath?.contains("/file/") == true -> {
|
uriPath?.contains("/file/") == true -> {
|
||||||
// e.g. `content://media/external/file/...`
|
// e.g. `content://media/external/file/...`
|
||||||
// create an ad-hoc temporary file for decoding only
|
// create an ad-hoc temporary file for decoding only
|
||||||
|
@ -601,6 +603,7 @@ object StorageUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uri.userInfo != null -> return stripMediaUriUserInfo(uri)
|
uri.userInfo != null -> return stripMediaUriUserInfo(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -617,6 +620,7 @@ object StorageUtils {
|
||||||
// e.g. `content://media/external_primary/downloads/...`
|
// e.g. `content://media/external_primary/downloads/...`
|
||||||
getMediaUriImageVideoUri(uri, mimeType)?.let { imageVideUri -> return imageVideUri }
|
getMediaUriImageVideoUri(uri, mimeType)?.let { imageVideUri -> return imageVideUri }
|
||||||
}
|
}
|
||||||
|
|
||||||
uri.userInfo != null -> return stripMediaUriUserInfo(uri)
|
uri.userInfo != null -> return stripMediaUriUserInfo(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -643,7 +647,10 @@ object StorageUtils {
|
||||||
fun openInputStream(context: Context, uri: Uri): InputStream? {
|
fun openInputStream(context: Context, uri: Uri): InputStream? {
|
||||||
val effectiveUri = getOriginalUri(context, uri)
|
val effectiveUri = getOriginalUri(context, uri)
|
||||||
return try {
|
return try {
|
||||||
context.contentResolver.openInputStream(effectiveUri)
|
return when (uri.scheme) {
|
||||||
|
ContentResolver.SCHEME_FILE -> FileInputStream(uri.path)
|
||||||
|
else -> context.contentResolver.openInputStream(effectiveUri)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// among various other exceptions,
|
// among various other exceptions,
|
||||||
// opening a file marked pending and owned by another package throws an `IllegalStateException`
|
// opening a file marked pending and owned by another package throws an `IllegalStateException`
|
||||||
|
|
Loading…
Reference in a new issue