Merge pull request #1925 from vector-im/feature/bma/testAndroidMediaPreProcessor
Feature/bma/test android media pre processor
This commit is contained in:
commit
df897e4fb8
14 changed files with 385 additions and 6 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
**/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text
|
**/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
**/docs/images-lfs/*.png filter=lfs diff=lfs merge=lfs -text
|
**/docs/images-lfs/*.png filter=lfs diff=lfs merge=lfs -text
|
||||||
|
libraries/mediaupload/impl/src/test/assets/* filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
import kotlin.coroutines.coroutineContext
|
import kotlin.coroutines.coroutineContext
|
||||||
|
|
||||||
class AttachmentsPreviewPresenter @AssistedInject constructor(
|
class AttachmentsPreviewPresenter @AssistedInject constructor(
|
||||||
|
|
@ -114,6 +115,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
|
||||||
sendActionState.value = SendActionState.Done
|
sendActionState.value = SendActionState.Done
|
||||||
},
|
},
|
||||||
onFailure = { error ->
|
onFailure = { error ->
|
||||||
|
Timber.e(error, "Failed to send attachment")
|
||||||
if (error is CancellationException) {
|
if (error is CancellationException) {
|
||||||
throw error
|
throw error
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,7 @@ import kotlinx.coroutines.flow.filter
|
||||||
import kotlinx.coroutines.flow.merge
|
import kotlinx.coroutines.flow.merge
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.coroutines.coroutineContext
|
import kotlin.coroutines.coroutineContext
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
@ -432,6 +433,7 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
attachmentState.value = AttachmentsState.None
|
attachmentState.value = AttachmentsState.None
|
||||||
}
|
}
|
||||||
.onFailure { cause ->
|
.onFailure { cause ->
|
||||||
|
Timber.e(cause, "Failed to send attachment")
|
||||||
attachmentState.value = AttachmentsState.None
|
attachmentState.value = AttachmentsState.None
|
||||||
if (cause is CancellationException) {
|
if (cause is CancellationException) {
|
||||||
throw cause
|
throw cause
|
||||||
|
|
|
||||||
|
|
@ -580,7 +580,7 @@ class RustMatrixRoom(
|
||||||
onNewSyncedEvent = { _syncUpdateFlow.value = systemClock.epochMillis() }
|
onNewSyncedEvent = { _syncUpdateFlow.value = systemClock.epochMillis() }
|
||||||
)
|
)
|
||||||
|
|
||||||
private suspend fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> {
|
private fun sendAttachment(files: List<File>, handle: () -> SendAttachmentJoinHandle): Result<MediaUploadHandler> {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
MediaUploadHandlerImpl(files, handle())
|
MediaUploadHandlerImpl(files, handle())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,6 @@ interface MediaPreProcessor {
|
||||||
compressIfPossible: Boolean
|
compressIfPossible: Boolean
|
||||||
): Result<MediaUploadInfo>
|
): Result<MediaUploadInfo>
|
||||||
|
|
||||||
data class Failure(override val cause: Throwable?) : RuntimeException(cause)
|
data class Failure(override val cause: Throwable?) : Exception(cause)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,12 @@ android {
|
||||||
generateDaggerFactories.set(true)
|
generateDaggerFactories.set(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
isIncludeAndroidResources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.anvilannotations)
|
implementation(projects.anvilannotations)
|
||||||
anvil(projects.anvilcodegen)
|
anvil(projects.anvilcodegen)
|
||||||
|
|
@ -37,6 +43,7 @@ android {
|
||||||
implementation(projects.libraries.core)
|
implementation(projects.libraries.core)
|
||||||
implementation(projects.libraries.di)
|
implementation(projects.libraries.di)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
|
implementation(projects.services.toolbox.api)
|
||||||
implementation(libs.inject)
|
implementation(libs.inject)
|
||||||
implementation(libs.androidx.exifinterface)
|
implementation(libs.androidx.exifinterface)
|
||||||
implementation(libs.coroutines.core)
|
implementation(libs.coroutines.core)
|
||||||
|
|
@ -44,7 +51,10 @@ android {
|
||||||
implementation(libs.vanniktech.blurhash)
|
implementation(libs.vanniktech.blurhash)
|
||||||
|
|
||||||
testImplementation(libs.test.junit)
|
testImplementation(libs.test.junit)
|
||||||
|
testImplementation(libs.test.robolectric)
|
||||||
testImplementation(libs.coroutines.test)
|
testImplementation(libs.coroutines.test)
|
||||||
testImplementation(libs.test.truth)
|
testImplementation(libs.test.truth)
|
||||||
|
testImplementation(projects.tests.testutils)
|
||||||
|
testImplementation(projects.services.toolbox.test)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ import io.element.android.libraries.androidutils.bitmap.calculateInSampleSize
|
||||||
import io.element.android.libraries.androidutils.bitmap.resizeToMax
|
import io.element.android.libraries.androidutils.bitmap.resizeToMax
|
||||||
import io.element.android.libraries.androidutils.bitmap.rotateToMetadataOrientation
|
import io.element.android.libraries.androidutils.bitmap.rotateToMetadataOrientation
|
||||||
import io.element.android.libraries.androidutils.file.createTmpFile
|
import io.element.android.libraries.androidutils.file.createTmpFile
|
||||||
|
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
@ -33,8 +33,8 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class ImageCompressor @Inject constructor(
|
class ImageCompressor @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
|
private val dispatchers: CoroutineDispatchers,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decodes the [inputStream] into a [Bitmap] and applies the needed transformations (rotation, scale) based on [resizeMode], then writes it into a
|
* Decodes the [inputStream] into a [Bitmap] and applies the needed transformations (rotation, scale) based on [resizeMode], then writes it into a
|
||||||
* temporary file using the passed [format], [orientation] and [desiredQuality].
|
* temporary file using the passed [format], [orientation] and [desiredQuality].
|
||||||
|
|
@ -46,7 +46,7 @@ class ImageCompressor @Inject constructor(
|
||||||
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
|
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
|
||||||
orientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
|
orientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
|
||||||
desiredQuality: Int = 80,
|
desiredQuality: Int = 80,
|
||||||
): Result<ImageCompressionResult> = withContext(Dispatchers.IO) {
|
): Result<ImageCompressionResult> = withContext(dispatchers.io) {
|
||||||
runCatching {
|
runCatching {
|
||||||
val compressedBitmap = compressToBitmap(inputStreamProvider, resizeMode, orientation).getOrThrow()
|
val compressedBitmap = compressToBitmap(inputStreamProvider, resizeMode, orientation).getOrThrow()
|
||||||
// Encode bitmap to the destination temporary file
|
// Encode bitmap to the destination temporary file
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import io.element.android.libraries.androidutils.media.runAndRelease
|
||||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||||
import io.element.android.libraries.di.ApplicationContext
|
import io.element.android.libraries.di.ApplicationContext
|
||||||
import io.element.android.libraries.matrix.api.media.ThumbnailInfo
|
import io.element.android.libraries.matrix.api.media.ThumbnailInfo
|
||||||
|
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -56,13 +57,14 @@ private const val VIDEO_THUMB_FRAME = 0L
|
||||||
|
|
||||||
class ThumbnailFactory @Inject constructor(
|
class ThumbnailFactory @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
|
private val sdkIntProvider: BuildVersionSdkIntProvider
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
suspend fun createImageThumbnail(file: File): ThumbnailResult {
|
suspend fun createImageThumbnail(file: File): ThumbnailResult {
|
||||||
return createThumbnail { cancellationSignal ->
|
return createThumbnail { cancellationSignal ->
|
||||||
// This API works correctly with GIF
|
// This API works correctly with GIF
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (sdkIntProvider.isAtLeast(Build.VERSION_CODES.Q)) {
|
||||||
ThumbnailUtils.createImageThumbnail(
|
ThumbnailUtils.createImageThumbnail(
|
||||||
file,
|
file,
|
||||||
Size(THUMB_MAX_WIDTH, THUMB_MAX_HEIGHT),
|
Size(THUMB_MAX_WIDTH, THUMB_MAX_HEIGHT),
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:6831610b21668c49e31732f9005177e959277233d3cab758910e061294f91d79
|
||||||
|
size 687979
|
||||||
3
libraries/mediaupload/impl/src/test/assets/image.png
Normal file
3
libraries/mediaupload/impl/src/test/assets/image.png
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:a980f7b74cb9edc323919db8652798da4b3dcf865fc7b6a1eb1110096b7bfb4f
|
||||||
|
size 1856786
|
||||||
3
libraries/mediaupload/impl/src/test/assets/sample3s.mp3
Normal file
3
libraries/mediaupload/impl/src/test/assets/sample3s.mp3
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0244590f2b4bcb62352b574e78bea940e8d89cfa69823b5208ef4c43e0abcb44
|
||||||
|
size 52079
|
||||||
3
libraries/mediaupload/impl/src/test/assets/text.txt
Normal file
3
libraries/mediaupload/impl/src/test/assets/text.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:0ba904eae8773b70c75333db4de2f3ac45a8ad4ddba1b242f0b3cfc199391dd8
|
||||||
|
size 13
|
||||||
3
libraries/mediaupload/impl/src/test/assets/video.mp4
Normal file
3
libraries/mediaupload/impl/src/test/assets/video.mp4
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:fb58436524db95bd0c10b2c3023c2eb7b87404a2eab8987939f051647eb859d3
|
||||||
|
size 1673712
|
||||||
|
|
@ -0,0 +1,347 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023 New Vector Ltd
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.mediaupload
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||||
|
import io.element.android.libraries.matrix.api.media.AudioInfo
|
||||||
|
import io.element.android.libraries.matrix.api.media.FileInfo
|
||||||
|
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||||
|
import io.element.android.libraries.matrix.api.media.ThumbnailInfo
|
||||||
|
import io.element.android.libraries.matrix.api.media.VideoInfo
|
||||||
|
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
|
||||||
|
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
||||||
|
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||||
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
|
import kotlinx.coroutines.test.TestScope
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Ignore
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.IOException
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
class AndroidMediaPreProcessorTest {
|
||||||
|
@Test
|
||||||
|
fun `test processing image`() = runTest {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
val sut = createAndroidMediaPreProcessor(context)
|
||||||
|
val file = getFileFromAssets(context, "image.png")
|
||||||
|
val result = sut.process(
|
||||||
|
uri = file.toUri(),
|
||||||
|
mimeType = MimeTypes.Png,
|
||||||
|
deleteOriginal = false,
|
||||||
|
compressIfPossible = true,
|
||||||
|
)
|
||||||
|
// This is failing for now
|
||||||
|
val error = result.exceptionOrNull()
|
||||||
|
assertThat(error).isInstanceOf(MediaPreProcessor.Failure::class.java)
|
||||||
|
assertThat(error?.cause).isInstanceOf(NullPointerException::class.java)
|
||||||
|
/*
|
||||||
|
val data = result.getOrThrow()
|
||||||
|
assertThat(data.file.path).endsWith("image.png")
|
||||||
|
val info = data as MediaUploadInfo.Image
|
||||||
|
assertThat(info.thumbnailFile).isNull() // TODO Check this
|
||||||
|
assertThat(info.imageInfo).isEqualTo(
|
||||||
|
ImageInfo(
|
||||||
|
height = 1_178,
|
||||||
|
width = 1_818,
|
||||||
|
mimetype = MimeTypes.Png,
|
||||||
|
size = 114_867,
|
||||||
|
thumbnailInfo = null,
|
||||||
|
thumbnailSource = null,
|
||||||
|
blurhash = null,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertThat(file.exists()).isTrue()
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test processing image api Q`() = runTest {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
val sut = createAndroidMediaPreProcessor(context, sdkIntVersion = Build.VERSION_CODES.Q)
|
||||||
|
val file = getFileFromAssets(context, "image.png")
|
||||||
|
val result = sut.process(
|
||||||
|
uri = file.toUri(),
|
||||||
|
mimeType = MimeTypes.Png,
|
||||||
|
deleteOriginal = false,
|
||||||
|
compressIfPossible = true,
|
||||||
|
)
|
||||||
|
// This is not working for now
|
||||||
|
val error = result.exceptionOrNull()
|
||||||
|
assertThat(error).isInstanceOf(MediaPreProcessor.Failure::class.java)
|
||||||
|
assertThat(error?.cause).isInstanceOf(NoSuchMethodError::class.java)
|
||||||
|
/*
|
||||||
|
val data = result.getOrThrow()
|
||||||
|
assertThat(data.file.path).endsWith("image.png")
|
||||||
|
val info = data as MediaUploadInfo.Image
|
||||||
|
assertThat(info.thumbnailFile).isNull() // TODO Check this
|
||||||
|
assertThat(info.imageInfo).isEqualTo(
|
||||||
|
ImageInfo(
|
||||||
|
height = 1_178,
|
||||||
|
width = 1_818,
|
||||||
|
mimetype = MimeTypes.Png,
|
||||||
|
size = 114_867,
|
||||||
|
thumbnailInfo = null,
|
||||||
|
thumbnailSource = null,
|
||||||
|
blurhash = null,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertThat(file.exists()).isTrue()
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test processing image no compression`() = runTest {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
val sut = createAndroidMediaPreProcessor(context)
|
||||||
|
val file = getFileFromAssets(context, "image.png")
|
||||||
|
val result = sut.process(
|
||||||
|
uri = file.toUri(),
|
||||||
|
mimeType = MimeTypes.Png,
|
||||||
|
deleteOriginal = false,
|
||||||
|
compressIfPossible = false,
|
||||||
|
).getOrThrow()
|
||||||
|
assertThat(result.file.path).endsWith("image.png")
|
||||||
|
val info = result as MediaUploadInfo.Image
|
||||||
|
assertThat(info.thumbnailFile).isNotNull()
|
||||||
|
assertThat(info.imageInfo).isEqualTo(
|
||||||
|
ImageInfo(
|
||||||
|
height = 1_178,
|
||||||
|
width = 1_818,
|
||||||
|
mimetype = MimeTypes.Png,
|
||||||
|
size = 1_856_786,
|
||||||
|
thumbnailInfo = ThumbnailInfo(height = 25, width = 25, mimetype = MimeTypes.Jpeg, size = 643),
|
||||||
|
thumbnailSource = null,
|
||||||
|
blurhash = "K00000fQfQfQfQfQfQfQfQ",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertThat(file.exists()).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test processing image and delete`() = runTest {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
val sut = createAndroidMediaPreProcessor(context)
|
||||||
|
val file = getFileFromAssets(context, "image.png")
|
||||||
|
val result = sut.process(
|
||||||
|
uri = file.toUri(),
|
||||||
|
mimeType = MimeTypes.Png,
|
||||||
|
deleteOriginal = true,
|
||||||
|
compressIfPossible = false,
|
||||||
|
).getOrThrow()
|
||||||
|
assertThat(result.file.path).endsWith("image.png")
|
||||||
|
val info = result as MediaUploadInfo.Image
|
||||||
|
assertThat(info.thumbnailFile).isNotNull()
|
||||||
|
assertThat(info.imageInfo).isEqualTo(
|
||||||
|
ImageInfo(
|
||||||
|
height = 1_178,
|
||||||
|
width = 1_818,
|
||||||
|
mimetype = MimeTypes.Png,
|
||||||
|
size = 1_856_786,
|
||||||
|
thumbnailInfo = ThumbnailInfo(height = 25, width = 25, mimetype = MimeTypes.Jpeg, size = 643),
|
||||||
|
thumbnailSource = null,
|
||||||
|
blurhash = "K00000fQfQfQfQfQfQfQfQ",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
// Does not work
|
||||||
|
// assertThat(file.exists()).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test processing gif`() = runTest {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
val sut = createAndroidMediaPreProcessor(context)
|
||||||
|
val file = getFileFromAssets(context, "animated_gif.gif")
|
||||||
|
val result = sut.process(
|
||||||
|
uri = file.toUri(),
|
||||||
|
mimeType = MimeTypes.Gif,
|
||||||
|
deleteOriginal = false,
|
||||||
|
compressIfPossible = true,
|
||||||
|
).getOrThrow()
|
||||||
|
assertThat(result.file.path).endsWith("animated_gif.gif")
|
||||||
|
val info = result as MediaUploadInfo.Image
|
||||||
|
assertThat(info.thumbnailFile).isNotNull()
|
||||||
|
assertThat(info.imageInfo).isEqualTo(
|
||||||
|
ImageInfo(
|
||||||
|
height = 600,
|
||||||
|
width = 800,
|
||||||
|
mimetype = MimeTypes.Gif,
|
||||||
|
size = 687_979,
|
||||||
|
thumbnailInfo = ThumbnailInfo(height = 50, width = 50, mimetype = MimeTypes.Jpeg, size = 691),
|
||||||
|
thumbnailSource = null,
|
||||||
|
blurhash = "K00000fQfQfQfQfQfQfQfQ",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertThat(file.exists()).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test processing file`() = runTest {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
val sut = createAndroidMediaPreProcessor(context)
|
||||||
|
val file = getFileFromAssets(context, "text.txt")
|
||||||
|
val result = sut.process(
|
||||||
|
uri = file.toUri(),
|
||||||
|
mimeType = MimeTypes.PlainText,
|
||||||
|
deleteOriginal = false,
|
||||||
|
compressIfPossible = true,
|
||||||
|
).getOrThrow()
|
||||||
|
assertThat(result.file.path).endsWith("text.txt")
|
||||||
|
val info = result as MediaUploadInfo.AnyFile
|
||||||
|
assertThat(info.fileInfo).isEqualTo(
|
||||||
|
FileInfo(
|
||||||
|
mimetype = MimeTypes.PlainText,
|
||||||
|
size = 13,
|
||||||
|
thumbnailInfo = null,
|
||||||
|
thumbnailSource = null,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertThat(file.exists()).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Ignore("Compressing video is not working with Robolectric")
|
||||||
|
@Test
|
||||||
|
fun `test processing video`() = runTest {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
val sut = createAndroidMediaPreProcessor(context)
|
||||||
|
val file = getFileFromAssets(context, "video.mp4")
|
||||||
|
val result = sut.process(
|
||||||
|
uri = file.toUri(),
|
||||||
|
mimeType = MimeTypes.Mp4,
|
||||||
|
deleteOriginal = false,
|
||||||
|
compressIfPossible = true,
|
||||||
|
).getOrThrow()
|
||||||
|
assertThat(result.file.path).endsWith("video.mp4")
|
||||||
|
val info = result as MediaUploadInfo.Video
|
||||||
|
assertThat(info.thumbnailFile).isNotNull()
|
||||||
|
assertThat(info.videoInfo).isEqualTo(
|
||||||
|
VideoInfo(
|
||||||
|
duration = Duration.ZERO, // Not available with Robolectric?
|
||||||
|
height = 1_178,
|
||||||
|
width = 1_818,
|
||||||
|
mimetype = MimeTypes.Mp4,
|
||||||
|
size = 114_867,
|
||||||
|
thumbnailInfo = null,
|
||||||
|
thumbnailSource = null,
|
||||||
|
blurhash = null,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertThat(file.exists()).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test processing video no compression`() = runTest {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
val sut = createAndroidMediaPreProcessor(context)
|
||||||
|
val file = getFileFromAssets(context, "video.mp4")
|
||||||
|
val result = sut.process(
|
||||||
|
uri = file.toUri(),
|
||||||
|
mimeType = MimeTypes.Mp4,
|
||||||
|
deleteOriginal = false,
|
||||||
|
compressIfPossible = false,
|
||||||
|
).getOrThrow()
|
||||||
|
assertThat(result.file.path).endsWith("video.mp4")
|
||||||
|
val info = result as MediaUploadInfo.Video
|
||||||
|
assertThat(info.thumbnailFile).isNotNull()
|
||||||
|
assertThat(info.videoInfo).isEqualTo(
|
||||||
|
VideoInfo(
|
||||||
|
duration = Duration.ZERO, // Not available with Robolectric?
|
||||||
|
height = 0, // Not available with Robolectric?
|
||||||
|
width = 0, // Not available with Robolectric?
|
||||||
|
mimetype = MimeTypes.Mp4,
|
||||||
|
size = 1_673_712,
|
||||||
|
thumbnailInfo = ThumbnailInfo(height = null, width = null, mimetype = MimeTypes.Jpeg, size = 0), // Not available with Robolectric?
|
||||||
|
thumbnailSource = null,
|
||||||
|
blurhash = null,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertThat(file.exists()).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test processing audio`() = runTest {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
val sut = createAndroidMediaPreProcessor(context)
|
||||||
|
val file = getFileFromAssets(context, "sample3s.mp3")
|
||||||
|
val result = sut.process(
|
||||||
|
uri = file.toUri(),
|
||||||
|
mimeType = MimeTypes.Mp3,
|
||||||
|
deleteOriginal = false,
|
||||||
|
compressIfPossible = true,
|
||||||
|
).getOrThrow()
|
||||||
|
assertThat(result.file.path).endsWith("sample3s.mp3")
|
||||||
|
val info = result as MediaUploadInfo.Audio
|
||||||
|
assertThat(info.audioInfo).isEqualTo(
|
||||||
|
AudioInfo(
|
||||||
|
duration = Duration.ZERO, // Not available with Robolectric?
|
||||||
|
size = 52_079,
|
||||||
|
mimetype = MimeTypes.Mp3,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assertThat(file.exists()).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test file which does not exist`() = runTest {
|
||||||
|
val context = InstrumentationRegistry.getInstrumentation().context
|
||||||
|
val sut = createAndroidMediaPreProcessor(context)
|
||||||
|
val file = File(context.cacheDir, "not found.txt")
|
||||||
|
val result = sut.process(
|
||||||
|
uri = file.toUri(),
|
||||||
|
mimeType = MimeTypes.PlainText,
|
||||||
|
deleteOriginal = false,
|
||||||
|
compressIfPossible = true,
|
||||||
|
)
|
||||||
|
assertThat(result.isFailure).isTrue()
|
||||||
|
val failure = result.exceptionOrNull()
|
||||||
|
assertThat(failure).isInstanceOf(MediaPreProcessor.Failure::class.java)
|
||||||
|
assertThat(failure?.cause).isInstanceOf(FileNotFoundException::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun TestScope.createAndroidMediaPreProcessor(
|
||||||
|
context: Context,
|
||||||
|
sdkIntVersion: Int = Build.VERSION_CODES.P
|
||||||
|
) = AndroidMediaPreProcessor(
|
||||||
|
context = context,
|
||||||
|
thumbnailFactory = ThumbnailFactory(context, FakeBuildVersionSdkIntProvider(sdkIntVersion)),
|
||||||
|
imageCompressor = ImageCompressor(context, testCoroutineDispatchers()),
|
||||||
|
videoCompressor = VideoCompressor(context),
|
||||||
|
coroutineDispatchers = testCoroutineDispatchers(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun getFileFromAssets(context: Context, fileName: String): File = File(context.cacheDir, fileName)
|
||||||
|
.also {
|
||||||
|
if (!it.exists()) {
|
||||||
|
it.outputStream().use { cache ->
|
||||||
|
context.assets.open(fileName).use { inputStream ->
|
||||||
|
inputStream.copyTo(cache)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue