code simplification and deduplication

This commit is contained in:
davotoula
2025-11-02 19:45:51 +01:00
parent 4b260cfc07
commit 76be7b0d58
4 changed files with 63 additions and 73 deletions
@@ -31,10 +31,24 @@ import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
import com.vitorpamplona.quartz.utils.Log
object BlurhashMetadataCalculator {
fun shouldAttempt(mimeType: String?): Boolean =
mimeType?.let {
it.startsWith("image/", ignoreCase = true) || it.startsWith("video/", ignoreCase = true)
} ?: false
private fun isImage(mimeType: String?) = mimeType?.startsWith("image/", ignoreCase = true) == true
private fun isVideo(mimeType: String?) = mimeType?.startsWith("video/", ignoreCase = true) == true
fun shouldAttempt(mimeType: String?): Boolean = isImage(mimeType) || isVideo(mimeType)
private fun createBitmapOptions() =
BitmapFactory.Options().apply {
inPreferredConfig = Bitmap.Config.ARGB_8888
}
private fun processImage(
bitmap: Bitmap?,
dimPrecomputed: DimensionTag?,
): Pair<BlurhashWrapper?, DimensionTag?> {
val (blur, dim) = processBitmap(bitmap)
return blur to (dim ?: dimPrecomputed)
}
fun computeFromBytes(
data: ByteArray,
@@ -42,14 +56,11 @@ object BlurhashMetadataCalculator {
dimPrecomputed: DimensionTag?,
): Pair<BlurhashWrapper?, DimensionTag?> =
when {
mimeType?.startsWith("image/", ignoreCase = true) == true -> {
val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.ARGB_8888
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size, options)
val (blur, dim) = processBitmap(bitmap)
blur to (dim ?: dimPrecomputed)
isImage(mimeType) -> {
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size, createBitmapOptions())
processImage(bitmap, dimPrecomputed)
}
mimeType?.startsWith("video/", ignoreCase = true) == true -> {
isVideo(mimeType) -> {
val retriever = MediaMetadataRetriever()
try {
retriever.setDataSource(ByteArrayMediaDataSource(data))
@@ -71,16 +82,13 @@ object BlurhashMetadataCalculator {
return try {
when {
mimeType?.startsWith("image/", ignoreCase = true) == true ->
isImage(mimeType) ->
context.contentResolver.openInputStream(uri)?.use { stream ->
val options = BitmapFactory.Options()
options.inPreferredConfig = Bitmap.Config.ARGB_8888
val bitmap = BitmapFactory.decodeStream(stream, null, options)
val (blur, dim) = processBitmap(bitmap)
blur to (dim ?: dimPrecomputed)
val bitmap = BitmapFactory.decodeStream(stream, null, createBitmapOptions())
processImage(bitmap, dimPrecomputed)
} ?: (null to dimPrecomputed)
mimeType?.startsWith("video/", ignoreCase = true) == true -> {
isVideo(mimeType) -> {
val retriever = MediaMetadataRetriever()
try {
retriever.setDataSource(context, uri)
@@ -89,6 +97,7 @@ object BlurhashMetadataCalculator {
retriever.release()
}
}
else -> null
}
} catch (e: Exception) {
@@ -232,35 +232,33 @@ class UploadOrchestrator {
updateState(0.6, UploadingState.Downloading)
// Use streaming verification for memory efficiency with large files
val verification: ImageDownloader.StreamVerification? = ImageDownloader().waitAndVerifyStream(uploadResult.url, okHttpClient)
val verification =
ImageDownloader().waitAndVerifyStream(uploadResult.url, okHttpClient)
?: return error(R.string.could_not_download_from_the_server)
if (verification != null) {
updateState(0.8, UploadingState.Hashing)
updateState(0.8, UploadingState.Hashing)
// Create FileHeader with hash from streaming verification
// Note: We skip blurhash/dimensions since we already have them from upload
val fileHeader =
FileHeader(
mimeType = uploadResult.type ?: localContentType ?: verification.contentType,
hash = verification.hash,
size = verification.size.toInt(),
dim = uploadResult.dimension,
blurHash = uploadResult.blurHash,
)
return finish(
OrchestratorResult.ServerResult(
fileHeader,
uploadResult.url,
uploadResult.magnet,
uploadResult.sha256,
originalContentType,
originalHash,
),
// Create FileHeader with hash from streaming verification
// Note: We skip blurhash/dimensions since we already have them from upload
val fileHeader =
FileHeader(
mimeType = uploadResult.type ?: localContentType ?: verification.contentType,
hash = verification.hash,
size = verification.size.toInt(),
dim = uploadResult.dimension,
blurHash = uploadResult.blurHash,
)
} else {
return error(R.string.could_not_download_from_the_server)
}
return finish(
OrchestratorResult.ServerResult(
fileHeader,
uploadResult.url,
uploadResult.magnet,
uploadResult.sha256,
originalContentType,
originalHash,
),
)
}
sealed class OrchestratorResult {
@@ -52,20 +52,6 @@ import java.io.InputStream
import java.util.Base64
class BlossomUploader {
data class StreamInfo(
val hash: HexKey,
val size: Long,
)
/**
* Calculate SHA256 hash and size of a file by streaming it in chunks
* to avoid loading the entire file into memory.
*/
private fun calculateHashAndSize(inputStream: InputStream): StreamInfo {
val (hash, size) = sha256StreamWithCount(inputStream)
return StreamInfo(hash.toHexKey(), size)
}
fun Context.getFileName(uri: Uri): String? =
when (uri.scheme) {
ContentResolver.SCHEME_CONTENT -> getContentFileName(uri)
@@ -100,23 +86,23 @@ class BlossomUploader {
val imageInputStreamForHash = contentResolver.openInputStream(uri)
checkNotNull(imageInputStreamForHash) { "Can't open the image input stream" }
val streamInfo =
imageInputStreamForHash.use {
calculateHashAndSize(it)
val (hash, size) =
imageInputStreamForHash.use { stream ->
val (hashBytes, totalBytes) = sha256StreamWithCount(stream)
hashBytes.toHexKey() to totalBytes
}
val localMetadata = BlurhashMetadataCalculator.computeFromUri(context, uri, myContentType)
val imageInputStream = contentResolver.openInputStream(uri)
checkNotNull(imageInputStream) { "Can't open the image input stream" }
val serverResult =
imageInputStream.use { stream ->
upload(
stream,
streamInfo.hash,
streamInfo.size,
hash,
size,
fileName,
myContentType,
alt,
@@ -128,15 +128,12 @@ class Nip96Uploader {
)
}
val merged =
localMetadata?.let { (blur, dim) ->
serverResult.copy(
dimension = dim ?: serverResult.dimension,
blurHash = blur ?: serverResult.blurHash,
)
} ?: serverResult
return merged
return localMetadata?.let { (blur, dim) ->
serverResult.copy(
dimension = dim ?: serverResult.dimension,
blurHash = blur ?: serverResult.blurHash,
)
} ?: serverResult
}
suspend fun upload(