Adds iMeta tags to GIF urls

This commit is contained in:
Vitor Pamplona
2025-01-05 12:14:28 -05:00
parent f1d61daa04
commit 306ce9a36c
5 changed files with 45 additions and 11 deletions
@@ -105,7 +105,7 @@ class ImageUploadTesting {
assertEquals("${server.baseUrl}/$initialHash", result.url?.removeSuffix(".png")) assertEquals("${server.baseUrl}/$initialHash", result.url?.removeSuffix(".png"))
val imageData: ByteArray = val imageData: ByteArray =
ImageDownloader().waitAndGetImage(result.url!!, false) ImageDownloader().waitAndGetImage(result.url!!, false)?.bytes
?: run { ?: run {
fail("${server.name}: Should not be null") fail("${server.name}: Should not be null")
return return
@@ -148,7 +148,7 @@ class ImageUploadTesting {
Assert.assertTrue("${server.name}: Invalid result url", url.startsWith("http")) Assert.assertTrue("${server.name}: Invalid result url", url.startsWith("http"))
val imageData: ByteArray = val imageData: ByteArray =
ImageDownloader().waitAndGetImage(url, false) ImageDownloader().waitAndGetImage(url, false)?.bytes
?: run { ?: run {
fail("${server.name}: Should not be null") fail("${server.name}: Should not be null")
return return
@@ -55,10 +55,10 @@ class FileHeader(
forceProxy: Boolean, forceProxy: Boolean,
): Result<FileHeader> = ): Result<FileHeader> =
try { try {
val imageData: ByteArray? = ImageDownloader().waitAndGetImage(fileUrl, forceProxy) val imageData: ImageDownloader.Blob? = ImageDownloader().waitAndGetImage(fileUrl, forceProxy)
if (imageData != null) { if (imageData != null) {
prepare(imageData, mimeType, dimPrecomputed) prepare(imageData.bytes, mimeType ?: imageData.contentType, dimPrecomputed)
} else { } else {
Result.failure(UnableToDownload(fileUrl)) Result.failure(UnableToDownload(fileUrl))
} }
@@ -29,12 +29,17 @@ import java.net.HttpURLConnection
import java.net.URL import java.net.URL
class ImageDownloader { class ImageDownloader {
class Blob(
val bytes: ByteArray,
val contentType: String?,
)
suspend fun waitAndGetImage( suspend fun waitAndGetImage(
imageUrl: String, imageUrl: String,
forceProxy: Boolean, forceProxy: Boolean,
): ByteArray? = ): Blob? =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
var imageData: ByteArray? = null var imageData: Blob? = null
var tentatives = 0 var tentatives = 0
// Servers are usually not ready.. so tries to download it for 15 times/seconds. // Servers are usually not ready.. so tries to download it for 15 times/seconds.
@@ -59,7 +64,7 @@ class ImageDownloader {
private suspend fun tryGetTheImage( private suspend fun tryGetTheImage(
imageUrl: String, imageUrl: String,
forceProxy: Boolean, forceProxy: Boolean,
): ByteArray? = ): Blob? =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
// TODO: Migrate to OkHttp // TODO: Migrate to OkHttp
HttpURLConnection.setFollowRedirects(true) HttpURLConnection.setFollowRedirects(true)
@@ -88,7 +93,10 @@ class ImageDownloader {
} }
return@withContext if (responseCode in 200..300) { return@withContext if (responseCode in 200..300) {
huc.inputStream.use { it.readBytes() } Blob(
huc.inputStream.use { it.readBytes() },
huc.headerFields.get("Content-Type")?.firstOrNull(),
)
} else { } else {
null null
} }
@@ -224,15 +224,15 @@ class UploadOrchestrator {
updateState(0.6, UploadingState.Downloading) updateState(0.6, UploadingState.Downloading)
val imageData: ByteArray? = ImageDownloader().waitAndGetImage(uploadResult.url, forceProxy(uploadResult.url)) val imageData: ImageDownloader.Blob? = ImageDownloader().waitAndGetImage(uploadResult.url, forceProxy(uploadResult.url))
if (imageData != null) { if (imageData != null) {
updateState(0.8, UploadingState.Hashing) updateState(0.8, UploadingState.Hashing)
val result = val result =
FileHeader.prepare( FileHeader.prepare(
imageData, imageData.bytes,
uploadResult.type ?: localContentType, uploadResult.type ?: localContentType ?: imageData.contentType,
uploadResult.dimension, uploadResult.dimension,
) )
@@ -20,8 +20,10 @@
*/ */
package com.vitorpamplona.amethyst.ui.actions package com.vitorpamplona.amethyst.ui.actions
import android.R.attr.mimeType
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import android.webkit.MimeTypeMap
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
@@ -43,6 +45,7 @@ import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.LocationState import com.vitorpamplona.amethyst.service.LocationState
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
import com.vitorpamplona.amethyst.service.uploads.FileHeader
import com.vitorpamplona.amethyst.service.uploads.MediaCompressor import com.vitorpamplona.amethyst.service.uploads.MediaCompressor
import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator import com.vitorpamplona.amethyst.service.uploads.MultiOrchestrator
import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator import com.vitorpamplona.amethyst.service.uploads.UploadOrchestrator
@@ -92,6 +95,7 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.Locale
import java.util.UUID import java.util.UUID
import kotlin.math.round import kotlin.math.round
@@ -1228,6 +1232,28 @@ open class NewPostViewModel : ViewModel() {
val lastWordStart = it.end - lastWord.length val lastWordStart = it.end - lastWord.length
val wordToInsert = item.url.url + " " val wordToInsert = item.url.url + " "
viewModelScope.launch(Dispatchers.IO) {
val fileExtension: String = MimeTypeMap.getFileExtensionFromUrl(item.url.url)
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileExtension.lowercase(Locale.getDefault()))
val forceProxy = accountViewModel?.account?.shouldUseTorForImageDownload() ?: false
val imeta =
FileHeader.prepare(item.url.url, mimeType, null, forceProxy).getOrNull()?.let {
IMetaTagBuilder(item.url.url)
.apply {
hash(it.hash)
size(it.size)
it.mimeType?.let { mimeType(it) }
it.dim?.let { dims(it) }
it.blurHash?.let { blurhash(it.blurhash) }
}.build()
}
if (imeta != null) {
iMetaAttachments += imeta
}
}
message = message =
TextFieldValue( TextFieldValue(
message.text.replaceRange(lastWordStart, it.end, wordToInsert), message.text.replaceRange(lastWordStart, it.end, wordToInsert),