- Adds support for multiple media uploads at the same time.

- Adds support to display PictureEvents with multiple images at the same time
- Improves Uploading feedback for the NewPost screen
- 10x better performance on Blurhash generation
- Fixes cosine caching on Blurhash
- Removes troublesome dependency on blurhash encoder liberary
- Restructures contentScale for Images and Video dialogs
- Refactors Media Uploaders to improve code reuse
- Refactors iMeta usage on Quartz to move away from NIP-94
This commit is contained in:
Vitor Pamplona
2024-12-18 16:52:50 -05:00
parent 728768632c
commit 579f63935a
100 changed files with 3260 additions and 2503 deletions
@@ -0,0 +1,90 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.blurhash
import kotlin.math.ceil
import kotlin.math.log
object Base83 {
fun encode(value: Long): String =
if (value > 82) {
encode(value, ceil(log((value + 1).toDouble(), 83.0)).toInt())
} else {
encode(value, 1)
}
fun encode(
value: Long,
length: Int,
): String {
val buffer = CharArray(length)
encode(value, length, buffer, 0)
return String(buffer)
}
fun encode(
value: Long,
length: Int,
buffer: CharArray,
offset: Int,
) {
var exp = 1L
for (i in 1..length) {
val digit = (value / exp % 83).toInt()
buffer[offset + length - i] = ALPHABET[digit]
exp *= 83
}
}
fun decodeAt(
str: String,
at: Int = 0,
): Int = charMap[str[at].code]
fun decodeFixed2(
str: String,
from: Int = 0,
): Int = charMap[str[from].code] * 83 + charMap[str[from + 1].code]
fun decode(
str: String,
from: Int = 0,
to: Int = str.length,
): Int {
var result = 0
for (i in from until to) {
result = result * 83 + charMap[str[i].code]
}
return result
}
val ALPHABET: CharArray = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".toCharArray()
private val charMap =
ALPHABET
.mapIndexed { i, c -> c.code to i }
.toMap()
.let { charMap ->
Array(255) {
charMap[it] ?: 0
}
}
}
@@ -0,0 +1,61 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.blurhash
import android.graphics.Bitmap
import kotlin.math.roundToInt
fun Bitmap.toBlurhash(): String {
val aspectRatio = this.width.toFloat() / this.height.toFloat()
if (this.width > 100 && this.height > 100) {
return Bitmap.createScaledBitmap(this, 100, (100 / aspectRatio).toInt(), false).toBlurhash()
}
val intArray = IntArray(width * height)
this.getPixels(intArray, 0, width, 0, 0, width, height)
val numX =
if (aspectRatio > 1) {
9
} else if (aspectRatio < 1) {
(9 * aspectRatio).roundToInt()
} else {
4
}
val numY =
if (aspectRatio > 1) {
(9 * (1 / aspectRatio)).roundToInt()
} else if (aspectRatio < 1) {
9
} else {
4
}
return BlurHashEncoder().encode(
intArray,
width,
height,
numX,
numY,
)
}
@@ -18,38 +18,22 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.preview
package com.vitorpamplona.amethyst.commons.blurhash
import android.graphics.Bitmap
import kotlin.math.cos
import com.vitorpamplona.amethyst.commons.blurhash.SRGB.Companion.linearToSrgb
import com.vitorpamplona.amethyst.commons.blurhash.SRGB.Companion.srgbToLinear
import kotlin.math.pow
import kotlin.math.roundToInt
import kotlin.math.withSign
object BlurHashDecoder {
// cache Math.cos() calculations to improve performance.
// The number of calculations can be huge for many bitmaps: width * height * numCompX * numCompY *
// 2 * nBitmaps
// the cache is enabled by default, it is recommended to disable it only when just a few images
// are displayed
private val cacheCosinesX = HashMap<Int, DoubleArray>()
private val cacheCosinesY = HashMap<Int, DoubleArray>()
/**
* Clear calculations stored in memory cache. The cache is not big, but will increase when many
* image sizes are used, if the app needs memory it is recommended to clear it.
*/
fun clearCache() {
cacheCosinesX.clear()
cacheCosinesY.clear()
}
/** Returns width/height */
fun aspectRatio(blurHash: String?): Float? {
if (blurHash == null || blurHash.length < 6) {
return null
}
val numCompEnc = decode83At(blurHash, 0)
val numCompEnc = Base83.decodeAt(blurHash, 0)
val numCompX = (numCompEnc % 9) + 1
val numCompY = (numCompEnc / 9) + 1
if (blurHash.length != 4 + 2 * numCompX * numCompY) {
@@ -64,18 +48,18 @@ object BlurHashDecoder {
numCompY: Int,
blurHash: String,
): Array<FloatArray> {
val maxAc = (decode83At(blurHash, 1) + 1) / 166f
val maxAc = (Base83.decodeAt(blurHash, 1) + 1) / 166f
return Array(numCompX * numCompY) { i ->
if (i == 0) {
decodeDc(decode83(blurHash, 2, 6))
decodeDc(Base83.decode(blurHash, 2, 6))
} else {
decodeAc(decode83Fixed2(blurHash, 4 + i * 2), maxAc)
decodeAc(Base83.decodeFixed2(blurHash, 4 + i * 2), maxAc)
}
}
}
fun computeNumComponets(blurHash: String): Pair<Int, Int> {
val numCompEnc = decode83At(blurHash, 0)
val numCompEnc = Base83.decodeAt(blurHash, 0)
val numCompX = (numCompEnc % 9) + 1
val numCompY = (numCompEnc / 9) + 1
return Pair(numCompX, numCompY)
@@ -106,28 +90,6 @@ object BlurHashDecoder {
return Bitmap.createBitmap(imageArray, width, height, Bitmap.Config.ARGB_8888)
}
private fun decode83At(
str: String,
at: Int = 0,
): Int = charMap[str[at].code]
private fun decode83Fixed2(
str: String,
from: Int = 0,
): Int = charMap[str[from].code] * 83 + charMap[str[from + 1].code]
private fun decode83(
str: String,
from: Int = 0,
to: Int = str.length,
): Int {
var result = 0
for (i in from until to) {
result = result * 83 + charMap[str[i].code]
}
return result
}
private fun decodeDc(colorEnc: Int): FloatArray {
val r = colorEnc shr 16
val g = (colorEnc shr 8) and 255
@@ -135,15 +97,6 @@ object BlurHashDecoder {
return floatArrayOf(srgbToLinear(r), srgbToLinear(g), srgbToLinear(b))
}
private fun srgbToLinear(colorEnc: Int): Float {
val v = colorEnc / 255f
return if (v <= 0.04045f) {
(v / 12.92f)
} else {
((v + 0.055f) / 1.055f).pow(2.4f)
}
}
private fun decodeAc(
value: Int,
maxAc: Float,
@@ -170,10 +123,10 @@ object BlurHashDecoder {
): IntArray {
// use an array for better performance when writing pixel colors
val imageArray = IntArray(width * height)
val calculateCosX = !useCache || !cacheCosinesX.containsKey(width * numCompX)
val cosinesX = getArrayForCosinesX(calculateCosX, width, numCompX)
val calculateCosY = !useCache || !cacheCosinesY.containsKey(height * numCompY)
val cosinesY = getArrayForCosinesY(calculateCosY, height, numCompY)
val calculateCosX = !useCache || !CosineCache.hasX(width * numCompX)
val cosinesX = CosineCache.getArrayForCosinesX(calculateCosX, width, numCompX)
val calculateCosY = !useCache || !CosineCache.hasY(height * numCompY)
val cosinesY = CosineCache.getArrayForCosinesY(calculateCosY, height, numCompY)
var r = 0.0f
var g = 0.0f
@@ -208,145 +161,4 @@ object BlurHashDecoder {
green: Int,
blue: Int,
): Int = -0x1000000 or (red shl 16) or (green shl 8) or blue
private fun getArrayForCosinesY(
calculate: Boolean,
height: Int,
numCompY: Int,
) = when {
calculate -> {
DoubleArray(height * numCompY) {
val y = it / numCompY
val j = it % numCompY
cos(Math.PI * y * j / height)
}.also {
cacheCosinesY[height * numCompY] = it
}
}
else -> {
cacheCosinesY[height * numCompY]!!
}
}
private fun getArrayForCosinesX(
calculate: Boolean,
width: Int,
numCompX: Int,
) = when {
calculate -> {
DoubleArray(width * numCompX) {
val x = it / numCompX
val i = it % numCompX
cos(Math.PI * x * i / width)
}.also { cacheCosinesX[width * numCompX] = it }
}
else -> cacheCosinesX[width * numCompX]!!
}
private fun linearToSrgb(value: Float): Int {
val v = value.coerceIn(0f, 1f)
return if (v <= 0.0031308f) {
(v * 12.92f * 255f + 0.5f).toInt()
} else {
((1.055f * v.pow(1 / 2.4f) - 0.055f) * 255 + 0.5f).toInt()
}
}
private val linToSrgbApproximation =
Array(255) {
linearToSrgb(it / 255f)
}
private val charMap =
listOf(
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'#',
'$',
'%',
'*',
'+',
',',
'-',
'.',
':',
';',
'=',
'?',
'@',
'[',
']',
'^',
'_',
'{',
'|',
'}',
'~',
).mapIndexed { i, c -> c.code to i }
.toMap()
.let { charMap ->
Array(255) {
charMap[it] ?: 0
}
}
}
@@ -18,7 +18,7 @@
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.preview
package com.vitorpamplona.amethyst.commons.blurhash
import android.graphics.Bitmap
import android.graphics.Color
@@ -0,0 +1,143 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.blurhash
import com.vitorpamplona.amethyst.commons.blurhash.Base83.encode
import com.vitorpamplona.amethyst.commons.blurhash.SRGB.Companion.linearToSrgb
import kotlin.math.abs
import kotlin.math.floor
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.withSign
class BlurHashEncoder {
fun signPow(
value: Double,
exp: Double,
): Double = abs(value).pow(exp).withSign(value)
private fun encodeAC(
value: DoubleArray,
maximumValue: Double,
): Long {
val quantR = floor(max(0.0, min(18.0, floor(signPow(value[0] / maximumValue, 0.5) * 9 + 9.5))))
val quantG = floor(max(0.0, min(18.0, floor(signPow(value[1] / maximumValue, 0.5) * 9 + 9.5))))
val quantB = floor(max(0.0, min(18.0, floor(signPow(value[2] / maximumValue, 0.5) * 9 + 9.5))))
return Math.round(quantR * 19 * 19 + quantG * 19 + quantB)
}
private fun encodeDC(value: DoubleArray): Long {
val r = linearToSrgb(value[0]).toLong()
val g = linearToSrgb(value[1]).toLong()
val b = linearToSrgb(value[2]).toLong()
return (r shl 16) + (g shl 8) + b
}
fun max(
values: Array<DoubleArray>,
from: Int,
endExclusive: Int,
): Double {
var result = Double.NEGATIVE_INFINITY
for (i in from until endExclusive) {
for (j in values[i].indices) {
val value = values[i][j]
if (value > result) {
result = value
}
}
}
return result
}
fun encode(
pixels: IntArray,
width: Int,
height: Int,
componentX: Int,
componentY: Int,
useCache: Boolean = true,
): String {
require(!(componentX < 1 || componentX > 9 || componentY < 1 || componentY > 9)) { "Blur hash must have between 1 and 9 components" }
require(width * height == pixels.size) { "Width and height must match the pixels array" }
val factors = Array(componentX * componentY) { DoubleArray(3) }
val calculateCosX = !useCache || !CosineCache.hasX(width * componentX)
val cosinesX = CosineCache.getArrayForCosinesX(calculateCosX, width, componentX)
val calculateCosY = !useCache || !CosineCache.hasY(height * componentY)
val cosinesY = CosineCache.getArrayForCosinesY(calculateCosY, height, componentY)
val scale = 1.0 / (width * height)
var r = 0.0
var g = 0.0
var b = 0.0
for (j in 0 until componentY) {
for (i in 0 until componentX) {
val normalisation = (if (i == 0 && j == 0) 1 else 2).toDouble()
r = 0.0
g = 0.0
b = 0.0
for (x in 0 until width) {
for (y in 0 until height) {
val basis = normalisation * cosinesY[j + componentY * y] * cosinesX[i + componentX * x]
val pixel = pixels[y * width + x]
r += basis * SRGB.srgbToLinear((pixel shr 16) and 0xff)
g += basis * SRGB.srgbToLinear((pixel shr 8) and 0xff)
b += basis * SRGB.srgbToLinear(pixel and 0xff)
}
}
val colors = factors[j * componentX + i]
colors[0] = r * scale
colors[1] = g * scale
colors[2] = b * scale
}
}
val hash = CharArray(1 + 1 + 4 + 2 * (factors.size - 1)) // size flag + max AC + DC + 2 * AC components
val sizeFlag = (componentX - 1 + (componentY - 1) * 9).toLong()
encode(sizeFlag, 1, hash, 0)
val maximumValue: Double
if (factors.size > 1) {
val actualMaximumValue = max(factors, 1, factors.size)
val quantisedMaximumValue = floor(max(0.0, min(82.0, floor(actualMaximumValue * 166 - 0.5))))
maximumValue = (quantisedMaximumValue + 1) / 166
encode(Math.round(quantisedMaximumValue), 1, hash, 1)
} else {
maximumValue = 1.0
encode(0, 1, hash, 1)
}
val dc = factors[0]
encode(encodeDC(dc), 4, hash, 2)
for (i in 1 until factors.size) {
encode(encodeAC(factors[i], maximumValue), 2, hash, 6 + 2 * (i - 1))
}
return String(hash)
}
}
@@ -0,0 +1,81 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.blurhash
import androidx.collection.LruCache
import kotlin.math.cos
object CosineCache {
// cache Math.cos() calculations to improve performance.
// The number of calculations can be huge for many bitmaps: width * height * numCompX * numCompY *
// 2 * nBitmaps
// the cache is enabled by default, it is recommended to disable it only when just a few images
// are displayed
private val cacheCosinesX = LruCache<Int, DoubleArray>(20)
private val cacheCosinesY = LruCache<Int, DoubleArray>(20)
/**
* Clear calculations stored in memory cache. The cache is not big, but will increase when many
* image sizes are used, if the app needs memory it is recommended to clear it.
*/
fun clearCache() {
cacheCosinesX.evictAll()
cacheCosinesY.evictAll()
}
fun hasX(idx: Int) = cacheCosinesX.get(idx) != null
fun hasY(idx: Int) = cacheCosinesY.get(idx) != null
fun getArrayForCosinesY(
calculate: Boolean,
height: Int,
numCompY: Int,
) = when {
calculate -> {
DoubleArray(height * numCompY) {
val y = it / numCompY
val j = it % numCompY
cos(Math.PI * y * j / height)
}.also {
cacheCosinesY.put(height * numCompY, it)
}
}
else -> {
cacheCosinesY[height * numCompY]!!
}
}
fun getArrayForCosinesX(
calculate: Boolean,
width: Int,
numCompX: Int,
) = when {
calculate -> {
DoubleArray(width * numCompX) {
val x = it / numCompX
val i = it % numCompX
cos(Math.PI * x * i / width)
}.also { cacheCosinesX.put(width * numCompX, it) }
}
else -> cacheCosinesX[width * numCompX]!!
}
}
@@ -0,0 +1,56 @@
/**
* Copyright (c) 2024 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.amethyst.commons.blurhash
import kotlin.math.pow
class SRGB {
companion object {
fun linearToSrgb(value: Float): Int {
val v = value.coerceIn(0.0f, 1.0f)
return if (v <= 0.0031308f) {
(v * 12.92f * 255f + 0.5f).toInt()
} else {
((1.055f * v.pow(1 / 2.4f) - 0.055f) * 255 + 0.5f).toInt()
}
}
fun linearToSrgb(value: Double): Int {
val v = value.coerceIn(0.0, 1.0)
return if (v <= 0.0031308f) {
(v * 12.92f * 255f + 0.5f).toInt()
} else {
((1.055f * v.pow(1 / 2.4) - 0.055f) * 255 + 0.5f).toInt()
}
}
fun srgbToLinear(value: Int): Float {
val valueCheck = value.coerceIn(0, 255)
val v = valueCheck / 255f
return if (v <= 0.04045f) {
v / 12.92f
} else {
((v + 0.055f) / 1.055f).pow(2.4f)
}
}
}
}
@@ -21,7 +21,7 @@
package com.vitorpamplona.amethyst.commons.richtext
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.events.Dimension
import com.vitorpamplona.quartz.encoders.Dimension
import java.io.File
@Immutable
@@ -24,10 +24,10 @@ import android.util.Log
import android.util.Patterns
import com.linkedin.urls.detection.UrlDetector
import com.linkedin.urls.detection.UrlDetectorOptions
import com.vitorpamplona.quartz.encoders.Dimension
import com.vitorpamplona.quartz.encoders.Nip30CustomEmoji
import com.vitorpamplona.quartz.encoders.Nip54InlineMetadata
import com.vitorpamplona.quartz.encoders.Nip92MediaAttachments
import com.vitorpamplona.quartz.events.Dimension
import com.vitorpamplona.quartz.events.FileHeaderEvent
import com.vitorpamplona.quartz.events.ImmutableListOfLists
import kotlinx.collections.immutable.ImmutableList
@@ -50,7 +50,7 @@ class RichTextParser {
callbackUri: String? = null,
): MediaUrlContent? {
val frags = Nip54InlineMetadata().parse(fullUrl)
val tags = Nip92MediaAttachments().parse(fullUrl, eventTags.lists)
val tags = Nip92MediaAttachments.parse(fullUrl, eventTags.lists)
val contentType = frags[FileHeaderEvent.MIME_TYPE] ?: tags[FileHeaderEvent.MIME_TYPE]