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.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
import com.vitorpamplona.amethyst.service.FileHeader
import com.vitorpamplona.amethyst.service.LocationState
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.OtsEvent
import com.vitorpamplona.quartz.events.PeopleListEvent
import com.vitorpamplona.quartz.events.PictureEvent
import com.vitorpamplona.quartz.events.PollNoteEvent
import com.vitorpamplona.quartz.events.Price
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.TextNoteModificationEvent
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.ZapSplitSetup
import com.vitorpamplona.quartz.signers.NostrSigner
@ -1855,12 +1859,12 @@ class Account(
}
fun sendHeader(
signedEvent: FileHeaderEvent,
signedEvent: Event,
relayList: List<RelaySetupInfo>,
onReady: (Note) -> Unit,
) {
Client.send(signedEvent, relayList = relayList)
LocalCache.consume(signedEvent, null)
LocalCache.justConsume(signedEvent, null)
LocalCache.getNoteIfExists(signedEvent.id)?.let { onReady(it) }
}
@ -1894,7 +1898,7 @@ class Account(
}
fun sendHeader(
imageUrl: String,
url: String,
magnetUri: String?,
headerInfo: FileHeader,
alt: String?,
@ -1905,20 +1909,72 @@ class Account(
) {
if (!isWriteable()) return
FileHeaderEvent.create(
url = imageUrl,
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)
val isImage = headerInfo.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(url)
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,
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 {
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)
}
}
}

View File

@ -31,6 +31,7 @@ import android.util.Log
import com.vitorpamplona.amethyst.ui.actions.ImageDownloader
import com.vitorpamplona.quartz.crypto.CryptoUtils
import com.vitorpamplona.quartz.encoders.toHexKey
import com.vitorpamplona.quartz.events.Dimension
import io.trbl.blurhash.BlurHash
import kotlinx.coroutines.CancellationException
import java.io.IOException
@ -40,14 +41,14 @@ class FileHeader(
val mimeType: String?,
val hash: String,
val size: Int,
val dim: String?,
val dim: Dimension?,
val blurHash: String?,
) {
companion object {
suspend fun prepare(
fileUrl: String,
mimeType: String?,
dimPrecomputed: String?,
dimPrecomputed: Dimension?,
forceProxy: Boolean,
onReady: (FileHeader) -> Unit,
onError: (String) -> Unit,
@ -70,7 +71,7 @@ class FileHeader(
fun prepare(
data: ByteArray,
mimeType: String?,
dimPrecomputed: String?,
dimPrecomputed: Dimension?,
onReady: (FileHeader) -> Unit,
onError: (String) -> Unit,
) {
@ -95,7 +96,7 @@ class FileHeader(
mBitmap.height,
)
val dim = "${mBitmap.width}x${mBitmap.height}"
val dim = Dimension(mBitmap.width, mBitmap.height)
val aspectRatio = (mBitmap.width).toFloat() / (mBitmap.height).toFloat()
@ -166,7 +167,7 @@ class FileHeader(
}
}
if (newDim != "0x0") {
if (newDim?.hasSize() == true) {
Pair(blurhash, newDim)
} else {
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 height = prepareVideoHeight() ?: return null
return "${width}x$height"
return if (width > 0 && height > 0) {
Dimension(width, height)
} else {
null
}
}
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.stringRes
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
import com.vitorpamplona.quartz.events.Dimension
import com.vitorpamplona.quartz.events.FileHeaderEvent
import com.vitorpamplona.quartz.events.FileStorageEvent
import com.vitorpamplona.quartz.events.FileStorageHeaderEvent
@ -341,6 +342,7 @@ open class EditPostViewModel : ViewModel() {
?.firstOrNull { it.size > 1 && it[0] == "dim" }
?.get(1)
?.ifBlank { null }
?.let { Dimension.parse(it) }
val magnet =
uploadingResult.tags
?.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.stringRes
import com.vitorpamplona.ammolite.relays.RelaySetupInfo
import com.vitorpamplona.quartz.events.Dimension
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -217,6 +218,7 @@ open class NewMediaModel : ViewModel() {
?.firstOrNull { it.size > 1 && it[0] == "dim" }
?.get(1)
?.ifBlank { null }
?.let { Dimension.parse(it) }
val magnet =
uploadingResult.tags
?.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.CommentEvent
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
import com.vitorpamplona.quartz.events.Dimension
import com.vitorpamplona.quartz.events.DraftEvent
import com.vitorpamplona.quartz.events.Event
import com.vitorpamplona.quartz.events.FileHeaderEvent
@ -1205,6 +1206,7 @@ open class NewPostViewModel : ViewModel() {
?.firstOrNull { it.size > 1 && it[0] == "dim" }
?.get(1)
?.ifBlank { null }
?.let { Dimension.parse(it) }
val magnet =
uploadingResult.tags
?.firstOrNull { it.size > 1 && it[0] == "magnet" }

View File

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

View File

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

View File

@ -74,7 +74,7 @@ class FileStorageHeaderEvent(
alt: String? = null,
hash: String? = null,
size: String? = null,
dimensions: String? = null,
dimensions: Dimension? = null,
blurhash: String? = null,
magnetURI: String? = null,
torrentInfoHash: String? = null,
@ -90,7 +90,7 @@ class FileStorageHeaderEvent(
hash?.let { arrayOf(HASH, it) },
alt?.let { arrayOf(ALT, it) } ?: arrayOf("alt", ALT_DESCRIPTION),
size?.let { arrayOf(FILE_SIZE, it) },
dimensions?.let { arrayOf(DIMENSION, it) },
dimensions?.let { arrayOf(DIMENSION, it.toString()) },
blurhash?.let { arrayOf(BLUR_HASH, it) },
magnetURI?.let { arrayOf(MAGNET_URI, 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.ETag
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.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.utils.TimeUtils
import kotlin.coroutines.cancellation.CancellationException
@ -53,27 +45,23 @@ class PictureEvent(
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 blurhash() = tags.firstOrNull { it.size > 1 && it[0] == BLUR_HASH }?.get(1)
fun hasUrl() = tags.any { it.size > 1 && it[0] == URL }
fun hasUrl() = tags.any { it.size > 1 && it[0] == PictureMeta.URL }
// hack to fix pablo's bug
fun rootImage() =
@ -104,41 +92,99 @@ class PictureEvent(
companion object {
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 HASH = "x"
private const val TITLE = "title"
private fun create(
msg: String,
tags: MutableList<Array<String>>,
fun create(
url: 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(),
addressesMentioned: Set<ATag> = emptySet(),
eventsMentioned: Set<ETag> = emptySet(),
nip94attachments: List<FileHeaderEvent>? = null,
geohash: String? = null,
zapReceiver: List<ZapSplitSetup>? = null,
markAsSensitive: Boolean = false,
zapRaiserAmount: Long? = null,
isDraft: Boolean,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
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()) }
addressesMentioned.forEach { tags.add(it.toQTagArray()) }
eventsMentioned.forEach { tags.add(it.toQTagArray()) }
findHashtags(msg).forEach {
val lowercaseTag = it.lowercase()
tags.add(arrayOf("t", it))
if (it != lowercaseTag) {
tags.add(arrayOf("t", it.lowercase()))
if (msg != null) {
findHashtags(msg).forEach {
val lowercaseTag = it.lowercase()
tags.add(arrayOf("t", it))
if (it != lowercaseTag) {
tags.add(arrayOf("t", it.lowercase()))
}
}
}
findURLs(msg).forEach { tags.add(arrayOf("r", it)) }
findURLs(msg).forEach { tags.add(arrayOf("r", it)) }
}
zapReceiver?.forEach {
tags.add(arrayOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
@ -148,19 +194,8 @@ class PictureEvent(
}
zapRaiserAmount?.let { tags.add(arrayOf("zapraiser", "$it")) }
geohash?.let { tags.addAll(geohashMipMap(it)) }
nip94attachments?.let {
it.forEach {
Nip92MediaAttachments().convertFromFileHeader(it)?.let {
tags.add(it)
}
}
}
if (isDraft) {
signer.assembleRumor(createdAt, KIND, tags.toTypedArray(), msg, onReady)
} else {
signer.sign(createdAt, KIND, tags.toTypedArray(), msg, onReady)
}
signer.sign(createdAt, KIND, tags.toTypedArray(), msg ?: "", onReady)
}
}
}
@ -176,7 +211,33 @@ class PictureMeta(
val fallback: List<String>,
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 {
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? {
var url: String? = null
var mimeType: String? = null
@ -188,9 +249,9 @@ class PictureMeta(
val fallback = mutableListOf<String>()
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
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
val values = mutableListOf<String>()
@ -198,15 +259,15 @@ class PictureMeta(
if (it in keys) {
if (keyNextValue != null && values.isNotEmpty()) {
when (keyNextValue) {
"url" -> url = values.joinToString(" ")
"m" -> mimeType = values.joinToString(" ")
"blurhash" -> blurhash = values.joinToString(" ")
"dim" -> dim = Dimension.parse(values.joinToString(" "))
"alt" -> alt = values.joinToString(" ")
"x" -> hash = values.joinToString(" ")
"size" -> size = values.joinToString(" ").toLongOrNull()
"fallback" -> fallback.add(values.joinToString(" "))
"annotate-user" -> {
URL -> url = values.joinToString(" ")
MIME_TYPE -> mimeType = values.joinToString(" ")
BLUR_HASH -> blurhash = values.joinToString(" ")
DIMENSION -> dim = Dimension.parse(values.joinToString(" "))
ALT -> alt = values.joinToString(" ")
HASH -> hash = values.joinToString(" ")
FILE_SIZE -> size = values.joinToString(" ").toLongOrNull()
FALLBACK -> fallback.add(values.joinToString(" "))
ANNOTATIONS -> {
UserAnnotation.parse(values.joinToString(" "))?.let {
annotations.add(it)
}
@ -222,15 +283,15 @@ class PictureMeta(
if (keyNextValue != null && values.isNotEmpty()) {
when (keyNextValue) {
"url" -> url = values.joinToString(" ")
"m" -> mimeType = values.joinToString(" ")
"blurhash" -> blurhash = values.joinToString(" ")
"dim" -> dim = Dimension.parse(values.joinToString(" "))
"alt" -> alt = values.joinToString(" ")
"x" -> hash = values.joinToString(" ")
"size" -> size = values.joinToString(" ").toLongOrNull()
"fallback" -> fallback.add(values.joinToString(" "))
"annotate-user" -> {
URL -> url = values.joinToString(" ")
MIME_TYPE -> mimeType = values.joinToString(" ")
BLUR_HASH -> blurhash = values.joinToString(" ")
DIMENSION -> dim = Dimension.parse(values.joinToString(" "))
ALT -> alt = values.joinToString(" ")
HASH -> hash = values.joinToString(" ")
FILE_SIZE -> size = values.joinToString(" ").toLongOrNull()
FALLBACK -> fallback.add(values.joinToString(" "))
ANNOTATIONS -> {
UserAnnotation.parse(values.joinToString(" "))?.let {
annotations.add(it)
}
@ -247,15 +308,15 @@ class PictureMeta(
if (value.isNotBlank()) {
when (key) {
"url" -> url = value
"m" -> mimeType = value
"blurhash" -> blurhash = value
"dim" -> dim = Dimension.parse(value)
"alt" -> alt = value
"x" -> hash = value
"size" -> size = value.toLongOrNull()
"fallback" -> fallback.add(value)
"annotate-user" -> {
URL -> url = value
MIME_TYPE -> mimeType = value
BLUR_HASH -> blurhash = value
DIMENSION -> dim = Dimension.parse(value)
ALT -> alt = value
HASH -> hash = value
FILE_SIZE -> size = value.toLongOrNull()
FALLBACK -> fallback.add(value)
ANNOTATIONS -> {
UserAnnotation.parse(value)?.let {
annotations.add(it)
}
@ -278,6 +339,8 @@ class Dimension(
) {
fun aspectRatio() = width.toFloat() / height.toFloat()
fun hasSize() = width > 0 && height > 0
override fun toString() = "${width}x$height"
companion object {
@ -305,6 +368,8 @@ class UserAnnotation(
val x: Int,
val y: Int,
) {
override fun toString() = "$pubkey:$x:$y"
companion object {
fun parse(value: String): UserAnnotation? {
val ann = value.split(":")

View File

@ -88,13 +88,14 @@ abstract class VideoEvent(
fun <T : VideoEvent> create(
kind: Int,
dTag: String,
url: String,
magnetUri: String? = null,
mimeType: String? = null,
alt: String? = null,
hash: String? = null,
size: String? = null,
dimensions: String? = null,
dimensions: Dimension? = null,
blurhash: String? = null,
originalHash: String? = null,
magnetURI: String? = null,
@ -107,13 +108,14 @@ abstract class VideoEvent(
) {
val tags =
listOfNotNull(
arrayOf("d", dTag),
arrayOf(URL, url),
magnetUri?.let { arrayOf(MAGNET_URI, it) },
mimeType?.let { arrayOf(MIME_TYPE, it) },
alt?.ifBlank { null }?.let { arrayOf(ALT, it) } ?: arrayOf("alt", altDescription),
hash?.let { arrayOf(HASH, it) },
size?.let { arrayOf(FILE_SIZE, it) },
dimensions?.let { arrayOf(DIMENSION, it) },
dimensions?.let { arrayOf(DIMENSION, it.toString()) },
blurhash?.let { arrayOf(BLUR_HASH, it) },
originalHash?.let { arrayOf(ORIGINAL_HASH, 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.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
import java.util.UUID
@Immutable
class VideoHorizontalEvent(
@ -45,18 +46,20 @@ class VideoHorizontalEvent(
alt: String? = null,
hash: String? = null,
size: String? = null,
dimensions: String? = null,
dimensions: Dimension? = null,
blurhash: String? = null,
originalHash: String? = null,
magnetURI: String? = null,
torrentInfoHash: String? = null,
sensitiveContent: Boolean? = null,
dTag: String = UUID.randomUUID().toString(),
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (VideoHorizontalEvent) -> Unit,
) {
create(
KIND,
dTag,
url,
magnetUri,
mimeType,

View File

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