diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index e05862066..66f4f084a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -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, 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) + } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt index 1e3d6d5be..d091976dc 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/FileHeader.kt @@ -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? { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt index 788882e92..73af5310d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/EditPostViewModel.kt @@ -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" } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt index 4a9f4c080..0fba89030 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewMediaModel.kt @@ -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" } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt index 12a3e1359..6dc82848d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostViewModel.kt @@ -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" } diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/ATag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/ATag.kt index 59d235e14..b2165e543 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/ATag.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/ATag.kt @@ -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() diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/FileHeaderEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/FileHeaderEvent.kt index 47dbe6275..6e9b6dc45 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/FileHeaderEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/FileHeaderEvent.kt @@ -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) }, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/FileStorageHeaderEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/FileStorageHeaderEvent.kt index f2659a9a6..c58bbed08 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/FileStorageHeaderEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/FileStorageHeaderEvent.kt @@ -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) }, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/PictureEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/PictureEvent.kt index 6a02ae018..707ae1212 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/PictureEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/PictureEvent.kt @@ -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>, + 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 = emptySet(), addressesMentioned: Set = emptySet(), eventsMentioned: Set = emptySet(), - nip94attachments: List? = null, geohash: String? = null, zapReceiver: List? = 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, + msg: String? = null, + title: String? = null, + usersMentioned: Set = emptySet(), + addressesMentioned: Set = emptySet(), + eventsMentioned: Set = emptySet(), + geohash: String? = null, + zapReceiver: List? = null, + markAsSensitive: Boolean = false, + zapRaiserAmount: Long? = null, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (PictureEvent) -> Unit, + ) { + val tags = mutableListOf(arrayOf("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, val annotations: List, ) { + fun toIMetaArray(): Array = + ( + 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): PictureMeta? { var url: String? = null var mimeType: String? = null @@ -188,9 +249,9 @@ class PictureMeta( val fallback = mutableListOf() val annotations = mutableListOf() - 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() @@ -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(":") diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoEvent.kt index cae5ba62f..2fc08d3d7 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoEvent.kt @@ -88,13 +88,14 @@ abstract class VideoEvent( fun 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) }, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoHorizontalEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoHorizontalEvent.kt index 3f7ea925c..703c3d787 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoHorizontalEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoHorizontalEvent.kt @@ -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, diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoVerticalEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoVerticalEvent.kt index 14d15b1c7..2ed43e2de 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoVerticalEvent.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoVerticalEvent.kt @@ -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,