fixed motion photo export

This commit is contained in:
Thibault Deckers 2021-04-26 19:00:45 +09:00
parent 768a077857
commit 2be011e66a
2 changed files with 71 additions and 60 deletions

View file

@ -24,6 +24,9 @@ import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.BmpWriter import deckers.thibault.aves.utils.BmpWriter
import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.LogUtils
import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.MimeTypes.extensionFor
import deckers.thibault.aves.utils.MimeTypes.isImage
import deckers.thibault.aves.utils.MimeTypes.isVideo
import deckers.thibault.aves.utils.StorageUtils.copyFileToTemp import deckers.thibault.aves.utils.StorageUtils.copyFileToTemp
import deckers.thibault.aves.utils.StorageUtils.createDirectoryIfAbsent import deckers.thibault.aves.utils.StorageUtils.createDirectoryIfAbsent
import deckers.thibault.aves.utils.StorageUtils.getDocumentFile import deckers.thibault.aves.utils.StorageUtils.getDocumentFile
@ -51,11 +54,15 @@ abstract class ImageProvider {
suspend fun exportMultiple( suspend fun exportMultiple(
context: Context, context: Context,
mimeType: String, imageExportMimeType: String,
destinationDir: String, destinationDir: String,
entries: List<AvesEntry>, entries: List<AvesEntry>,
callback: ImageOpCallback, callback: ImageOpCallback,
) { ) {
if (!supportedExportMimeTypes.contains(imageExportMimeType)) {
throw Exception("unsupported export MIME type=$imageExportMimeType")
}
val destinationDirDocFile = createDirectoryIfAbsent(context, destinationDir) val destinationDirDocFile = createDirectoryIfAbsent(context, destinationDir)
if (destinationDirDocFile == null) { if (destinationDirDocFile == null) {
callback.onFailure(Exception("failed to create directory at path=$destinationDir")) callback.onFailure(Exception("failed to create directory at path=$destinationDir"))
@ -73,13 +80,15 @@ abstract class ImageProvider {
"success" to false, "success" to false,
) )
val sourceMimeType = entry.mimeType
val exportMimeType = if (isVideo(sourceMimeType)) sourceMimeType else imageExportMimeType
try { try {
val newFields = exportSingleByTreeDocAndScan( val newFields = exportSingleByTreeDocAndScan(
context = context, context = context,
sourceEntry = entry, sourceEntry = entry,
destinationDir = destinationDir, destinationDir = destinationDir,
destinationDirDocFile = destinationDirDocFile, destinationDirDocFile = destinationDirDocFile,
exportMimeType = mimeType, exportMimeType = exportMimeType,
) )
result["newFields"] = newFields result["newFields"] = newFields
result["success"] = true result["success"] = true
@ -113,13 +122,7 @@ abstract class ImageProvider {
val page = if (sourceMimeType == MimeTypes.TIFF) pageId + 1 else pageId val page = if (sourceMimeType == MimeTypes.TIFF) pageId + 1 else pageId
desiredNameWithoutExtension += "_${page.toString().padStart(3, '0')}" desiredNameWithoutExtension += "_${page.toString().padStart(3, '0')}"
} }
val desiredFileName = desiredNameWithoutExtension + when (exportMimeType) { val desiredFileName = desiredNameWithoutExtension + extensionFor(exportMimeType)
MimeTypes.BMP -> ".bmp"
MimeTypes.JPEG -> ".jpg"
MimeTypes.PNG -> ".png"
MimeTypes.WEBP -> ".webp"
else -> throw Exception("unsupported export MIME type=$exportMimeType")
}
if (File(destinationDir, desiredFileName).exists()) { if (File(destinationDir, desiredFileName).exists()) {
throw Exception("file with name=$desiredFileName already exists in destination directory") throw Exception("file with name=$desiredFileName already exists in destination directory")
@ -133,6 +136,11 @@ abstract class ImageProvider {
val destinationTreeFile = destinationDirDocFile.createFile(exportMimeType, desiredNameWithoutExtension) val destinationTreeFile = destinationDirDocFile.createFile(exportMimeType, desiredNameWithoutExtension)
val destinationDocFile = DocumentFileCompat.fromSingleUri(context, destinationTreeFile.uri) val destinationDocFile = DocumentFileCompat.fromSingleUri(context, destinationTreeFile.uri)
if (isVideo(sourceMimeType)) {
val sourceDocFile = DocumentFileCompat.fromSingleUri(context, sourceUri)
@Suppress("BlockingMethodInNonBlockingContext")
sourceDocFile.copyTo(destinationDocFile)
} else {
val model: Any = if (MimeTypes.isHeic(sourceMimeType) && pageId != null) { val model: Any = if (MimeTypes.isHeic(sourceMimeType) && pageId != null) {
MultiTrackImage(context, sourceUri, pageId) MultiTrackImage(context, sourceUri, pageId)
} else if (sourceMimeType == MimeTypes.TIFF) { } else if (sourceMimeType == MimeTypes.TIFF) {
@ -161,9 +169,9 @@ abstract class ImageProvider {
bitmap ?: throw Exception("failed to get image from uri=$sourceUri page=$pageId") bitmap ?: throw Exception("failed to get image from uri=$sourceUri page=$pageId")
@Suppress("BlockingMethodInNonBlockingContext") @Suppress("BlockingMethodInNonBlockingContext")
destinationDocFile.openOutputStream().use { destinationDocFile.openOutputStream().use { output ->
if (exportMimeType == MimeTypes.BMP) { if (exportMimeType == MimeTypes.BMP) {
BmpWriter.writeRGB24(bitmap, it) BmpWriter.writeRGB24(bitmap, output)
} else { } else {
val quality = 100 val quality = 100
val format = when (exportMimeType) { val format = when (exportMimeType) {
@ -181,12 +189,13 @@ abstract class ImageProvider {
} }
else -> throw Exception("unsupported export MIME type=$exportMimeType") else -> throw Exception("unsupported export MIME type=$exportMimeType")
} }
bitmap.compress(format, quality, it) bitmap.compress(format, quality, output)
} }
} }
} finally { } finally {
Glide.with(context).clear(target) Glide.with(context).clear(target)
} }
}
val fileName = destinationDocFile.name val fileName = destinationDocFile.name
val destinationFullPath = destinationDir + fileName val destinationFullPath = destinationDir + fileName
@ -306,9 +315,9 @@ abstract class ImageProvider {
// but we need an image/video media URI (e.g. "content://media/external/images/media/62872") // but we need an image/video media URI (e.g. "content://media/external/images/media/62872")
contentId = newUri.tryParseId() contentId = newUri.tryParseId()
if (contentId != null) { if (contentId != null) {
if (MimeTypes.isImage(mimeType)) { if (isImage(mimeType)) {
contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentId) contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentId)
} else if (MimeTypes.isVideo(mimeType)) { } else if (isVideo(mimeType)) {
contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentId) contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentId)
} }
} }
@ -356,5 +365,7 @@ abstract class ImageProvider {
companion object { companion object {
private val LOG_TAG = LogUtils.createTag<ImageProvider>() private val LOG_TAG = LogUtils.createTag<ImageProvider>()
val supportedExportMimeTypes = listOf(MimeTypes.BMP, MimeTypes.JPEG, MimeTypes.PNG, MimeTypes.WEBP)
} }
} }

View file

@ -60,7 +60,7 @@ class Durations {
static const doubleBackTimerDelay = Duration(milliseconds: 1000); static const doubleBackTimerDelay = Duration(milliseconds: 1000);
static const softKeyboardDisplayDelay = Duration(milliseconds: 300); static const softKeyboardDisplayDelay = Duration(milliseconds: 300);
static const searchDebounceDelay = Duration(milliseconds: 250); static const searchDebounceDelay = Duration(milliseconds: 250);
static const contentChangeDebounceDelay = Duration(milliseconds: 500); static const contentChangeDebounceDelay = Duration(milliseconds: 1000);
// app life // app life
static const lastVersionCheckInterval = Duration(days: 7); static const lastVersionCheckInterval = Duration(days: 7);