fixed motion photo export
This commit is contained in:
parent
768a077857
commit
2be011e66a
2 changed files with 71 additions and 60 deletions
|
@ -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,59 +136,65 @@ 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)
|
||||||
|
|
||||||
val model: Any = if (MimeTypes.isHeic(sourceMimeType) && pageId != null) {
|
if (isVideo(sourceMimeType)) {
|
||||||
MultiTrackImage(context, sourceUri, pageId)
|
val sourceDocFile = DocumentFileCompat.fromSingleUri(context, sourceUri)
|
||||||
} else if (sourceMimeType == MimeTypes.TIFF) {
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
TiffImage(context, sourceUri, pageId)
|
sourceDocFile.copyTo(destinationDocFile)
|
||||||
} else {
|
} else {
|
||||||
sourceUri
|
val model: Any = if (MimeTypes.isHeic(sourceMimeType) && pageId != null) {
|
||||||
}
|
MultiTrackImage(context, sourceUri, pageId)
|
||||||
|
} else if (sourceMimeType == MimeTypes.TIFF) {
|
||||||
// request a fresh image with the highest quality format
|
TiffImage(context, sourceUri, pageId)
|
||||||
val glideOptions = RequestOptions()
|
} else {
|
||||||
.format(DecodeFormat.PREFER_ARGB_8888)
|
sourceUri
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
|
|
||||||
val target = Glide.with(context)
|
|
||||||
.asBitmap()
|
|
||||||
.apply(glideOptions)
|
|
||||||
.load(model)
|
|
||||||
.submit()
|
|
||||||
try {
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
|
||||||
var bitmap = target.get()
|
|
||||||
if (MimeTypes.needRotationAfterGlide(sourceMimeType)) {
|
|
||||||
bitmap = BitmapUtils.applyExifOrientation(context, bitmap, sourceEntry.rotationDegrees, sourceEntry.isFlipped)
|
|
||||||
}
|
}
|
||||||
bitmap ?: throw Exception("failed to get image from uri=$sourceUri page=$pageId")
|
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
// request a fresh image with the highest quality format
|
||||||
destinationDocFile.openOutputStream().use {
|
val glideOptions = RequestOptions()
|
||||||
if (exportMimeType == MimeTypes.BMP) {
|
.format(DecodeFormat.PREFER_ARGB_8888)
|
||||||
BmpWriter.writeRGB24(bitmap, it)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
} else {
|
.skipMemoryCache(true)
|
||||||
val quality = 100
|
|
||||||
val format = when (exportMimeType) {
|
val target = Glide.with(context)
|
||||||
MimeTypes.JPEG -> Bitmap.CompressFormat.JPEG
|
.asBitmap()
|
||||||
MimeTypes.PNG -> Bitmap.CompressFormat.PNG
|
.apply(glideOptions)
|
||||||
MimeTypes.WEBP -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
.load(model)
|
||||||
if (quality == 100) {
|
.submit()
|
||||||
Bitmap.CompressFormat.WEBP_LOSSLESS
|
try {
|
||||||
} else {
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
Bitmap.CompressFormat.WEBP_LOSSY
|
var bitmap = target.get()
|
||||||
}
|
if (MimeTypes.needRotationAfterGlide(sourceMimeType)) {
|
||||||
} else {
|
bitmap = BitmapUtils.applyExifOrientation(context, bitmap, sourceEntry.rotationDegrees, sourceEntry.isFlipped)
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
Bitmap.CompressFormat.WEBP
|
|
||||||
}
|
|
||||||
else -> throw Exception("unsupported export MIME type=$exportMimeType")
|
|
||||||
}
|
|
||||||
bitmap.compress(format, quality, it)
|
|
||||||
}
|
}
|
||||||
|
bitmap ?: throw Exception("failed to get image from uri=$sourceUri page=$pageId")
|
||||||
|
|
||||||
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
|
destinationDocFile.openOutputStream().use { output ->
|
||||||
|
if (exportMimeType == MimeTypes.BMP) {
|
||||||
|
BmpWriter.writeRGB24(bitmap, output)
|
||||||
|
} else {
|
||||||
|
val quality = 100
|
||||||
|
val format = when (exportMimeType) {
|
||||||
|
MimeTypes.JPEG -> Bitmap.CompressFormat.JPEG
|
||||||
|
MimeTypes.PNG -> Bitmap.CompressFormat.PNG
|
||||||
|
MimeTypes.WEBP -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
if (quality == 100) {
|
||||||
|
Bitmap.CompressFormat.WEBP_LOSSLESS
|
||||||
|
} else {
|
||||||
|
Bitmap.CompressFormat.WEBP_LOSSY
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
Bitmap.CompressFormat.WEBP
|
||||||
|
}
|
||||||
|
else -> throw Exception("unsupported export MIME type=$exportMimeType")
|
||||||
|
}
|
||||||
|
bitmap.compress(format, quality, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
Glide.with(context).clear(target)
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
Glide.with(context).clear(target)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val fileName = destinationDocFile.name
|
val fileName = destinationDocFile.name
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue