Migrates Video feed new posts from NIP-94 to Video and Picture events.

This commit is contained in:
Vitor Pamplona 2024-11-19 17:54:51 -05:00
parent d33421fce4
commit 03662b9d1b
12 changed files with 248 additions and 108 deletions

View File

@ -30,6 +30,7 @@ import androidx.lifecycle.switchMap
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.service.FileHeader import com.vitorpamplona.amethyst.service.FileHeader
import com.vitorpamplona.amethyst.service.LocationState import com.vitorpamplona.amethyst.service.LocationState
import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource
@ -94,6 +95,7 @@ import com.vitorpamplona.quartz.events.NIP17Factory
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryRequestEvent import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryRequestEvent
import com.vitorpamplona.quartz.events.OtsEvent import com.vitorpamplona.quartz.events.OtsEvent
import com.vitorpamplona.quartz.events.PeopleListEvent import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.events.PictureEvent
import com.vitorpamplona.quartz.events.PollNoteEvent import com.vitorpamplona.quartz.events.PollNoteEvent
import com.vitorpamplona.quartz.events.Price import com.vitorpamplona.quartz.events.Price
import com.vitorpamplona.quartz.events.PrivateDmEvent import com.vitorpamplona.quartz.events.PrivateDmEvent
@ -110,6 +112,8 @@ import com.vitorpamplona.quartz.events.StatusEvent
import com.vitorpamplona.quartz.events.TextNoteEvent import com.vitorpamplona.quartz.events.TextNoteEvent
import com.vitorpamplona.quartz.events.TextNoteModificationEvent import com.vitorpamplona.quartz.events.TextNoteModificationEvent
import com.vitorpamplona.quartz.events.TorrentCommentEvent import com.vitorpamplona.quartz.events.TorrentCommentEvent
import com.vitorpamplona.quartz.events.VideoHorizontalEvent
import com.vitorpamplona.quartz.events.VideoVerticalEvent
import com.vitorpamplona.quartz.events.WrappedEvent import com.vitorpamplona.quartz.events.WrappedEvent
import com.vitorpamplona.quartz.events.ZapSplitSetup import com.vitorpamplona.quartz.events.ZapSplitSetup
import com.vitorpamplona.quartz.signers.NostrSigner import com.vitorpamplona.quartz.signers.NostrSigner
@ -1855,12 +1859,12 @@ class Account(
} }
fun sendHeader( fun sendHeader(
signedEvent: FileHeaderEvent, signedEvent: Event,
relayList: List<RelaySetupInfo>, relayList: List<RelaySetupInfo>,
onReady: (Note) -> Unit, onReady: (Note) -> Unit,
) { ) {
Client.send(signedEvent, relayList = relayList) Client.send(signedEvent, relayList = relayList)
LocalCache.consume(signedEvent, null) LocalCache.justConsume(signedEvent, null)
LocalCache.getNoteIfExists(signedEvent.id)?.let { onReady(it) } LocalCache.getNoteIfExists(signedEvent.id)?.let { onReady(it) }
} }
@ -1894,7 +1898,7 @@ class Account(
} }
fun sendHeader( fun sendHeader(
imageUrl: String, url: String,
magnetUri: String?, magnetUri: String?,
headerInfo: FileHeader, headerInfo: FileHeader,
alt: String?, alt: String?,
@ -1905,8 +1909,26 @@ class Account(
) { ) {
if (!isWriteable()) return if (!isWriteable()) return
FileHeaderEvent.create( val isImage = headerInfo.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(url)
url = imageUrl, val isVideo = headerInfo.mimeType?.startsWith("video/") == true || RichTextParser.isVideoUrl(url)
if (isImage) {
PictureEvent.create(
url = url,
mimeType = headerInfo.mimeType,
hash = headerInfo.hash,
size = headerInfo.size.toLong(),
dimensions = headerInfo.dim,
blurhash = headerInfo.blurHash,
alt = alt,
signer = signer,
) { event ->
sendHeader(event, relayList = relayList, onReady)
}
} else if (isVideo && headerInfo.dim != null) {
if (headerInfo.dim.height > headerInfo.dim.width) {
VideoVerticalEvent.create(
url = url,
magnetUri = magnetUri, magnetUri = magnetUri,
mimeType = headerInfo.mimeType, mimeType = headerInfo.mimeType,
hash = headerInfo.hash, hash = headerInfo.hash,
@ -1920,6 +1942,40 @@ class Account(
) { event -> ) { event ->
sendHeader(event, relayList = relayList, onReady) sendHeader(event, relayList = relayList, onReady)
} }
} else {
VideoHorizontalEvent.create(
url = url,
magnetUri = magnetUri,
mimeType = headerInfo.mimeType,
hash = headerInfo.hash,
size = headerInfo.size.toString(),
dimensions = headerInfo.dim,
blurhash = headerInfo.blurHash,
alt = alt,
originalHash = originalHash,
sensitiveContent = sensitiveContent,
signer = signer,
) { event ->
sendHeader(event, relayList = relayList, onReady)
}
}
} else {
FileHeaderEvent.create(
url = url,
magnetUri = magnetUri,
mimeType = headerInfo.mimeType,
hash = headerInfo.hash,
size = headerInfo.size.toString(),
dimensions = headerInfo.dim,
blurhash = headerInfo.blurHash,
alt = alt,
originalHash = originalHash,
sensitiveContent = sensitiveContent,
signer = signer,
) { event ->
sendHeader(event, relayList = relayList, onReady)
}
}
} }
fun sendClassifieds( fun sendClassifieds(

View File

@ -31,6 +31,7 @@ import android.util.Log
import com.vitorpamplona.amethyst.ui.actions.ImageDownloader import com.vitorpamplona.amethyst.ui.actions.ImageDownloader
import com.vitorpamplona.quartz.crypto.CryptoUtils import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.encoders.toHexKey import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.Dimension
import io.trbl.blurhash.BlurHash import io.trbl.blurhash.BlurHash
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import java.io.IOException import java.io.IOException
@ -40,14 +41,14 @@ class FileHeader(
val mimeType: String?, val mimeType: String?,
val hash: String, val hash: String,
val size: Int, val size: Int,
val dim: String?, val dim: Dimension?,
val blurHash: String?, val blurHash: String?,
) { ) {
companion object { companion object {
suspend fun prepare( suspend fun prepare(
fileUrl: String, fileUrl: String,
mimeType: String?, mimeType: String?,
dimPrecomputed: String?, dimPrecomputed: Dimension?,
forceProxy: Boolean, forceProxy: Boolean,
onReady: (FileHeader) -> Unit, onReady: (FileHeader) -> Unit,
onError: (String) -> Unit, onError: (String) -> Unit,
@ -70,7 +71,7 @@ class FileHeader(
fun prepare( fun prepare(
data: ByteArray, data: ByteArray,
mimeType: String?, mimeType: String?,
dimPrecomputed: String?, dimPrecomputed: Dimension?,
onReady: (FileHeader) -> Unit, onReady: (FileHeader) -> Unit,
onError: (String) -> Unit, onError: (String) -> Unit,
) { ) {
@ -95,7 +96,7 @@ class FileHeader(
mBitmap.height, mBitmap.height,
) )
val dim = "${mBitmap.width}x${mBitmap.height}" val dim = Dimension(mBitmap.width, mBitmap.height)
val aspectRatio = (mBitmap.width).toFloat() / (mBitmap.height).toFloat() val aspectRatio = (mBitmap.width).toFloat() / (mBitmap.height).toFloat()
@ -166,7 +167,7 @@ class FileHeader(
} }
} }
if (newDim != "0x0") { if (newDim?.hasSize() == true) {
Pair(blurhash, newDim) Pair(blurhash, newDim)
} else { } else {
Pair(blurhash, null) Pair(blurhash, null)
@ -212,11 +213,15 @@ fun MediaMetadataRetriever.getThumbnail(): Bitmap? {
} }
} }
fun MediaMetadataRetriever.prepareDimFromVideo(): String? { fun MediaMetadataRetriever.prepareDimFromVideo(): Dimension? {
val width = prepareVideoWidth() ?: return null val width = prepareVideoWidth() ?: return null
val height = prepareVideoHeight() ?: return null val height = prepareVideoHeight() ?: return null
return "${width}x$height" return if (width > 0 && height > 0) {
Dimension(width, height)
} else {
null
}
} }
fun MediaMetadataRetriever.prepareVideoWidth(): Int? { fun MediaMetadataRetriever.prepareVideoWidth(): Int? {

View File

@ -45,6 +45,7 @@ import com.vitorpamplona.amethyst.ui.components.MediaCompressor
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.ammolite.relays.RelaySetupInfo import com.vitorpamplona.ammolite.relays.RelaySetupInfo
import com.vitorpamplona.quartz.events.Dimension
import com.vitorpamplona.quartz.events.FileHeaderEvent import com.vitorpamplona.quartz.events.FileHeaderEvent
import com.vitorpamplona.quartz.events.FileStorageEvent import com.vitorpamplona.quartz.events.FileStorageEvent
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
@ -341,6 +342,7 @@ open class EditPostViewModel : ViewModel() {
?.firstOrNull { it.size > 1 && it[0] == "dim" } ?.firstOrNull { it.size > 1 && it[0] == "dim" }
?.get(1) ?.get(1)
?.ifBlank { null } ?.ifBlank { null }
?.let { Dimension.parse(it) }
val magnet = val magnet =
uploadingResult.tags uploadingResult.tags
?.firstOrNull { it.size > 1 && it[0] == "magnet" } ?.firstOrNull { it.size > 1 && it[0] == "magnet" }

View File

@ -37,6 +37,7 @@ import com.vitorpamplona.amethyst.service.Nip96Uploader
import com.vitorpamplona.amethyst.ui.components.MediaCompressor import com.vitorpamplona.amethyst.ui.components.MediaCompressor
import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.ammolite.relays.RelaySetupInfo import com.vitorpamplona.ammolite.relays.RelaySetupInfo
import com.vitorpamplona.quartz.events.Dimension
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -217,6 +218,7 @@ open class NewMediaModel : ViewModel() {
?.firstOrNull { it.size > 1 && it[0] == "dim" } ?.firstOrNull { it.size > 1 && it[0] == "dim" }
?.get(1) ?.get(1)
?.ifBlank { null } ?.ifBlank { null }
?.let { Dimension.parse(it) }
val magnet = val magnet =
uploadingResult.tags uploadingResult.tags
?.firstOrNull { it.size > 1 && it[0] == "magnet" } ?.firstOrNull { it.size > 1 && it[0] == "magnet" }

View File

@ -61,6 +61,7 @@ import com.vitorpamplona.quartz.events.ChatMessageEvent
import com.vitorpamplona.quartz.events.ClassifiedsEvent import com.vitorpamplona.quartz.events.ClassifiedsEvent
import com.vitorpamplona.quartz.events.CommentEvent import com.vitorpamplona.quartz.events.CommentEvent
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
import com.vitorpamplona.quartz.events.Dimension
import com.vitorpamplona.quartz.events.DraftEvent import com.vitorpamplona.quartz.events.DraftEvent
import com.vitorpamplona.quartz.events.Event import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.FileHeaderEvent import com.vitorpamplona.quartz.events.FileHeaderEvent
@ -1205,6 +1206,7 @@ open class NewPostViewModel : ViewModel() {
?.firstOrNull { it.size > 1 && it[0] == "dim" } ?.firstOrNull { it.size > 1 && it[0] == "dim" }
?.get(1) ?.get(1)
?.ifBlank { null } ?.ifBlank { null }
?.let { Dimension.parse(it) }
val magnet = val magnet =
uploadingResult.tags uploadingResult.tags
?.firstOrNull { it.size > 1 && it[0] == "magnet" } ?.firstOrNull { it.size > 1 && it[0] == "magnet" }

View File

@ -60,7 +60,7 @@ data class ATag(
TlvBuilder() TlvBuilder()
.apply { .apply {
addString(Nip19Bech32.TlvTypes.SPECIAL, dTag) addString(Nip19Bech32.TlvTypes.SPECIAL, dTag)
addStringIfNotNull(Nip19Bech32.TlvTypes.RELAY, overrideRelay) addStringIfNotNull(Nip19Bech32.TlvTypes.RELAY, overrideRelay ?: relay)
addHex(Nip19Bech32.TlvTypes.AUTHOR, pubKeyHex) addHex(Nip19Bech32.TlvTypes.AUTHOR, pubKeyHex)
addInt(Nip19Bech32.TlvTypes.KIND, kind) addInt(Nip19Bech32.TlvTypes.KIND, kind)
}.build() }.build()

View File

@ -81,7 +81,7 @@ class FileHeaderEvent(
alt: String? = null, alt: String? = null,
hash: String? = null, hash: String? = null,
size: String? = null, size: String? = null,
dimensions: String? = null, dimensions: Dimension? = null,
blurhash: String? = null, blurhash: String? = null,
originalHash: String? = null, originalHash: String? = null,
magnetURI: String? = null, magnetURI: String? = null,
@ -99,7 +99,7 @@ class FileHeaderEvent(
alt?.ifBlank { null }?.let { arrayOf(ALT, it) } ?: arrayOf("alt", ALT_DESCRIPTION), alt?.ifBlank { null }?.let { arrayOf(ALT, it) } ?: arrayOf("alt", ALT_DESCRIPTION),
hash?.let { arrayOf(HASH, it) }, hash?.let { arrayOf(HASH, it) },
size?.let { arrayOf(FILE_SIZE, it) }, size?.let { arrayOf(FILE_SIZE, it) },
dimensions?.let { arrayOf(DIMENSION, it) }, dimensions?.let { arrayOf(DIMENSION, it.toString()) },
blurhash?.let { arrayOf(BLUR_HASH, it) }, blurhash?.let { arrayOf(BLUR_HASH, it) },
originalHash?.let { arrayOf(ORIGINAL_HASH, it) }, originalHash?.let { arrayOf(ORIGINAL_HASH, it) },
magnetURI?.let { arrayOf(MAGNET_URI, it) }, magnetURI?.let { arrayOf(MAGNET_URI, it) },

View File

@ -74,7 +74,7 @@ class FileStorageHeaderEvent(
alt: String? = null, alt: String? = null,
hash: String? = null, hash: String? = null,
size: String? = null, size: String? = null,
dimensions: String? = null, dimensions: Dimension? = null,
blurhash: String? = null, blurhash: String? = null,
magnetURI: String? = null, magnetURI: String? = null,
torrentInfoHash: String? = null, torrentInfoHash: String? = null,
@ -90,7 +90,7 @@ class FileStorageHeaderEvent(
hash?.let { arrayOf(HASH, it) }, hash?.let { arrayOf(HASH, it) },
alt?.let { arrayOf(ALT, it) } ?: arrayOf("alt", ALT_DESCRIPTION), alt?.let { arrayOf(ALT, it) } ?: arrayOf("alt", ALT_DESCRIPTION),
size?.let { arrayOf(FILE_SIZE, it) }, size?.let { arrayOf(FILE_SIZE, it) },
dimensions?.let { arrayOf(DIMENSION, it) }, dimensions?.let { arrayOf(DIMENSION, it.toString()) },
blurhash?.let { arrayOf(BLUR_HASH, it) }, blurhash?.let { arrayOf(BLUR_HASH, it) },
magnetURI?.let { arrayOf(MAGNET_URI, it) }, magnetURI?.let { arrayOf(MAGNET_URI, it) },
torrentInfoHash?.let { arrayOf(TORRENT_INFOHASH, it) }, torrentInfoHash?.let { arrayOf(TORRENT_INFOHASH, it) },

View File

@ -24,16 +24,8 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.ATag import com.vitorpamplona.quartz.encoders.ATag
import com.vitorpamplona.quartz.encoders.ETag import com.vitorpamplona.quartz.encoders.ETag
import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.Nip92MediaAttachments
import com.vitorpamplona.quartz.encoders.Nip92MediaAttachments.Companion.IMETA import com.vitorpamplona.quartz.encoders.Nip92MediaAttachments.Companion.IMETA
import com.vitorpamplona.quartz.encoders.PTag import com.vitorpamplona.quartz.encoders.PTag
import com.vitorpamplona.quartz.events.FileHeaderEvent.Companion.ALT
import com.vitorpamplona.quartz.events.FileHeaderEvent.Companion.BLUR_HASH
import com.vitorpamplona.quartz.events.FileHeaderEvent.Companion.DIMENSION
import com.vitorpamplona.quartz.events.FileHeaderEvent.Companion.FILE_SIZE
import com.vitorpamplona.quartz.events.FileHeaderEvent.Companion.MAGNET_URI
import com.vitorpamplona.quartz.events.FileHeaderEvent.Companion.TORRENT_INFOHASH
import com.vitorpamplona.quartz.events.FileHeaderEvent.Companion.URL
import com.vitorpamplona.quartz.signers.NostrSigner import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.TimeUtils
import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.cancellation.CancellationException
@ -53,27 +45,23 @@ class PictureEvent(
fun title() = tags.firstOrNull { it.size > 1 && it[0] == TITLE }?.get(1) fun title() = tags.firstOrNull { it.size > 1 && it[0] == TITLE }?.get(1)
fun url() = tags.firstOrNull { it.size > 1 && it[0] == URL }?.get(1) fun url() = tags.firstOrNull { it.size > 1 && it[0] == PictureMeta.URL }?.get(1)
fun urls() = tags.filter { it.size > 1 && it[0] == URL }.map { it[1] } fun urls() = tags.filter { it.size > 1 && it[0] == PictureMeta.URL }.map { it[1] }
fun mimeType() = tags.firstOrNull { it.size > 1 && it[0] == FileHeaderEvent.MIME_TYPE }?.get(1) fun mimeType() = tags.firstOrNull { it.size > 1 && it[0] == PictureMeta.MIME_TYPE }?.get(1)
fun hash() = tags.firstOrNull { it.size > 1 && it[0] == FileHeaderEvent.HASH }?.get(1) fun hash() = tags.firstOrNull { it.size > 1 && it[0] == PictureMeta.HASH }?.get(1)
fun size() = tags.firstOrNull { it.size > 1 && it[0] == FILE_SIZE }?.get(1) fun size() = tags.firstOrNull { it.size > 1 && it[0] == PictureMeta.FILE_SIZE }?.get(1)
fun alt() = tags.firstOrNull { it.size > 1 && it[0] == ALT }?.get(1) fun alt() = tags.firstOrNull { it.size > 1 && it[0] == PictureMeta.ALT }?.get(1)
fun dimensions() = tags.firstOrNull { it.size > 1 && it[0] == DIMENSION }?.get(1)?.let { Dimension.parse(it) } fun dimensions() = tags.firstOrNull { it.size > 1 && it[0] == PictureMeta.DIMENSION }?.get(1)?.let { Dimension.parse(it) }
fun magnetURI() = tags.firstOrNull { it.size > 1 && it[0] == MAGNET_URI }?.get(1) fun blurhash() = tags.firstOrNull { it.size > 1 && it[0] == PictureMeta.BLUR_HASH }?.get(1)
fun torrentInfoHash() = tags.firstOrNull { it.size > 1 && it[0] == TORRENT_INFOHASH }?.get(1) fun hasUrl() = tags.any { it.size > 1 && it[0] == PictureMeta.URL }
fun blurhash() = tags.firstOrNull { it.size > 1 && it[0] == BLUR_HASH }?.get(1)
fun hasUrl() = tags.any { it.size > 1 && it[0] == URL }
// hack to fix pablo's bug // hack to fix pablo's bug
fun rootImage() = fun rootImage() =
@ -104,32 +92,89 @@ class PictureEvent(
companion object { companion object {
const val KIND = 20 const val KIND = 20
const val ALT_DESCRIPTION = "Picture" const val ALT_DESCRIPTION = "List of pictures"
private const val MIME_TYPE = "m" private const val MIME_TYPE = "m"
private const val HASH = "x" private const val HASH = "x"
private const val TITLE = "title" private const val TITLE = "title"
private fun create( fun create(
msg: String, url: String,
tags: MutableList<Array<String>>, msg: String? = null,
title: String? = null,
mimeType: String? = null,
alt: String? = null,
hash: String? = null,
size: Long? = null,
dimensions: Dimension? = null,
blurhash: String? = null,
usersMentioned: Set<PTag> = emptySet(), usersMentioned: Set<PTag> = emptySet(),
addressesMentioned: Set<ATag> = emptySet(), addressesMentioned: Set<ATag> = emptySet(),
eventsMentioned: Set<ETag> = emptySet(), eventsMentioned: Set<ETag> = emptySet(),
nip94attachments: List<FileHeaderEvent>? = null,
geohash: String? = null, geohash: String? = null,
zapReceiver: List<ZapSplitSetup>? = null, zapReceiver: List<ZapSplitSetup>? = null,
markAsSensitive: Boolean = false, markAsSensitive: Boolean = false,
zapRaiserAmount: Long? = null, zapRaiserAmount: Long? = null,
isDraft: Boolean,
signer: NostrSigner, signer: NostrSigner,
createdAt: Long = TimeUtils.now(), createdAt: Long = TimeUtils.now(),
onReady: (PictureEvent) -> Unit, onReady: (PictureEvent) -> Unit,
) { ) {
val image =
PictureMeta(
url,
mimeType,
blurhash,
dimensions,
alt,
hash,
size,
emptyList(),
emptyList(),
)
create(listOf(image), msg, title, usersMentioned, addressesMentioned, eventsMentioned, geohash, zapReceiver, markAsSensitive, zapRaiserAmount, signer, createdAt, onReady)
}
fun create(
images: List<PictureMeta>,
msg: String? = null,
title: String? = null,
usersMentioned: Set<PTag> = emptySet(),
addressesMentioned: Set<ATag> = emptySet(),
eventsMentioned: Set<ETag> = emptySet(),
geohash: String? = null,
zapReceiver: List<ZapSplitSetup>? = null,
markAsSensitive: Boolean = false,
zapRaiserAmount: Long? = null,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (PictureEvent) -> Unit,
) {
val tags = mutableListOf(arrayOf<String>("alt", ALT_DESCRIPTION))
images.forEach {
tags.add(it.toIMetaArray())
}
title?.let { tags.add(arrayOf("title", it)) }
images.distinctBy { it.hash }.forEach {
if (it.hash != null) {
tags.add(arrayOf("x", it.hash))
}
}
images.distinctBy { it.mimeType }.forEach {
if (it.mimeType != null) {
tags.add(arrayOf("m", it.mimeType))
}
}
usersMentioned.forEach { tags.add(it.toPTagArray()) } usersMentioned.forEach { tags.add(it.toPTagArray()) }
addressesMentioned.forEach { tags.add(it.toQTagArray()) } addressesMentioned.forEach { tags.add(it.toQTagArray()) }
eventsMentioned.forEach { tags.add(it.toQTagArray()) } eventsMentioned.forEach { tags.add(it.toQTagArray()) }
if (msg != null) {
findHashtags(msg).forEach { findHashtags(msg).forEach {
val lowercaseTag = it.lowercase() val lowercaseTag = it.lowercase()
tags.add(arrayOf("t", it)) tags.add(arrayOf("t", it))
@ -139,6 +184,7 @@ class PictureEvent(
} }
findURLs(msg).forEach { tags.add(arrayOf("r", it)) } findURLs(msg).forEach { tags.add(arrayOf("r", it)) }
}
zapReceiver?.forEach { zapReceiver?.forEach {
tags.add(arrayOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString())) tags.add(arrayOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
@ -148,19 +194,8 @@ class PictureEvent(
} }
zapRaiserAmount?.let { tags.add(arrayOf("zapraiser", "$it")) } zapRaiserAmount?.let { tags.add(arrayOf("zapraiser", "$it")) }
geohash?.let { tags.addAll(geohashMipMap(it)) } geohash?.let { tags.addAll(geohashMipMap(it)) }
nip94attachments?.let {
it.forEach {
Nip92MediaAttachments().convertFromFileHeader(it)?.let {
tags.add(it)
}
}
}
if (isDraft) { signer.sign(createdAt, KIND, tags.toTypedArray(), msg ?: "", onReady)
signer.assembleRumor(createdAt, KIND, tags.toTypedArray(), msg, onReady)
} else {
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
}
} }
} }
} }
@ -176,7 +211,33 @@ class PictureMeta(
val fallback: List<String>, val fallback: List<String>,
val annotations: List<UserAnnotation>, val annotations: List<UserAnnotation>,
) { ) {
fun toIMetaArray(): Array<String> =
(
listOfNotNull(
"imeta",
"$URL $url",
mimeType?.let { "$MIME_TYPE $it" },
alt?.let { "$ALT $it" },
hash?.let { "$HASH $it" },
size?.let { "$FILE_SIZE $it" },
dimension?.let { "$DIMENSION $it" },
blurhash?.let { "$BLUR_HASH $it" },
) +
fallback.map { "$FALLBACK $it" } +
annotations.map { "$ANNOTATIONS $it" }
).toTypedArray()
companion object { companion object {
const val URL = "url"
const val MIME_TYPE = "m"
const val FILE_SIZE = "size"
const val DIMENSION = "dim"
const val HASH = "x"
const val BLUR_HASH = "blurhash"
const val ALT = "alt"
const val FALLBACK = "fallback"
const val ANNOTATIONS = "annotate-user"
fun parse(tagArray: Array<String>): PictureMeta? { fun parse(tagArray: Array<String>): PictureMeta? {
var url: String? = null var url: String? = null
var mimeType: String? = null var mimeType: String? = null
@ -188,9 +249,9 @@ class PictureMeta(
val fallback = mutableListOf<String>() val fallback = mutableListOf<String>()
val annotations = mutableListOf<UserAnnotation>() val annotations = mutableListOf<UserAnnotation>()
if (tagArray.size == 2 && tagArray[1].contains("url") && (tagArray[1].contains("blurhash") || tagArray[1].contains("size"))) { if (tagArray.size == 2 && tagArray[1].contains(URL) && (tagArray[1].contains(BLUR_HASH) || tagArray[1].contains(FILE_SIZE))) {
// hack to fix pablo's bug // hack to fix pablo's bug
val keys = setOf("url", "m", "blurhash", "dim", "alt", "x", "size", "fallback", "annotate-user") val keys = setOf(URL, MIME_TYPE, BLUR_HASH, DIMENSION, ALT, HASH, FILE_SIZE, FALLBACK, ANNOTATIONS)
var keyNextValue: String? = null var keyNextValue: String? = null
val values = mutableListOf<String>() val values = mutableListOf<String>()
@ -198,15 +259,15 @@ class PictureMeta(
if (it in keys) { if (it in keys) {
if (keyNextValue != null && values.isNotEmpty()) { if (keyNextValue != null && values.isNotEmpty()) {
when (keyNextValue) { when (keyNextValue) {
"url" -> url = values.joinToString(" ") URL -> url = values.joinToString(" ")
"m" -> mimeType = values.joinToString(" ") MIME_TYPE -> mimeType = values.joinToString(" ")
"blurhash" -> blurhash = values.joinToString(" ") BLUR_HASH -> blurhash = values.joinToString(" ")
"dim" -> dim = Dimension.parse(values.joinToString(" ")) DIMENSION -> dim = Dimension.parse(values.joinToString(" "))
"alt" -> alt = values.joinToString(" ") ALT -> alt = values.joinToString(" ")
"x" -> hash = values.joinToString(" ") HASH -> hash = values.joinToString(" ")
"size" -> size = values.joinToString(" ").toLongOrNull() FILE_SIZE -> size = values.joinToString(" ").toLongOrNull()
"fallback" -> fallback.add(values.joinToString(" ")) FALLBACK -> fallback.add(values.joinToString(" "))
"annotate-user" -> { ANNOTATIONS -> {
UserAnnotation.parse(values.joinToString(" "))?.let { UserAnnotation.parse(values.joinToString(" "))?.let {
annotations.add(it) annotations.add(it)
} }
@ -222,15 +283,15 @@ class PictureMeta(
if (keyNextValue != null && values.isNotEmpty()) { if (keyNextValue != null && values.isNotEmpty()) {
when (keyNextValue) { when (keyNextValue) {
"url" -> url = values.joinToString(" ") URL -> url = values.joinToString(" ")
"m" -> mimeType = values.joinToString(" ") MIME_TYPE -> mimeType = values.joinToString(" ")
"blurhash" -> blurhash = values.joinToString(" ") BLUR_HASH -> blurhash = values.joinToString(" ")
"dim" -> dim = Dimension.parse(values.joinToString(" ")) DIMENSION -> dim = Dimension.parse(values.joinToString(" "))
"alt" -> alt = values.joinToString(" ") ALT -> alt = values.joinToString(" ")
"x" -> hash = values.joinToString(" ") HASH -> hash = values.joinToString(" ")
"size" -> size = values.joinToString(" ").toLongOrNull() FILE_SIZE -> size = values.joinToString(" ").toLongOrNull()
"fallback" -> fallback.add(values.joinToString(" ")) FALLBACK -> fallback.add(values.joinToString(" "))
"annotate-user" -> { ANNOTATIONS -> {
UserAnnotation.parse(values.joinToString(" "))?.let { UserAnnotation.parse(values.joinToString(" "))?.let {
annotations.add(it) annotations.add(it)
} }
@ -247,15 +308,15 @@ class PictureMeta(
if (value.isNotBlank()) { if (value.isNotBlank()) {
when (key) { when (key) {
"url" -> url = value URL -> url = value
"m" -> mimeType = value MIME_TYPE -> mimeType = value
"blurhash" -> blurhash = value BLUR_HASH -> blurhash = value
"dim" -> dim = Dimension.parse(value) DIMENSION -> dim = Dimension.parse(value)
"alt" -> alt = value ALT -> alt = value
"x" -> hash = value HASH -> hash = value
"size" -> size = value.toLongOrNull() FILE_SIZE -> size = value.toLongOrNull()
"fallback" -> fallback.add(value) FALLBACK -> fallback.add(value)
"annotate-user" -> { ANNOTATIONS -> {
UserAnnotation.parse(value)?.let { UserAnnotation.parse(value)?.let {
annotations.add(it) annotations.add(it)
} }
@ -278,6 +339,8 @@ class Dimension(
) { ) {
fun aspectRatio() = width.toFloat() / height.toFloat() fun aspectRatio() = width.toFloat() / height.toFloat()
fun hasSize() = width > 0 && height > 0
override fun toString() = "${width}x$height" override fun toString() = "${width}x$height"
companion object { companion object {
@ -305,6 +368,8 @@ class UserAnnotation(
val x: Int, val x: Int,
val y: Int, val y: Int,
) { ) {
override fun toString() = "$pubkey:$x:$y"
companion object { companion object {
fun parse(value: String): UserAnnotation? { fun parse(value: String): UserAnnotation? {
val ann = value.split(":") val ann = value.split(":")

View File

@ -88,13 +88,14 @@ abstract class VideoEvent(
fun <T : VideoEvent> create( fun <T : VideoEvent> create(
kind: Int, kind: Int,
dTag: String,
url: String, url: String,
magnetUri: String? = null, magnetUri: String? = null,
mimeType: String? = null, mimeType: String? = null,
alt: String? = null, alt: String? = null,
hash: String? = null, hash: String? = null,
size: String? = null, size: String? = null,
dimensions: String? = null, dimensions: Dimension? = null,
blurhash: String? = null, blurhash: String? = null,
originalHash: String? = null, originalHash: String? = null,
magnetURI: String? = null, magnetURI: String? = null,
@ -107,13 +108,14 @@ abstract class VideoEvent(
) { ) {
val tags = val tags =
listOfNotNull( listOfNotNull(
arrayOf("d", dTag),
arrayOf(URL, url), arrayOf(URL, url),
magnetUri?.let { arrayOf(MAGNET_URI, it) }, magnetUri?.let { arrayOf(MAGNET_URI, it) },
mimeType?.let { arrayOf(MIME_TYPE, it) }, mimeType?.let { arrayOf(MIME_TYPE, it) },
alt?.ifBlank { null }?.let { arrayOf(ALT, it) } ?: arrayOf("alt", altDescription), alt?.ifBlank { null }?.let { arrayOf(ALT, it) } ?: arrayOf("alt", altDescription),
hash?.let { arrayOf(HASH, it) }, hash?.let { arrayOf(HASH, it) },
size?.let { arrayOf(FILE_SIZE, it) }, size?.let { arrayOf(FILE_SIZE, it) },
dimensions?.let { arrayOf(DIMENSION, it) }, dimensions?.let { arrayOf(DIMENSION, it.toString()) },
blurhash?.let { arrayOf(BLUR_HASH, it) }, blurhash?.let { arrayOf(BLUR_HASH, it) },
originalHash?.let { arrayOf(ORIGINAL_HASH, it) }, originalHash?.let { arrayOf(ORIGINAL_HASH, it) },
magnetURI?.let { arrayOf(MAGNET_URI, it) }, magnetURI?.let { arrayOf(MAGNET_URI, it) },

View File

@ -24,6 +24,7 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.TimeUtils
import java.util.UUID
@Immutable @Immutable
class VideoHorizontalEvent( class VideoHorizontalEvent(
@ -45,18 +46,20 @@ class VideoHorizontalEvent(
alt: String? = null, alt: String? = null,
hash: String? = null, hash: String? = null,
size: String? = null, size: String? = null,
dimensions: String? = null, dimensions: Dimension? = null,
blurhash: String? = null, blurhash: String? = null,
originalHash: String? = null, originalHash: String? = null,
magnetURI: String? = null, magnetURI: String? = null,
torrentInfoHash: String? = null, torrentInfoHash: String? = null,
sensitiveContent: Boolean? = null, sensitiveContent: Boolean? = null,
dTag: String = UUID.randomUUID().toString(),
signer: NostrSigner, signer: NostrSigner,
createdAt: Long = TimeUtils.now(), createdAt: Long = TimeUtils.now(),
onReady: (VideoHorizontalEvent) -> Unit, onReady: (VideoHorizontalEvent) -> Unit,
) { ) {
create( create(
KIND, KIND,
dTag,
url, url,
magnetUri, magnetUri,
mimeType, mimeType,

View File

@ -24,6 +24,7 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.signers.NostrSigner import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils import com.vitorpamplona.quartz.utils.TimeUtils
import java.util.UUID
@Immutable @Immutable
class VideoVerticalEvent( class VideoVerticalEvent(
@ -45,18 +46,20 @@ class VideoVerticalEvent(
alt: String? = null, alt: String? = null,
hash: String? = null, hash: String? = null,
size: String? = null, size: String? = null,
dimensions: String? = null, dimensions: Dimension? = null,
blurhash: String? = null, blurhash: String? = null,
originalHash: String? = null, originalHash: String? = null,
magnetURI: String? = null, magnetURI: String? = null,
torrentInfoHash: String? = null, torrentInfoHash: String? = null,
sensitiveContent: Boolean? = null, sensitiveContent: Boolean? = null,
dTag: String = UUID.randomUUID().toString(),
signer: NostrSigner, signer: NostrSigner,
createdAt: Long = TimeUtils.now(), createdAt: Long = TimeUtils.now(),
onReady: (VideoVerticalEvent) -> Unit, onReady: (VideoVerticalEvent) -> Unit,
) { ) {
create( create(
KIND, KIND,
dTag,
url, url,
magnetUri, magnetUri,
mimeType, mimeType,