#838 viewer: mpf multipage retrieval / thumbnails
This commit is contained in:
parent
82b4c8aaa1
commit
16aa283425
11 changed files with 217 additions and 121 deletions
|
@ -20,7 +20,6 @@ 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.metadata.metadataextractor.mpf.MpEntry
|
import deckers.thibault.aves.metadata.metadataextractor.mpf.MpEntry
|
||||||
import deckers.thibault.aves.metadata.metadataextractor.mpf.MpEntryDirectory
|
|
||||||
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.ContentImageProvider
|
||||||
import deckers.thibault.aves.model.provider.ImageProvider
|
import deckers.thibault.aves.model.provider.ImageProvider
|
||||||
|
@ -51,7 +50,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
"getExifThumbnails" -> ioScope.launch { safeSuspend(call, result, ::getExifThumbnails) }
|
"getExifThumbnails" -> ioScope.launch { safeSuspend(call, result, ::getExifThumbnails) }
|
||||||
"extractGoogleDeviceItem" -> ioScope.launch { safe(call, result, ::extractGoogleDeviceItem) }
|
"extractGoogleDeviceItem" -> ioScope.launch { safe(call, result, ::extractGoogleDeviceItem) }
|
||||||
"extractJpegMultiPictureFormat" -> ioScope.launch { safe(call, result, ::extractJpegMultiPictureFormat) }
|
"extractJpegMpfItem" -> ioScope.launch { safe(call, result, ::extractJpegMpfItem) }
|
||||||
"extractMotionPhotoImage" -> ioScope.launch { safe(call, result, ::extractMotionPhotoImage) }
|
"extractMotionPhotoImage" -> ioScope.launch { safe(call, result, ::extractMotionPhotoImage) }
|
||||||
"extractMotionPhotoVideo" -> ioScope.launch { safe(call, result, ::extractMotionPhotoVideo) }
|
"extractMotionPhotoVideo" -> ioScope.launch { safe(call, result, ::extractMotionPhotoVideo) }
|
||||||
"extractVideoEmbeddedPicture" -> ioScope.launch { safe(call, result, ::extractVideoEmbeddedPicture) }
|
"extractVideoEmbeddedPicture" -> ioScope.launch { safe(call, result, ::extractVideoEmbeddedPicture) }
|
||||||
|
@ -151,48 +150,38 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
|
||||||
result.error("extractGoogleDeviceItem-empty", "failed to extract item from Google Device XMP at uri=$uri dataUri=$dataUri", null)
|
result.error("extractGoogleDeviceItem-empty", "failed to extract item from Google Device XMP at uri=$uri dataUri=$dataUri", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractJpegMultiPictureFormat(call: MethodCall, result: MethodChannel.Result) {
|
private fun extractJpegMpfItem(call: MethodCall, result: MethodChannel.Result) {
|
||||||
val mimeType = call.argument<String>("mimeType")
|
val mimeType = call.argument<String>("mimeType")
|
||||||
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
|
||||||
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
|
val sizeBytes = call.argument<Number>("sizeBytes")?.toLong()
|
||||||
val displayName = call.argument<String>("displayName")
|
val displayName = call.argument<String>("displayName")
|
||||||
val id = call.argument<Int>("id")
|
val id = call.argument<Int>("id")
|
||||||
if (mimeType == null || uri == null || sizeBytes == null || id == null) {
|
if (mimeType == null || uri == null || sizeBytes == null || id == null) {
|
||||||
result.error("extractJpegMultiPictureFormat-args", "missing arguments", null)
|
result.error("extractJpegMpfItem-args", "missing arguments", null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canReadWithMetadataExtractor(mimeType)) {
|
val pageIndex = id - 1
|
||||||
try {
|
val mpEntries = MultiPage.getJpegMpfEntries(context, uri)
|
||||||
Metadata.openSafeInputStream(context, uri, mimeType, sizeBytes)?.use { input ->
|
if (mpEntries != null && pageIndex < mpEntries.size) {
|
||||||
val metadata = Helper.safeRead(input)
|
val mpEntry = mpEntries[pageIndex]
|
||||||
metadata.getDirectoriesOfType(MpEntryDirectory::class.java).first { it.id == id }?.let { dir ->
|
MpEntry.getMimeType(mpEntry.format)?.let { embedMimeType ->
|
||||||
val mpEntry = dir.entry
|
var dataOffset = mpEntry.dataOffset
|
||||||
MpEntry.getMimeType(dir.entry.format)?.let { embedMimeType ->
|
if (dataOffset > 0) {
|
||||||
var dataOffset = mpEntry.dataOffset
|
val baseOffset = MultiPage.getJpegMpfBaseOffset(context, uri)
|
||||||
if (dataOffset > 0) {
|
if (baseOffset != null) {
|
||||||
val baseOffset = MultiPage.getJpegMultiPictureFormatBaseOffset(context, uri, sizeBytes)
|
dataOffset += baseOffset
|
||||||
if (baseOffset != null) {
|
|
||||||
dataOffset += baseOffset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StorageUtils.openInputStream(context, uri)?.let { input ->
|
|
||||||
input.skip(dataOffset)
|
|
||||||
copyEmbeddedBytes(result, embedMimeType, displayName, input, mpEntry.size)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
StorageUtils.openInputStream(context, uri)?.let { input ->
|
||||||
Log.w(LOG_TAG, "failed to extract file from MPF", e)
|
input.skip(dataOffset)
|
||||||
} catch (e: NoClassDefFoundError) {
|
copyEmbeddedBytes(result, embedMimeType, displayName, input, mpEntry.size)
|
||||||
Log.w(LOG_TAG, "failed to extract file from MPF", e)
|
}
|
||||||
} catch (e: AssertionError) {
|
return
|
||||||
Log.w(LOG_TAG, "failed to extract file from MPF", e)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result.error("extractJpegMultiPictureFormat-empty", "failed to extract file index=$id from MPF at uri=$uri", null)
|
|
||||||
|
result.error("extractJpegMpfItem-empty", "failed to extract file index=$id from MPF at uri=$uri", null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractMotionPhotoImage(call: MethodCall, result: MethodChannel.Result) {
|
private fun extractMotionPhotoImage(call: MethodCall, result: MethodChannel.Result) {
|
||||||
|
|
|
@ -933,10 +933,11 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
val pages: ArrayList<FieldMap>? = if (isMotionPhoto) {
|
val pages: ArrayList<FieldMap>? = if (isMotionPhoto) {
|
||||||
MultiPage.getMotionPhotoPages(context, uri, mimeType, sizeBytes = sizeBytes)
|
MultiPage.getMotionPhotoPages(context, uri, mimeType, sizeBytes)
|
||||||
} else {
|
} else {
|
||||||
when (mimeType) {
|
when (mimeType) {
|
||||||
MimeTypes.HEIC, MimeTypes.HEIF -> MultiPage.getHeicTracks(context, uri)
|
MimeTypes.HEIC, MimeTypes.HEIF -> MultiPage.getHeicTracks(context, uri)
|
||||||
|
MimeTypes.JPEG -> MultiPage.getJpegMpfPages(context, uri)
|
||||||
MimeTypes.TIFF -> MultiPage.getTiffPages(context, uri)
|
MimeTypes.TIFF -> MultiPage.getTiffPages(context, uri)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.DecodeFormat
|
import com.bumptech.glide.load.DecodeFormat
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import deckers.thibault.aves.decoder.MultiTrackImage
|
import deckers.thibault.aves.decoder.MultiPageImage
|
||||||
import deckers.thibault.aves.utils.BitmapRegionDecoderCompat
|
import deckers.thibault.aves.utils.BitmapRegionDecoderCompat
|
||||||
import deckers.thibault.aves.utils.BitmapUtils.getBytes
|
import deckers.thibault.aves.utils.BitmapUtils.getBytes
|
||||||
import deckers.thibault.aves.utils.MimeTypes
|
import deckers.thibault.aves.utils.MimeTypes
|
||||||
|
@ -40,10 +40,10 @@ class RegionFetcher internal constructor(
|
||||||
imageHeight: Int,
|
imageHeight: Int,
|
||||||
result: MethodChannel.Result,
|
result: MethodChannel.Result,
|
||||||
) {
|
) {
|
||||||
if (MimeTypes.isHeic(mimeType) && pageId != null) {
|
if (pageId != null && MultiPageImage.isSupported(mimeType)) {
|
||||||
val id = Pair(uri, pageId)
|
val id = Pair(uri, pageId)
|
||||||
fetch(
|
fetch(
|
||||||
uri = pageTempUris.getOrPut(id) { createJpegForPage(uri, pageId) },
|
uri = pageTempUris.getOrPut(id) { createJpegForPage(uri, mimeType, pageId) },
|
||||||
mimeType = MimeTypes.JPEG,
|
mimeType = MimeTypes.JPEG,
|
||||||
pageId = null,
|
pageId = null,
|
||||||
sampleSize = sampleSize,
|
sampleSize = sampleSize,
|
||||||
|
@ -104,11 +104,11 @@ class RegionFetcher internal constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createJpegForPage(sourceUri: Uri, pageId: Int): Uri {
|
private fun createJpegForPage(sourceUri: Uri, mimeType: String, pageId: Int): Uri {
|
||||||
val target = Glide.with(context)
|
val target = Glide.with(context)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.apply(multiTrackGlideOptions)
|
.apply(multiTrackGlideOptions)
|
||||||
.load(MultiTrackImage(context, sourceUri, pageId))
|
.load(MultiPageImage(context, sourceUri, mimeType, pageId))
|
||||||
.submit()
|
.submit()
|
||||||
try {
|
try {
|
||||||
val bitmap = target.get()
|
val bitmap = target.get()
|
||||||
|
|
|
@ -12,7 +12,7 @@ import com.bumptech.glide.load.DecodeFormat
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.bumptech.glide.signature.ObjectKey
|
import com.bumptech.glide.signature.ObjectKey
|
||||||
import deckers.thibault.aves.decoder.MultiTrackImage
|
import deckers.thibault.aves.decoder.MultiPageImage
|
||||||
import deckers.thibault.aves.decoder.SvgImage
|
import deckers.thibault.aves.decoder.SvgImage
|
||||||
import deckers.thibault.aves.decoder.TiffImage
|
import deckers.thibault.aves.decoder.TiffImage
|
||||||
import deckers.thibault.aves.decoder.VideoThumbnail
|
import deckers.thibault.aves.decoder.VideoThumbnail
|
||||||
|
@ -20,7 +20,6 @@ import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation
|
||||||
import deckers.thibault.aves.utils.BitmapUtils.getBytes
|
import deckers.thibault.aves.utils.BitmapUtils.getBytes
|
||||||
import deckers.thibault.aves.utils.MimeTypes
|
import deckers.thibault.aves.utils.MimeTypes
|
||||||
import deckers.thibault.aves.utils.MimeTypes.SVG
|
import deckers.thibault.aves.utils.MimeTypes.SVG
|
||||||
import deckers.thibault.aves.utils.MimeTypes.isHeic
|
|
||||||
import deckers.thibault.aves.utils.MimeTypes.isVideo
|
import deckers.thibault.aves.utils.MimeTypes.isVideo
|
||||||
import deckers.thibault.aves.utils.MimeTypes.needRotationAfterContentResolverThumbnail
|
import deckers.thibault.aves.utils.MimeTypes.needRotationAfterContentResolverThumbnail
|
||||||
import deckers.thibault.aves.utils.MimeTypes.needRotationAfterGlide
|
import deckers.thibault.aves.utils.MimeTypes.needRotationAfterGlide
|
||||||
|
@ -47,8 +46,8 @@ class ThumbnailFetcher internal constructor(
|
||||||
private val height: Int = if (height?.takeIf { it > 0 } != null) height else defaultSize
|
private val height: Int = if (height?.takeIf { it > 0 } != null) height else defaultSize
|
||||||
private val svgFetch = mimeType == SVG
|
private val svgFetch = mimeType == SVG
|
||||||
private val tiffFetch = mimeType == MimeTypes.TIFF
|
private val tiffFetch = mimeType == MimeTypes.TIFF
|
||||||
private val multiTrackFetch = isHeic(mimeType) && pageId != null
|
private val multiPageFetch = pageId != null && MultiPageImage.isSupported(mimeType)
|
||||||
private val customFetch = svgFetch || tiffFetch || multiTrackFetch
|
private val customFetch = svgFetch || tiffFetch || multiPageFetch
|
||||||
|
|
||||||
suspend fun fetch() {
|
suspend fun fetch() {
|
||||||
var bitmap: Bitmap? = null
|
var bitmap: Bitmap? = null
|
||||||
|
@ -135,7 +134,7 @@ class ThumbnailFetcher internal constructor(
|
||||||
val model: Any = when {
|
val model: Any = when {
|
||||||
svgFetch -> SvgImage(context, uri)
|
svgFetch -> SvgImage(context, uri)
|
||||||
tiffFetch -> TiffImage(context, uri, pageId)
|
tiffFetch -> TiffImage(context, uri, pageId)
|
||||||
multiTrackFetch -> MultiTrackImage(context, uri, pageId)
|
multiPageFetch -> MultiPageImage(context, uri, mimeType, pageId)
|
||||||
else -> StorageUtils.getGlideSafeUri(context, uri, mimeType)
|
else -> StorageUtils.getGlideSafeUri(context, uri, mimeType)
|
||||||
}
|
}
|
||||||
Glide.with(context)
|
Glide.with(context)
|
||||||
|
|
|
@ -9,7 +9,7 @@ import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.DecodeFormat
|
import com.bumptech.glide.load.DecodeFormat
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import deckers.thibault.aves.decoder.MultiTrackImage
|
import deckers.thibault.aves.decoder.MultiPageImage
|
||||||
import deckers.thibault.aves.decoder.TiffImage
|
import deckers.thibault.aves.decoder.TiffImage
|
||||||
import deckers.thibault.aves.decoder.VideoThumbnail
|
import deckers.thibault.aves.decoder.VideoThumbnail
|
||||||
import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation
|
import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation
|
||||||
|
@ -18,7 +18,6 @@ import deckers.thibault.aves.utils.LogUtils
|
||||||
import deckers.thibault.aves.utils.MemoryUtils
|
import deckers.thibault.aves.utils.MemoryUtils
|
||||||
import deckers.thibault.aves.utils.MimeTypes
|
import deckers.thibault.aves.utils.MimeTypes
|
||||||
import deckers.thibault.aves.utils.MimeTypes.canDecodeWithFlutter
|
import deckers.thibault.aves.utils.MimeTypes.canDecodeWithFlutter
|
||||||
import deckers.thibault.aves.utils.MimeTypes.isHeic
|
|
||||||
import deckers.thibault.aves.utils.MimeTypes.isVideo
|
import deckers.thibault.aves.utils.MimeTypes.isVideo
|
||||||
import deckers.thibault.aves.utils.MimeTypes.needRotationAfterGlide
|
import deckers.thibault.aves.utils.MimeTypes.needRotationAfterGlide
|
||||||
import deckers.thibault.aves.utils.StorageUtils
|
import deckers.thibault.aves.utils.StorageUtils
|
||||||
|
@ -131,8 +130,8 @@ class ImageByteStreamHandler(private val context: Context, private val arguments
|
||||||
rotationDegrees: Int,
|
rotationDegrees: Int,
|
||||||
isFlipped: Boolean,
|
isFlipped: Boolean,
|
||||||
) {
|
) {
|
||||||
val model: Any = if (isHeic(mimeType) && pageId != null) {
|
val model: Any = if (pageId != null && MultiPageImage.isSupported(mimeType)) {
|
||||||
MultiTrackImage(context, uri, pageId)
|
MultiPageImage(context, uri, mimeType, pageId)
|
||||||
} else if (mimeType == MimeTypes.TIFF) {
|
} else if (mimeType == MimeTypes.TIFF) {
|
||||||
TiffImage(context, uri, pageId)
|
TiffImage(context, uri, pageId)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -17,32 +17,38 @@ import com.bumptech.glide.load.model.ModelLoaderFactory
|
||||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
||||||
import com.bumptech.glide.module.LibraryGlideModule
|
import com.bumptech.glide.module.LibraryGlideModule
|
||||||
import com.bumptech.glide.signature.ObjectKey
|
import com.bumptech.glide.signature.ObjectKey
|
||||||
|
import deckers.thibault.aves.metadata.MultiPage
|
||||||
import deckers.thibault.aves.metadata.MultiTrackMedia
|
import deckers.thibault.aves.metadata.MultiTrackMedia
|
||||||
|
import deckers.thibault.aves.utils.MimeTypes
|
||||||
|
|
||||||
@GlideModule
|
@GlideModule
|
||||||
class MultiTrackImageGlideModule : LibraryGlideModule() {
|
class MultiPageImageGlideModule : LibraryGlideModule() {
|
||||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||||
registry.append(MultiTrackImage::class.java, Bitmap::class.java, MultiTrackThumbnailLoader.Factory())
|
registry.append(MultiPageImage::class.java, Bitmap::class.java, MultiPageThumbnailLoader.Factory())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MultiTrackImage(val context: Context, val uri: Uri, val trackIndex: Int?)
|
class MultiPageImage(val context: Context, val uri: Uri, val mimeType: String, val pageId: Int?) {
|
||||||
|
companion object {
|
||||||
|
fun isSupported(mimeType: String) = MimeTypes.isHeic(mimeType) || mimeType == MimeTypes.JPEG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal class MultiTrackThumbnailLoader : ModelLoader<MultiTrackImage, Bitmap> {
|
internal class MultiPageThumbnailLoader : ModelLoader<MultiPageImage, Bitmap> {
|
||||||
override fun buildLoadData(model: MultiTrackImage, width: Int, height: Int, options: Options): ModelLoader.LoadData<Bitmap> {
|
override fun buildLoadData(model: MultiPageImage, width: Int, height: Int, options: Options): ModelLoader.LoadData<Bitmap> {
|
||||||
return ModelLoader.LoadData(ObjectKey(model.uri), MultiTrackImageFetcher(model, width, height))
|
return ModelLoader.LoadData(ObjectKey(model.uri), MultiPageImageFetcher(model, width, height))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun handles(model: MultiTrackImage): Boolean = true
|
override fun handles(model: MultiPageImage): Boolean = true
|
||||||
|
|
||||||
internal class Factory : ModelLoaderFactory<MultiTrackImage, Bitmap> {
|
internal class Factory : ModelLoaderFactory<MultiPageImage, Bitmap> {
|
||||||
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<MultiTrackImage, Bitmap> = MultiTrackThumbnailLoader()
|
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<MultiPageImage, Bitmap> = MultiPageThumbnailLoader()
|
||||||
|
|
||||||
override fun teardown() {}
|
override fun teardown() {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class MultiTrackImageFetcher(val model: MultiTrackImage, val width: Int, val height: Int) : DataFetcher<Bitmap> {
|
internal class MultiPageImageFetcher(val model: MultiPageImage, val width: Int, val height: Int) : DataFetcher<Bitmap> {
|
||||||
override fun loadData(priority: Priority, callback: DataCallback<in Bitmap>) {
|
override fun loadData(priority: Priority, callback: DataCallback<in Bitmap>) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||||
callback.onLoadFailed(Exception("unsupported Android version"))
|
callback.onLoadFailed(Exception("unsupported Android version"))
|
||||||
|
@ -51,9 +57,17 @@ internal class MultiTrackImageFetcher(val model: MultiTrackImage, val width: Int
|
||||||
|
|
||||||
val context = model.context
|
val context = model.context
|
||||||
val uri = model.uri
|
val uri = model.uri
|
||||||
val trackIndex = model.trackIndex
|
val mimeType = model.mimeType
|
||||||
|
|
||||||
|
var bitmap: Bitmap? = null
|
||||||
|
if (MimeTypes.isHeic(mimeType)) {
|
||||||
|
val trackIndex = model.pageId
|
||||||
|
bitmap = MultiTrackMedia.getImage(context, uri, trackIndex)
|
||||||
|
} else if (mimeType == MimeTypes.JPEG) {
|
||||||
|
val pageIndex = model.pageId ?: 0
|
||||||
|
bitmap = MultiPage.getJpegMpfBitmap(context, uri, pageIndex)
|
||||||
|
}
|
||||||
|
|
||||||
val bitmap = MultiTrackMedia.getImage(context, uri, trackIndex)
|
|
||||||
if (bitmap == null) {
|
if (bitmap == null) {
|
||||||
callback.onLoadFailed(Exception("null bitmap"))
|
callback.onLoadFailed(Exception("null bitmap"))
|
||||||
} else {
|
} else {
|
|
@ -1,6 +1,8 @@
|
||||||
package deckers.thibault.aves.metadata
|
package deckers.thibault.aves.metadata
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.media.MediaExtractor
|
import android.media.MediaExtractor
|
||||||
import android.media.MediaFormat
|
import android.media.MediaFormat
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
@ -15,9 +17,12 @@ import deckers.thibault.aves.metadata.XMP.doesPropExist
|
||||||
import deckers.thibault.aves.metadata.XMP.getSafeLong
|
import deckers.thibault.aves.metadata.XMP.getSafeLong
|
||||||
import deckers.thibault.aves.metadata.XMP.getSafeStructField
|
import deckers.thibault.aves.metadata.XMP.getSafeStructField
|
||||||
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
||||||
|
import deckers.thibault.aves.metadata.metadataextractor.mpf.MpEntry
|
||||||
|
import deckers.thibault.aves.metadata.metadataextractor.mpf.MpEntryDirectory
|
||||||
import deckers.thibault.aves.model.FieldMap
|
import deckers.thibault.aves.model.FieldMap
|
||||||
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.StorageUtils
|
||||||
import deckers.thibault.aves.utils.indexOfBytes
|
import deckers.thibault.aves.utils.indexOfBytes
|
||||||
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
|
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
|
||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
|
@ -48,13 +53,13 @@ object MultiPage {
|
||||||
val tracks = ArrayList<FieldMap>()
|
val tracks = ArrayList<FieldMap>()
|
||||||
val extractor = MediaExtractor()
|
val extractor = MediaExtractor()
|
||||||
extractor.setDataSource(context, uri, null)
|
extractor.setDataSource(context, uri, null)
|
||||||
for (i in 0 until extractor.trackCount) {
|
for (pageIndex in 0 until extractor.trackCount) {
|
||||||
try {
|
try {
|
||||||
val format = extractor.getTrackFormat(i)
|
val format = extractor.getTrackFormat(pageIndex)
|
||||||
format.getString(MediaFormat.KEY_MIME)?.let { mime ->
|
format.getString(MediaFormat.KEY_MIME)?.let { mime ->
|
||||||
val trackMime = if (mime == MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC) MimeTypes.HEIC else mime
|
val trackMime = if (mime == MediaFormat.MIMETYPE_IMAGE_ANDROID_HEIC) MimeTypes.HEIC else mime
|
||||||
val track: FieldMap = hashMapOf(
|
val track: FieldMap = hashMapOf(
|
||||||
KEY_PAGE to i,
|
KEY_PAGE to pageIndex,
|
||||||
KEY_MIME_TYPE to trackMime,
|
KEY_MIME_TYPE to trackMime,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -73,13 +78,115 @@ object MultiPage {
|
||||||
tracks.add(track)
|
tracks.add(track)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w(LOG_TAG, "failed to get HEIC track information for uri=$uri, track num=$i", e)
|
Log.w(LOG_TAG, "failed to get HEIC track information for uri=$uri, pageIndex=$pageIndex", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extractor.release()
|
extractor.release()
|
||||||
return tracks
|
return tracks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// starts after `[APP2 marker (1 byte)] [segment size (2 bytes)] [MPF marker (4 bytes)]`
|
||||||
|
fun getJpegMpfBaseOffset(context: Context, uri: Uri): Int? {
|
||||||
|
val app2Marker = JpegSegmentType.APP2.byteValue
|
||||||
|
val mpfMarker = "MPF".toByteArray() + 0x00
|
||||||
|
|
||||||
|
try {
|
||||||
|
Metadata.openSafeInputStream(context, uri, MimeTypes.JPEG, null)?.use { input ->
|
||||||
|
var offset = 0
|
||||||
|
while (true) {
|
||||||
|
do {
|
||||||
|
val b = input.read().toByte()
|
||||||
|
offset++
|
||||||
|
} while (b != app2Marker)
|
||||||
|
// skip 2 bytes for segment size
|
||||||
|
input.skip(2)
|
||||||
|
offset += 2
|
||||||
|
val marker = ByteArray(4)
|
||||||
|
input.read(marker, 0, marker.size)
|
||||||
|
offset += 4
|
||||||
|
if (marker.contentEquals(mpfMarker)) {
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(LOG_TAG, "failed to get MPF base offset from uri=$uri", e)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getJpegMpfEntries(context: Context, uri: Uri): List<MpEntry>? {
|
||||||
|
try {
|
||||||
|
Metadata.openSafeInputStream(context, uri, MimeTypes.JPEG, null)?.use { input ->
|
||||||
|
val metadata = Helper.safeRead(input)
|
||||||
|
return metadata.getDirectoriesOfType(MpEntryDirectory::class.java).map { it.entry }
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(LOG_TAG, "failed to find MPF entries", e)
|
||||||
|
} catch (e: NoClassDefFoundError) {
|
||||||
|
Log.w(LOG_TAG, "failed to find MPF entries", e)
|
||||||
|
} catch (e: AssertionError) {
|
||||||
|
Log.w(LOG_TAG, "failed to find MPF entries", e)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getJpegMpfPages(context: Context, uri: Uri): ArrayList<FieldMap> {
|
||||||
|
val pages = ArrayList<FieldMap>()
|
||||||
|
val baseOffset = getJpegMpfBaseOffset(context, uri)
|
||||||
|
val mpEntries = getJpegMpfEntries(context, uri)
|
||||||
|
if (mpEntries != null && baseOffset != null) {
|
||||||
|
for ((pageIndex, mpEntry) in mpEntries.withIndex()) {
|
||||||
|
MpEntry.getMimeType(mpEntry.format)?.let { embedMimeType ->
|
||||||
|
val page = hashMapOf<String, Any?>(
|
||||||
|
KEY_PAGE to pageIndex,
|
||||||
|
KEY_MIME_TYPE to embedMimeType,
|
||||||
|
KEY_IS_DEFAULT to (pageIndex == 0),
|
||||||
|
// TODO TLAD [MPF] page[KEY_ROTATION_DEGREES] = same as primary
|
||||||
|
KEY_ROTATION_DEGREES to 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
var dataOffset = mpEntry.dataOffset
|
||||||
|
if (dataOffset > 0) {
|
||||||
|
dataOffset += baseOffset
|
||||||
|
}
|
||||||
|
StorageUtils.openInputStream(context, uri)?.let { input ->
|
||||||
|
input.skip(dataOffset)
|
||||||
|
val options = BitmapFactory.Options().apply {
|
||||||
|
inJustDecodeBounds = true
|
||||||
|
}
|
||||||
|
BitmapFactory.decodeStream(input, null, options)
|
||||||
|
options.outWidth.takeIf { it >= 0 }?.let { page[KEY_WIDTH] = it }
|
||||||
|
options.outHeight.takeIf { it >= 0 }?.let { page[KEY_HEIGHT] = it }
|
||||||
|
|
||||||
|
pages.add(page)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getJpegMpfBitmap(context: Context, uri: Uri, pageIndex: Int): Bitmap? {
|
||||||
|
val mpEntries = getJpegMpfEntries(context, uri)
|
||||||
|
if (mpEntries != null && pageIndex < mpEntries.size) {
|
||||||
|
val mpEntry = mpEntries[pageIndex]
|
||||||
|
var dataOffset = mpEntry.dataOffset
|
||||||
|
if (dataOffset > 0) {
|
||||||
|
val baseOffset = getJpegMpfBaseOffset(context, uri)
|
||||||
|
if (baseOffset != null) {
|
||||||
|
dataOffset += baseOffset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StorageUtils.openInputStream(context, uri)?.let { input ->
|
||||||
|
input.skip(dataOffset)
|
||||||
|
return BitmapFactory.decodeStream(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
fun getMotionPhotoPages(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): ArrayList<FieldMap> {
|
fun getMotionPhotoPages(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): ArrayList<FieldMap> {
|
||||||
fun MediaFormat.getSafeInt(key: String, save: (value: Int) -> Unit) {
|
fun MediaFormat.getSafeInt(key: String, save: (value: Int) -> Unit) {
|
||||||
if (this.containsKey(key)) save(this.getInteger(key))
|
if (this.containsKey(key)) save(this.getInteger(key))
|
||||||
|
@ -89,7 +196,7 @@ object MultiPage {
|
||||||
if (this.containsKey(key)) save(this.getLong(key))
|
if (this.containsKey(key)) save(this.getLong(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
val tracks = ArrayList<FieldMap>()
|
val pages = ArrayList<FieldMap>()
|
||||||
val extractor = MediaExtractor()
|
val extractor = MediaExtractor()
|
||||||
var pfd: ParcelFileDescriptor? = null
|
var pfd: ParcelFileDescriptor? = null
|
||||||
try {
|
try {
|
||||||
|
@ -99,10 +206,10 @@ object MultiPage {
|
||||||
pfd?.fileDescriptor?.let { fd ->
|
pfd?.fileDescriptor?.let { fd ->
|
||||||
extractor.setDataSource(fd, videoStartOffset, videoSizeBytes)
|
extractor.setDataSource(fd, videoStartOffset, videoSizeBytes)
|
||||||
// set the original image as the first and default track
|
// set the original image as the first and default track
|
||||||
var trackCount = 0
|
var pageIndex = 0
|
||||||
tracks.add(
|
pages.add(
|
||||||
hashMapOf(
|
hashMapOf(
|
||||||
KEY_PAGE to trackCount++,
|
KEY_PAGE to pageIndex++,
|
||||||
KEY_MIME_TYPE to mimeType,
|
KEY_MIME_TYPE to mimeType,
|
||||||
KEY_IS_DEFAULT to true,
|
KEY_IS_DEFAULT to true,
|
||||||
)
|
)
|
||||||
|
@ -115,18 +222,18 @@ object MultiPage {
|
||||||
val format = extractor.getTrackFormat(trackIndex)
|
val format = extractor.getTrackFormat(trackIndex)
|
||||||
format.getString(MediaFormat.KEY_MIME)?.let { mime ->
|
format.getString(MediaFormat.KEY_MIME)?.let { mime ->
|
||||||
if (MimeTypes.isVideo(mime)) {
|
if (MimeTypes.isVideo(mime)) {
|
||||||
val track: FieldMap = hashMapOf(
|
val page: FieldMap = hashMapOf(
|
||||||
KEY_PAGE to trackCount++,
|
KEY_PAGE to pageIndex++,
|
||||||
KEY_MIME_TYPE to MimeTypes.MP4,
|
KEY_MIME_TYPE to MimeTypes.MP4,
|
||||||
KEY_IS_DEFAULT to false,
|
KEY_IS_DEFAULT to false,
|
||||||
)
|
)
|
||||||
format.getSafeInt(MediaFormat.KEY_WIDTH) { track[KEY_WIDTH] = it }
|
format.getSafeInt(MediaFormat.KEY_WIDTH) { page[KEY_WIDTH] = it }
|
||||||
format.getSafeInt(MediaFormat.KEY_HEIGHT) { track[KEY_HEIGHT] = it }
|
format.getSafeInt(MediaFormat.KEY_HEIGHT) { page[KEY_HEIGHT] = it }
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
format.getSafeInt(MediaFormat.KEY_ROTATION) { track[KEY_ROTATION_DEGREES] = it }
|
format.getSafeInt(MediaFormat.KEY_ROTATION) { page[KEY_ROTATION_DEGREES] = it }
|
||||||
}
|
}
|
||||||
format.getSafeLong(MediaFormat.KEY_DURATION) { track[KEY_DURATION] = it / 1000 }
|
format.getSafeLong(MediaFormat.KEY_DURATION) { page[KEY_DURATION] = it / 1000 }
|
||||||
tracks.add(track)
|
pages.add(page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -141,7 +248,7 @@ object MultiPage {
|
||||||
extractor.release()
|
extractor.release()
|
||||||
pfd?.close()
|
pfd?.close()
|
||||||
}
|
}
|
||||||
return tracks
|
return pages
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMotionPhotoOffset(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): Long? {
|
fun getMotionPhotoOffset(context: Context, uri: Uri, mimeType: String, sizeBytes: Long): Long? {
|
||||||
|
@ -204,40 +311,10 @@ object MultiPage {
|
||||||
return offsetFromEnd
|
return offsetFromEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
// starts after `[APP2 marker (1 byte)] [segment size (2 bytes)] [MPF marker (4 bytes)]`
|
|
||||||
fun getJpegMultiPictureFormatBaseOffset(context: Context, uri: Uri, sizeBytes: Long): Int? {
|
|
||||||
val app2Marker = JpegSegmentType.APP2.byteValue
|
|
||||||
val mpfMarker = "MPF".toByteArray() + 0x00
|
|
||||||
|
|
||||||
try {
|
|
||||||
Metadata.openSafeInputStream(context, uri, MimeTypes.JPEG, sizeBytes)?.use { input ->
|
|
||||||
var offset = 0
|
|
||||||
while (true) {
|
|
||||||
do {
|
|
||||||
val b = input.read().toByte()
|
|
||||||
offset++
|
|
||||||
} while (b != app2Marker)
|
|
||||||
// skip 2 bytes for segment size
|
|
||||||
input.skip(2)
|
|
||||||
offset += 2
|
|
||||||
val marker = ByteArray(4)
|
|
||||||
input.read(marker, 0, marker.size)
|
|
||||||
offset += 4
|
|
||||||
if (marker.contentEquals(mpfMarker)) {
|
|
||||||
return offset
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(LOG_TAG, "failed to get MPF base offset from uri=$uri", e)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTiffPages(context: Context, uri: Uri): ArrayList<FieldMap> {
|
fun getTiffPages(context: Context, uri: Uri): ArrayList<FieldMap> {
|
||||||
fun toMap(page: Int, options: TiffBitmapFactory.Options): FieldMap {
|
fun toMap(pageIndex: Int, options: TiffBitmapFactory.Options): FieldMap {
|
||||||
return hashMapOf(
|
return hashMapOf(
|
||||||
KEY_PAGE to page,
|
KEY_PAGE to pageIndex,
|
||||||
KEY_MIME_TYPE to MimeTypes.TIFF,
|
KEY_MIME_TYPE to MimeTypes.TIFF,
|
||||||
KEY_WIDTH to options.outWidth,
|
KEY_WIDTH to options.outWidth,
|
||||||
KEY_HEIGHT to options.outHeight,
|
KEY_HEIGHT to options.outHeight,
|
||||||
|
@ -248,8 +325,8 @@ object MultiPage {
|
||||||
getTiffPageInfo(context, uri, 0)?.let { first ->
|
getTiffPageInfo(context, uri, 0)?.let { first ->
|
||||||
pages.add(toMap(0, first))
|
pages.add(toMap(0, first))
|
||||||
val pageCount = first.outDirectoryCount
|
val pageCount = first.outDirectoryCount
|
||||||
for (i in 1 until pageCount) {
|
for (pageIndex in 1 until pageCount) {
|
||||||
getTiffPageInfo(context, uri, i)?.let { pages.add(toMap(i, it)) }
|
getTiffPageInfo(context, uri, pageIndex)?.let { pages.add(toMap(pageIndex, it)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pages
|
return pages
|
||||||
|
|
|
@ -19,25 +19,36 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import com.bumptech.glide.request.FutureTarget
|
import com.bumptech.glide.request.FutureTarget
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
import com.commonsware.cwac.document.DocumentFileCompat
|
import com.commonsware.cwac.document.DocumentFileCompat
|
||||||
import deckers.thibault.aves.decoder.MultiTrackImage
|
import deckers.thibault.aves.decoder.MultiPageImage
|
||||||
import deckers.thibault.aves.decoder.SvgImage
|
import deckers.thibault.aves.decoder.SvgImage
|
||||||
import deckers.thibault.aves.decoder.TiffImage
|
import deckers.thibault.aves.decoder.TiffImage
|
||||||
import deckers.thibault.aves.metadata.*
|
import deckers.thibault.aves.metadata.ExifInterfaceHelper
|
||||||
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDateMillis
|
import deckers.thibault.aves.metadata.ExifInterfaceHelper.getSafeDateMillis
|
||||||
import deckers.thibault.aves.metadata.Metadata.TYPE_EXIF
|
import deckers.thibault.aves.metadata.Metadata.TYPE_EXIF
|
||||||
import deckers.thibault.aves.metadata.Metadata.TYPE_IPTC
|
import deckers.thibault.aves.metadata.Metadata.TYPE_IPTC
|
||||||
import deckers.thibault.aves.metadata.Metadata.TYPE_MP4
|
import deckers.thibault.aves.metadata.Metadata.TYPE_MP4
|
||||||
import deckers.thibault.aves.metadata.Metadata.TYPE_XMP
|
import deckers.thibault.aves.metadata.Metadata.TYPE_XMP
|
||||||
|
import deckers.thibault.aves.metadata.Mp4ParserHelper
|
||||||
import deckers.thibault.aves.metadata.Mp4ParserHelper.updateLocation
|
import deckers.thibault.aves.metadata.Mp4ParserHelper.updateLocation
|
||||||
import deckers.thibault.aves.metadata.Mp4ParserHelper.updateRotation
|
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.MultiPage
|
||||||
|
import deckers.thibault.aves.metadata.PixyMetaHelper
|
||||||
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.XMP
|
||||||
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
import deckers.thibault.aves.metadata.metadataextractor.Helper
|
||||||
import deckers.thibault.aves.model.*
|
import deckers.thibault.aves.model.AvesEntry
|
||||||
import deckers.thibault.aves.utils.*
|
import deckers.thibault.aves.model.ExifOrientationOp
|
||||||
|
import deckers.thibault.aves.model.FieldMap
|
||||||
|
import deckers.thibault.aves.model.NameConflictStrategy
|
||||||
|
import deckers.thibault.aves.model.SourceEntry
|
||||||
|
import deckers.thibault.aves.utils.BitmapUtils
|
||||||
|
import deckers.thibault.aves.utils.BmpWriter
|
||||||
import deckers.thibault.aves.utils.FileUtils.transferFrom
|
import deckers.thibault.aves.utils.FileUtils.transferFrom
|
||||||
import deckers.thibault.aves.utils.FileUtils.transferTo
|
import deckers.thibault.aves.utils.FileUtils.transferTo
|
||||||
|
import deckers.thibault.aves.utils.LogUtils
|
||||||
|
import deckers.thibault.aves.utils.MimeTypes
|
||||||
import deckers.thibault.aves.utils.MimeTypes.canEditExif
|
import deckers.thibault.aves.utils.MimeTypes.canEditExif
|
||||||
import deckers.thibault.aves.utils.MimeTypes.canEditIptc
|
import deckers.thibault.aves.utils.MimeTypes.canEditIptc
|
||||||
import deckers.thibault.aves.utils.MimeTypes.canEditXmp
|
import deckers.thibault.aves.utils.MimeTypes.canEditXmp
|
||||||
|
@ -46,13 +57,19 @@ import deckers.thibault.aves.utils.MimeTypes.canReadWithPixyMeta
|
||||||
import deckers.thibault.aves.utils.MimeTypes.canRemoveMetadata
|
import deckers.thibault.aves.utils.MimeTypes.canRemoveMetadata
|
||||||
import deckers.thibault.aves.utils.MimeTypes.extensionFor
|
import deckers.thibault.aves.utils.MimeTypes.extensionFor
|
||||||
import deckers.thibault.aves.utils.MimeTypes.isVideo
|
import deckers.thibault.aves.utils.MimeTypes.isVideo
|
||||||
|
import deckers.thibault.aves.utils.StorageUtils
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import pixy.meta.meta.Metadata
|
import pixy.meta.meta.Metadata
|
||||||
import pixy.meta.meta.MetadataType
|
import pixy.meta.meta.MetadataType
|
||||||
import java.io.*
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.OutputStream
|
||||||
import java.nio.channels.Channels
|
import java.nio.channels.Channels
|
||||||
import java.util.*
|
import java.util.Date
|
||||||
|
import java.util.TimeZone
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
abstract class ImageProvider {
|
abstract class ImageProvider {
|
||||||
|
@ -291,8 +308,8 @@ abstract class ImageProvider {
|
||||||
targetHeightPx = sourceEntry.height * targetHeightPx / 100
|
targetHeightPx = sourceEntry.height * targetHeightPx / 100
|
||||||
}
|
}
|
||||||
|
|
||||||
val model: Any = if (MimeTypes.isHeic(sourceMimeType) && pageId != null) {
|
val model: Any = if (pageId != null && MultiPageImage.isSupported(sourceMimeType)) {
|
||||||
MultiTrackImage(activity, sourceUri, pageId)
|
MultiPageImage(activity, sourceUri, sourceMimeType, pageId)
|
||||||
} else if (sourceMimeType == MimeTypes.TIFF) {
|
} else if (sourceMimeType == MimeTypes.TIFF) {
|
||||||
TiffImage(activity, sourceUri, pageId)
|
TiffImage(activity, sourceUri, pageId)
|
||||||
} else if (sourceMimeType == MimeTypes.SVG) {
|
} else if (sourceMimeType == MimeTypes.SVG) {
|
||||||
|
|
|
@ -38,7 +38,7 @@ class PlatformAppService implements AppService {
|
||||||
'com.sony.playmemories.mobile': {'Imaging Edge Mobile'},
|
'com.sony.playmemories.mobile': {'Imaging Edge Mobile'},
|
||||||
'nekox.messenger': {'NekoX'},
|
'nekox.messenger': {'NekoX'},
|
||||||
'org.telegram.messenger': {'Telegram Images', 'Telegram Video'},
|
'org.telegram.messenger': {'Telegram Images', 'Telegram Video'},
|
||||||
'com.whatsapp': {'Whatsapp', 'WhatsApp Animated Gifs', 'WhatsApp Images', 'WhatsApp Video'}
|
'com.whatsapp': {'Whatsapp', 'WhatsApp Animated Gifs', 'WhatsApp Documents', 'WhatsApp Images', 'WhatsApp Video'}
|
||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -12,7 +12,7 @@ abstract class EmbeddedDataService {
|
||||||
|
|
||||||
Future<Map> extractMotionPhotoVideo(AvesEntry entry);
|
Future<Map> extractMotionPhotoVideo(AvesEntry entry);
|
||||||
|
|
||||||
Future<Map> extractJpegMultiPictureFormat(AvesEntry entry, int index);
|
Future<Map> extractJpegMpfItem(AvesEntry entry, int index);
|
||||||
|
|
||||||
Future<Map> extractVideoEmbeddedPicture(AvesEntry entry);
|
Future<Map> extractVideoEmbeddedPicture(AvesEntry entry);
|
||||||
|
|
||||||
|
@ -87,9 +87,9 @@ class PlatformEmbeddedDataService implements EmbeddedDataService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map> extractJpegMultiPictureFormat(AvesEntry entry, int id) async {
|
Future<Map> extractJpegMpfItem(AvesEntry entry, int id) async {
|
||||||
try {
|
try {
|
||||||
final result = await _platform.invokeMethod('extractJpegMultiPictureFormat', <String, dynamic>{
|
final result = await _platform.invokeMethod('extractJpegMpfItem', <String, dynamic>{
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'uri': entry.uri,
|
'uri': entry.uri,
|
||||||
'sizeBytes': entry.sizeBytes,
|
'sizeBytes': entry.sizeBytes,
|
||||||
|
|
|
@ -45,7 +45,7 @@ class EmbeddedDataOpener extends StatelessWidget with FeedbackMixin {
|
||||||
case EmbeddedDataSource.motionPhotoVideo:
|
case EmbeddedDataSource.motionPhotoVideo:
|
||||||
fields = await embeddedDataService.extractMotionPhotoVideo(entry);
|
fields = await embeddedDataService.extractMotionPhotoVideo(entry);
|
||||||
case EmbeddedDataSource.mpf:
|
case EmbeddedDataSource.mpf:
|
||||||
fields = await embeddedDataService.extractJpegMultiPictureFormat(entry, notification.mpfId!);
|
fields = await embeddedDataService.extractJpegMpfItem(entry, notification.mpfId!);
|
||||||
case EmbeddedDataSource.videoCover:
|
case EmbeddedDataSource.videoCover:
|
||||||
fields = await embeddedDataService.extractVideoEmbeddedPicture(entry);
|
fields = await embeddedDataService.extractVideoEmbeddedPicture(entry);
|
||||||
case EmbeddedDataSource.xmp:
|
case EmbeddedDataSource.xmp:
|
||||||
|
|
Loading…
Reference in a new issue