- Migrates Video events to imeta tags

- Removes youtu.be links from the video feed.
- Checks for video file types in uppercase as well as lowercase
This commit is contained in:
Vitor Pamplona
2024-12-05 16:41:43 -05:00
parent 81591d6f76
commit b47d9ad4d9
9 changed files with 258 additions and 146 deletions
@@ -22,8 +22,10 @@ package com.vitorpamplona.quartz.events
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.encoders.HexKey
import com.vitorpamplona.quartz.encoders.Nip92MediaAttachments.Companion.IMETA
import com.vitorpamplona.quartz.signers.NostrSigner
import com.vitorpamplona.quartz.utils.TimeUtils
import com.vitorpamplona.quartz.utils.filter
@Immutable
abstract class VideoEvent(
@@ -36,52 +38,73 @@ abstract class VideoEvent(
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, kind, tags, content, sig),
RootScope {
fun url() = tags.firstOrNull { it.size > 1 && it[0] == URL }?.get(1)
private 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] }
private 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)
private 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)
private 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)
private fun size() = tags.firstOrNull { it.size > 1 && it[0] == FILE_SIZE }?.get(1)
private fun dimensions() = tags.firstOrNull { it.size > 1 && it[0] == DIMENSION }?.get(1)?.let { Dimension.parse(it) }
private fun blurhash() = tags.firstOrNull { it.size > 1 && it[0] == BLUR_HASH }?.get(1)
private fun image() = tags.filter { it.size > 1 && it[0] == IMAGE }.map { it[1] }
private fun thumb() = tags.firstOrNull { it.size > 1 && it[0] == THUMB }?.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)?.let { Dimension.parse(it) }
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 title() = tags.firstOrNull { it.size > 1 && it[0] == TITLE }?.get(1)
fun summary() = tags.firstOrNull { it.size > 1 && it[0] == SUMMARY }?.get(1)
fun image() = tags.firstOrNull { it.size > 1 && it[0] == IMAGE }?.get(1)
fun thumb() = tags.firstOrNull { it.size > 1 && it[0] == THUMB }?.get(1)
fun duration() = tags.firstOrNull { it.size > 1 && it[0] == DURATION }?.get(1)
fun hasUrl() = tags.any { it.size > 1 && it[0] == URL }
fun isOneOf(mimeTypes: Set<String>) = tags.any { it.size > 1 && it[0] == FileHeaderEvent.MIME_TYPE && mimeTypes.contains(it[1]) }
// hack to fix pablo's bug
fun rootVideo() =
url()?.let {
VideoMeta(
url = it,
mimeType = mimeType(),
blurhash = blurhash(),
alt = alt(),
hash = hash(),
dimension = dimensions(),
size = size()?.toIntOrNull(),
service = null,
fallback = emptyList(),
image = image(),
)
}
fun imetaTags() =
tags
.map { tagArray ->
if (tagArray.size > 1 && tagArray[0] == IMETA) {
VideoMeta.parse(tagArray)
} else {
null
}
}.plus(rootVideo())
.filterNotNull()
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"
@@ -91,47 +114,129 @@ abstract class VideoEvent(
kind: Int,
dTag: String,
url: String,
magnetUri: String? = null,
mimeType: String? = null,
alt: String? = null,
hash: String? = null,
size: String? = null,
size: Int? = null,
duration: Int? = null,
dimensions: Dimension? = null,
blurhash: String? = null,
originalHash: String? = null,
magnetURI: String? = null,
torrentInfoHash: String? = null,
sensitiveContent: Boolean? = null,
service: String? = null,
altDescription: String,
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (T) -> Unit,
) {
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.toString()) },
blurhash?.let { arrayOf(BLUR_HASH, it) },
originalHash?.let { arrayOf(ORIGINAL_HASH, it) },
magnetURI?.let { arrayOf(MAGNET_URI, it) },
torrentInfoHash?.let { arrayOf(TORRENT_INFOHASH, it) },
sensitiveContent?.let {
if (it) {
arrayOf("content-warning", "")
} else {
null
}
},
val video =
VideoMeta(
url,
mimeType,
blurhash,
dimensions,
alt,
hash,
size,
service,
emptyList(),
emptyList(),
)
val tags = mutableListOf<Array<String>>()
tags.add(arrayOf("d", dTag))
tags.add(arrayOf(ALT, altDescription))
if (sensitiveContent == true) {
tags.add(arrayOf("content-warning", ""))
}
duration?.let { tags.add(arrayOf(DURATION, "duration")) }
tags.add(video.toIMetaArray())
val content = alt ?: ""
signer.sign<T>(createdAt, kind, tags.toTypedArray(), content, onReady)
}
}
}
data class VideoMeta(
val url: String,
val mimeType: String?,
val blurhash: String?,
val dimension: Dimension?,
val alt: String?,
val hash: String?,
val size: Int?,
val service: String?,
val fallback: List<String>,
val image: List<String>,
) {
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" },
service?.let { "$SERVICE $it" },
) +
fallback.map { "$FALLBACK $it" } +
image.map { "$IMAGE $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 IMAGE = "image"
const val SERVICE = "service"
fun parse(tagArray: Array<String>): VideoMeta? {
var url: String? = null
var mimeType: String? = null
var blurhash: String? = null
var dim: Dimension? = null
var alt: String? = null
var hash: String? = null
var size: Int? = null
var service: String? = null
val fallback = mutableListOf<String>()
val images = mutableListOf<String>()
tagArray.forEach {
val parts = it.split(" ", limit = 2)
val key = parts[0]
val value = if (parts.size == 2) parts[1] else ""
if (value.isNotBlank()) {
when (key) {
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.toIntOrNull()
SERVICE -> service = value
FALLBACK -> fallback.add(value)
IMAGE -> images.add(value)
}
}
}
return url?.let {
VideoMeta(it, mimeType, blurhash, dim, alt, hash, size, service, fallback, images)
}
}
}
}
@@ -34,48 +34,45 @@ class VideoHorizontalEvent(
tags: Array<Array<String>>,
content: String,
sig: HexKey,
) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig),
RootScope {
companion object {
const val KIND = 34235
const val ALT_DESCRIPTION = "Horizontal Video"
fun create(
url: String,
magnetUri: String? = null,
mimeType: String? = null,
alt: String? = null,
hash: String? = null,
size: String? = null,
size: Int? = null,
duration: Int? = null,
dimensions: Dimension? = null,
blurhash: String? = null,
originalHash: String? = null,
magnetURI: String? = null,
torrentInfoHash: String? = null,
sensitiveContent: Boolean? = null,
service: String? = null,
dTag: String = UUID.randomUUID().toString(),
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (VideoHorizontalEvent) -> Unit,
) {
create(
KIND,
dTag,
url,
magnetUri,
mimeType,
alt,
hash,
size,
dimensions,
blurhash,
originalHash,
magnetURI,
torrentInfoHash,
sensitiveContent,
ALT_DESCRIPTION,
signer,
createdAt,
onReady,
kind = KIND,
dTag = dTag,
url = url,
mimeType = mimeType,
alt = alt,
hash = hash,
size = size,
duration = duration,
dimensions = dimensions,
blurhash = blurhash,
sensitiveContent = sensitiveContent,
service = service,
altDescription = ALT_DESCRIPTION,
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}
@@ -42,41 +42,37 @@ class VideoVerticalEvent(
fun create(
url: String,
magnetUri: String? = null,
mimeType: String? = null,
alt: String? = null,
hash: String? = null,
size: String? = null,
size: Int? = null,
duration: Int? = null,
dimensions: Dimension? = null,
blurhash: String? = null,
originalHash: String? = null,
magnetURI: String? = null,
torrentInfoHash: String? = null,
sensitiveContent: Boolean? = null,
service: String? = null,
dTag: String = UUID.randomUUID().toString(),
signer: NostrSigner,
createdAt: Long = TimeUtils.now(),
onReady: (VideoVerticalEvent) -> Unit,
) {
create(
KIND,
dTag,
url,
magnetUri,
mimeType,
alt,
hash,
size,
dimensions,
blurhash,
originalHash,
magnetURI,
torrentInfoHash,
sensitiveContent,
ALT_DESCRIPTION,
signer,
createdAt,
onReady,
kind = KIND,
dTag = dTag,
url = url,
mimeType = mimeType,
alt = alt,
hash = hash,
size = size,
duration = duration,
dimensions = dimensions,
blurhash = blurhash,
sensitiveContent = sensitiveContent,
service = service,
altDescription = ALT_DESCRIPTION,
signer = signer,
createdAt = createdAt,
onReady = onReady,
)
}
}