temp: do not tile multi-track images
This commit is contained in:
parent
bd8bc19fa1
commit
a37c10a969
9 changed files with 138 additions and 132 deletions
|
@ -98,7 +98,7 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
|
||||
implementation 'androidx.core:core-ktx:1.5.0-alpha05' // v1.5.0-alpha02+ for ShortcutManagerCompat.setDynamicShortcuts
|
||||
implementation 'androidx.core:core-ktx:1.5.0-beta01' // v1.5.0-alpha02+ for ShortcutManagerCompat.setDynamicShortcuts
|
||||
implementation 'androidx.exifinterface:exifinterface:1.3.2'
|
||||
implementation 'com.commonsware.cwac:document:0.4.1'
|
||||
implementation 'com.drewnoakes:metadata-extractor:2.15.0'
|
||||
|
|
|
@ -13,7 +13,7 @@ import com.bumptech.glide.load.DecodeFormat
|
|||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import deckers.thibault.aves.decoder.MultiTrackThumbnail
|
||||
import deckers.thibault.aves.decoder.MultiTrackImage
|
||||
import deckers.thibault.aves.decoder.TiffThumbnail
|
||||
import deckers.thibault.aves.decoder.VideoThumbnail
|
||||
import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation
|
||||
|
@ -128,7 +128,7 @@ class ThumbnailFetcher internal constructor(
|
|||
val model: Any = if (tiffFetch) {
|
||||
TiffThumbnail(context, uri, page ?: 0)
|
||||
} else if (multiTrackFetch) {
|
||||
MultiTrackThumbnail(context, uri, page ?: 0)
|
||||
MultiTrackImage(context, uri, page ?: 0)
|
||||
} else uri
|
||||
Glide.with(context)
|
||||
.asBitmap()
|
||||
|
|
|
@ -9,7 +9,7 @@ import com.bumptech.glide.Glide
|
|||
import com.bumptech.glide.load.DecodeFormat
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.request.RequestOptions
|
||||
import deckers.thibault.aves.decoder.MultiTrackThumbnail
|
||||
import deckers.thibault.aves.decoder.MultiTrackImage
|
||||
import deckers.thibault.aves.decoder.VideoThumbnail
|
||||
import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation
|
||||
import deckers.thibault.aves.utils.BitmapUtils.getBytes
|
||||
|
@ -118,7 +118,7 @@ class ImageByteStreamHandler(private val activity: Activity, private val argumen
|
|||
|
||||
private fun streamImageByGlide(uri: Uri, page: Int?, mimeType: String, rotationDegrees: Int, isFlipped: Boolean) {
|
||||
val model: Any = if (isHeifLike(mimeType) && page != null) {
|
||||
MultiTrackThumbnail(activity, uri, page)
|
||||
MultiTrackImage(activity, uri, page)
|
||||
} else {
|
||||
uri
|
||||
}
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
package deckers.thibault.aves.decoder
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.Priority
|
||||
import com.bumptech.glide.Registry
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.data.DataFetcher
|
||||
import com.bumptech.glide.load.data.DataFetcher.DataCallback
|
||||
import com.bumptech.glide.load.model.ModelLoader
|
||||
import com.bumptech.glide.load.model.ModelLoaderFactory
|
||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
||||
import com.bumptech.glide.module.LibraryGlideModule
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import deckers.thibault.aves.metadata.MultiTrackMedia
|
||||
import deckers.thibault.aves.utils.BitmapUtils.getBytes
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
@GlideModule
|
||||
class MultiTrackImageGlideModule : LibraryGlideModule() {
|
||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||
registry.append(MultiTrackImage::class.java, InputStream::class.java, MultiTrackThumbnailLoader.Factory())
|
||||
}
|
||||
}
|
||||
|
||||
class MultiTrackImage(val context: Context, val uri: Uri, val trackIndex: Int)
|
||||
|
||||
internal class MultiTrackThumbnailLoader : ModelLoader<MultiTrackImage, InputStream> {
|
||||
override fun buildLoadData(model: MultiTrackImage, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream> {
|
||||
return ModelLoader.LoadData(ObjectKey(model.uri), MultiTrackImageFetcher(model, width, height))
|
||||
}
|
||||
|
||||
override fun handles(model: MultiTrackImage): Boolean = true
|
||||
|
||||
internal class Factory : ModelLoaderFactory<MultiTrackImage, InputStream> {
|
||||
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<MultiTrackImage, InputStream> = MultiTrackThumbnailLoader()
|
||||
|
||||
override fun teardown() {}
|
||||
}
|
||||
}
|
||||
|
||||
internal class MultiTrackImageFetcher(val model: MultiTrackImage, val width: Int, val height: Int) : DataFetcher<InputStream> {
|
||||
override fun loadData(priority: Priority, callback: DataCallback<in InputStream>) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
callback.onLoadFailed(Exception("unsupported Android version"))
|
||||
return
|
||||
}
|
||||
|
||||
val context = model.context
|
||||
val uri = model.uri
|
||||
val trackIndex = model.trackIndex
|
||||
|
||||
val bitmap = MultiTrackMedia.getImage(context, uri, trackIndex)
|
||||
if (bitmap == null) {
|
||||
callback.onLoadFailed(Exception("null bitmap"))
|
||||
} else {
|
||||
callback.onDataReady(bitmap.getBytes()?.inputStream())
|
||||
}
|
||||
}
|
||||
|
||||
// already cleaned up in loadData and ByteArrayInputStream will be GC'd
|
||||
override fun cleanup() {}
|
||||
|
||||
// cannot cancel
|
||||
override fun cancel() {}
|
||||
|
||||
override fun getDataClass(): Class<InputStream> = InputStream::class.java
|
||||
|
||||
override fun getDataSource(): DataSource = DataSource.LOCAL
|
||||
}
|
|
@ -1,124 +0,0 @@
|
|||
package deckers.thibault.aves.decoder
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.media.MediaExtractor
|
||||
import android.media.MediaFormat
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.Priority
|
||||
import com.bumptech.glide.Registry
|
||||
import com.bumptech.glide.annotation.GlideModule
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.Options
|
||||
import com.bumptech.glide.load.data.DataFetcher
|
||||
import com.bumptech.glide.load.data.DataFetcher.DataCallback
|
||||
import com.bumptech.glide.load.model.ModelLoader
|
||||
import com.bumptech.glide.load.model.ModelLoaderFactory
|
||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
||||
import com.bumptech.glide.module.LibraryGlideModule
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import deckers.thibault.aves.utils.BitmapUtils.getBytes
|
||||
import deckers.thibault.aves.utils.LogUtils
|
||||
import deckers.thibault.aves.utils.MimeTypes
|
||||
import deckers.thibault.aves.utils.StorageUtils
|
||||
import java.io.InputStream
|
||||
|
||||
|
||||
@GlideModule
|
||||
class MultiTrackThumbnailGlideModule : LibraryGlideModule() {
|
||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
||||
registry.append(MultiTrackThumbnail::class.java, InputStream::class.java, MultiTrackThumbnailLoader.Factory())
|
||||
}
|
||||
}
|
||||
|
||||
class MultiTrackThumbnail(val context: Context, val uri: Uri, val trackIndex: Int)
|
||||
|
||||
internal class MultiTrackThumbnailLoader : ModelLoader<MultiTrackThumbnail, InputStream> {
|
||||
override fun buildLoadData(model: MultiTrackThumbnail, width: Int, height: Int, options: Options): ModelLoader.LoadData<InputStream> {
|
||||
return ModelLoader.LoadData(ObjectKey(model.uri), MultiTrackThumbnailFetcher(model, width, height))
|
||||
}
|
||||
|
||||
override fun handles(MultiTrackThumbnail: MultiTrackThumbnail): Boolean = true
|
||||
|
||||
internal class Factory : ModelLoaderFactory<MultiTrackThumbnail, InputStream> {
|
||||
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<MultiTrackThumbnail, InputStream> = MultiTrackThumbnailLoader()
|
||||
|
||||
override fun teardown() {}
|
||||
}
|
||||
}
|
||||
|
||||
internal class MultiTrackThumbnailFetcher(val model: MultiTrackThumbnail, val width: Int, val height: Int) : DataFetcher<InputStream> {
|
||||
override fun loadData(priority: Priority, callback: DataCallback<in InputStream>) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
callback.onLoadFailed(Exception("unsupported Android version"))
|
||||
return
|
||||
}
|
||||
|
||||
val context = model.context
|
||||
val uri = model.uri
|
||||
val trackIndex = model.trackIndex
|
||||
|
||||
val imageIndex = trackIndexToImageIndex(context, uri, trackIndex)
|
||||
if (imageIndex == null) {
|
||||
callback.onLoadFailed(Exception("no image index"))
|
||||
return
|
||||
}
|
||||
|
||||
val bitmap: Bitmap?
|
||||
|
||||
val retriever = StorageUtils.openMetadataRetriever(context, uri) ?: return
|
||||
try {
|
||||
bitmap = retriever.getImageAtIndex(imageIndex)
|
||||
} catch (e: Exception) {
|
||||
callback.onLoadFailed(e)
|
||||
return
|
||||
} finally {
|
||||
// cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs
|
||||
retriever.release()
|
||||
}
|
||||
|
||||
if (bitmap == null) {
|
||||
callback.onLoadFailed(Exception("null bitmap"))
|
||||
} else {
|
||||
callback.onDataReady(bitmap.getBytes()?.inputStream())
|
||||
}
|
||||
}
|
||||
|
||||
private fun trackIndexToImageIndex(context: Context, uri: Uri, trackIndex: Int): Int? {
|
||||
val extractor = MediaExtractor()
|
||||
try {
|
||||
extractor.setDataSource(context, uri, null)
|
||||
val trackCount = extractor.trackCount
|
||||
if (trackIndex < trackCount) {
|
||||
var imageIndex = 0
|
||||
for (i in 0 until trackIndex) {
|
||||
val mimeType = extractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME)
|
||||
if (MimeTypes.isImage(mimeType)) imageIndex++
|
||||
}
|
||||
return imageIndex
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to get image index for uri=$uri, trackIndex=$trackIndex", e)
|
||||
} finally {
|
||||
extractor.release()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// already cleaned up in loadData and ByteArrayInputStream will be GC'd
|
||||
override fun cleanup() {}
|
||||
|
||||
// cannot cancel
|
||||
override fun cancel() {}
|
||||
|
||||
override fun getDataClass(): Class<InputStream> = InputStream::class.java
|
||||
|
||||
override fun getDataSource(): DataSource = DataSource.LOCAL
|
||||
|
||||
companion object {
|
||||
private val LOG_TAG = LogUtils.createTag(MultiTrackThumbnailFetcher::class.java)
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ internal class TiffThumbnailLoader : ModelLoader<TiffThumbnail, InputStream> {
|
|||
return ModelLoader.LoadData(ObjectKey(model.uri), TiffThumbnailFetcher(model, width, height))
|
||||
}
|
||||
|
||||
override fun handles(tiffThumbnail: TiffThumbnail): Boolean = true
|
||||
override fun handles(model: TiffThumbnail): Boolean = true
|
||||
|
||||
internal class Factory : ModelLoaderFactory<TiffThumbnail, InputStream> {
|
||||
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<TiffThumbnail, InputStream> = TiffThumbnailLoader()
|
||||
|
|
|
@ -34,7 +34,7 @@ internal class VideoThumbnailLoader : ModelLoader<VideoThumbnail, InputStream> {
|
|||
return ModelLoader.LoadData(ObjectKey(model.uri), VideoThumbnailFetcher(model))
|
||||
}
|
||||
|
||||
override fun handles(videoThumbnail: VideoThumbnail): Boolean = true
|
||||
override fun handles(model: VideoThumbnail): Boolean = true
|
||||
|
||||
internal class Factory : ModelLoaderFactory<VideoThumbnail, InputStream> {
|
||||
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<VideoThumbnail, InputStream> = VideoThumbnailLoader()
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package deckers.thibault.aves.metadata
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.media.MediaExtractor
|
||||
import android.media.MediaFormat
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import deckers.thibault.aves.utils.LogUtils
|
||||
import deckers.thibault.aves.utils.MimeTypes
|
||||
import deckers.thibault.aves.utils.StorageUtils
|
||||
|
||||
object MultiTrackMedia {
|
||||
private val LOG_TAG = LogUtils.createTag(MultiTrackMedia::class.java)
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
fun getImage(context: Context, uri: Uri, trackIndex: Int): Bitmap? {
|
||||
val imageIndex = trackIndexToImageIndex(context, uri, trackIndex) ?: return null
|
||||
|
||||
val retriever = StorageUtils.openMetadataRetriever(context, uri) ?: return null
|
||||
try {
|
||||
return retriever.getImageAtIndex(imageIndex)
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to extract image from uri=$uri trackIndex=$trackIndex imageIndex=$imageIndex", e)
|
||||
} finally {
|
||||
// cannot rely on `MediaMetadataRetriever` being `AutoCloseable` on older APIs
|
||||
retriever.release()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun trackIndexToImageIndex(context: Context, uri: Uri, trackIndex: Int): Int? {
|
||||
val extractor = MediaExtractor()
|
||||
try {
|
||||
extractor.setDataSource(context, uri, null)
|
||||
val trackCount = extractor.trackCount
|
||||
if (trackIndex < trackCount) {
|
||||
var imageIndex = 0
|
||||
for (i in 0 until trackIndex) {
|
||||
val mimeType = extractor.getTrackFormat(i).getString(MediaFormat.KEY_MIME)
|
||||
if (MimeTypes.isImage(mimeType)) imageIndex++
|
||||
}
|
||||
return imageIndex
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to get image index for uri=$uri, trackIndex=$trackIndex", e)
|
||||
} finally {
|
||||
extractor.release()
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
|
@ -224,7 +224,8 @@ class ImageEntry {
|
|||
MimeTypes.rw2,
|
||||
MimeTypes.srw,
|
||||
].contains(mimeType) &&
|
||||
!isAnimated;
|
||||
!isAnimated &&
|
||||
page == null;
|
||||
|
||||
bool get canTile => _supportedByBitmapRegionDecoder || mimeType == MimeTypes.tiff;
|
||||
|
||||
|
|
Loading…
Reference in a new issue