From 4afb0027fb7b8f2ab6e6919b1d836899a00203fe Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 26 Dec 2023 18:02:13 -0500 Subject: [PATCH] Initial support for separate vertical and horizontal videos. --- .../vitorpamplona/quartz/events/VideoEvent.kt | 99 +++++++++++++++++++ .../quartz/events/VideoHorizontalEvent.kt | 62 ++++++++++++ .../quartz/events/VideoVerticalEvent.kt | 61 ++++++++++++ .../quartz/events/VideoViewEvent.kt | 63 ++++++++++++ 4 files changed, 285 insertions(+) create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/events/VideoEvent.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/events/VideoHorizontalEvent.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/events/VideoVerticalEvent.kt create mode 100644 quartz/src/main/java/com/vitorpamplona/quartz/events/VideoViewEvent.kt diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoEvent.kt new file mode 100644 index 000000000..c3da90f28 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoEvent.kt @@ -0,0 +1,99 @@ +package com.vitorpamplona.quartz.events + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.utils.TimeUtils +import com.vitorpamplona.quartz.encoders.HexKey +import com.vitorpamplona.quartz.signers.NostrSigner + +@Immutable +abstract class VideoEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + kind: Int, + tags: Array>, + content: String, + sig: HexKey +) : BaseAddressableEvent(id, pubKey, createdAt, kind, tags, content, sig) { + + fun url() = tags.firstOrNull { it.size > 1 && it[0] == URL }?.get(1) + fun urls() = tags.filter { it.size > 1 && it[0] == URL }.map { it[1] } + + fun mimeType() = tags.firstOrNull { it.size > 1 && it[0] == MIME_TYPE }?.get(1) + fun hash() = tags.firstOrNull { it.size > 1 && it[0] == HASH }?.get(1) + fun size() = tags.firstOrNull { it.size > 1 && it[0] == FILE_SIZE }?.get(1) + fun alt() = tags.firstOrNull { it.size > 1 && it[0] == ALT }?.get(1) + fun dimensions() = tags.firstOrNull { it.size > 1 && it[0] == DIMENSION }?.get(1) + fun magnetURI() = tags.firstOrNull { it.size > 1 && it[0] == MAGNET_URI }?.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 } + + companion object { + private const val URL = "url" + private const val ENCRYPTION_KEY = "aes-256-gcm" + private const val MIME_TYPE = "m" + private const val FILE_SIZE = "size" + private const val DIMENSION = "dim" + private const val HASH = "x" + private const val MAGNET_URI = "magnet" + private const val TORRENT_INFOHASH = "i" + private const val BLUR_HASH = "blurhash" + private const val ORIGINAL_HASH = "ox" + private const val ALT = "alt" + private const val TITLE = "title" + private const val PUBLISHED_AT = "published_at" + private const val SUMMARY = "summary" + private const val DURATION = "duration" + private const val IMAGE = "image" + private const val THUMB = "thumb" + + fun create( + kind: Int, + url: String, + magnetUri: String? = null, + mimeType: String? = null, + alt: String? = null, + hash: String? = null, + size: String? = null, + dimensions: String? = null, + blurhash: String? = null, + originalHash: String? = null, + magnetURI: String? = null, + torrentInfoHash: String? = null, + encryptionKey: AESGCM? = null, + sensitiveContent: Boolean? = null, + altDescription: String, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (FileHeaderEvent) -> Unit + ) { + val tags = listOfNotNull( + 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) }, + blurhash?.let { arrayOf(BLUR_HASH, it) }, + originalHash?.let { arrayOf(ORIGINAL_HASH, it) }, + magnetURI?.let { arrayOf(MAGNET_URI, it) }, + + torrentInfoHash?.let { arrayOf(TORRENT_INFOHASH, it) }, + encryptionKey?.let { arrayOf(ENCRYPTION_KEY, it.key, it.nonce) }, + sensitiveContent?.let { + if (it) { + arrayOf("content-warning", "") + } else { + null + } + } + ) + + val content = alt ?: "" + signer.sign(createdAt, kind, tags.toTypedArray(), content, onReady) + } + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoHorizontalEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoHorizontalEvent.kt new file mode 100644 index 000000000..42a6cfa04 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoHorizontalEvent.kt @@ -0,0 +1,62 @@ +package com.vitorpamplona.quartz.events + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.utils.TimeUtils +import com.vitorpamplona.quartz.encoders.HexKey +import com.vitorpamplona.quartz.signers.NostrSigner + +@Immutable +class VideoHorizontalEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey +) : VideoEvent(id, pubKey, createdAt, kind, tags, content, sig) { + + companion object { + const val kind = 34235 + const val altDescription = "Horizontal Video" + + fun create( + url: String, + magnetUri: String? = null, + mimeType: String? = null, + alt: String? = null, + hash: String? = null, + size: String? = null, + dimensions: String? = null, + blurhash: String? = null, + originalHash: String? = null, + magnetURI: String? = null, + torrentInfoHash: String? = null, + encryptionKey: AESGCM? = null, + sensitiveContent: Boolean? = null, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (FileHeaderEvent) -> Unit + ) { + create( + kind, + url, + magnetUri, + mimeType, + alt, + hash, + size, + dimensions, + blurhash, + originalHash, + magnetURI, + torrentInfoHash, + encryptionKey, + sensitiveContent, + altDescription, + signer, + createdAt, + onReady + ) + } + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoVerticalEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoVerticalEvent.kt new file mode 100644 index 000000000..9f87a0696 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoVerticalEvent.kt @@ -0,0 +1,61 @@ +package com.vitorpamplona.quartz.events + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.utils.TimeUtils +import com.vitorpamplona.quartz.encoders.HexKey +import com.vitorpamplona.quartz.signers.NostrSigner + +@Immutable +class VideoVerticalEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey +) : VideoEvent(id, pubKey, createdAt, kind, tags, content, sig) { + companion object { + const val kind = 34236 + const val altDescription = "Vertical Video" + + fun create( + url: String, + magnetUri: String? = null, + mimeType: String? = null, + alt: String? = null, + hash: String? = null, + size: String? = null, + dimensions: String? = null, + blurhash: String? = null, + originalHash: String? = null, + magnetURI: String? = null, + torrentInfoHash: String? = null, + encryptionKey: AESGCM? = null, + sensitiveContent: Boolean? = null, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (FileHeaderEvent) -> Unit + ) { + create( + kind, + url, + magnetUri, + mimeType, + alt, + hash, + size, + dimensions, + blurhash, + originalHash, + magnetURI, + torrentInfoHash, + encryptionKey, + sensitiveContent, + altDescription, + signer, + createdAt, + onReady + ) + } + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoViewEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoViewEvent.kt new file mode 100644 index 000000000..57c20b8cc --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/events/VideoViewEvent.kt @@ -0,0 +1,63 @@ +package com.vitorpamplona.quartz.events + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.utils.TimeUtils +import com.vitorpamplona.quartz.encoders.toHexKey +import com.vitorpamplona.quartz.crypto.CryptoUtils +import com.vitorpamplona.quartz.crypto.KeyPair +import com.vitorpamplona.quartz.encoders.ATag +import com.vitorpamplona.quartz.encoders.HexKey +import com.vitorpamplona.quartz.signers.NostrSigner + +@Immutable +class VideoViewEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey +) : BaseAddressableEvent(id, pubKey, createdAt, kind, tags, content, sig) { + + companion object { + const val kind = 34237 + + fun create( + video: ATag, + signer: NostrSigner, + viewStart: Long?, + viewEnd: Long?, + createdAt: Long = TimeUtils.now(), + onReady: (VideoViewEvent) -> Unit + ) { + val tags = mutableListOf>() + + val aTag = video.toTag() + tags.add(arrayOf("d", aTag)) + tags.add(arrayOf("a", aTag)) + if (viewEnd != null) + tags.add(arrayOf("viewed", viewStart?.toString() ?: "0", viewEnd.toString())) + else + tags.add(arrayOf("viewed", viewStart?.toString() ?: "0")) + + signer.sign(createdAt, kind, tags.toTypedArray(), "", onReady) + } + + fun addViewedTime( + event: VideoViewEvent, + viewStart: Long?, + viewEnd: Long?, + signer: NostrSigner, + createdAt: Long = TimeUtils.now(), + onReady: (VideoViewEvent) -> Unit + ) { + val tags = event.tags.toMutableList() + if (viewEnd != null) + tags.add(arrayOf("viewed", viewStart?.toString() ?: "0", viewEnd.toString())) + else + tags.add(arrayOf("viewed", viewStart?.toString() ?: "0")) + + signer.sign(createdAt, kind, tags.toTypedArray(), "", onReady) + } + } +}