Adds an iMeta cache for Picture and Video kinds and speeds up the procedure to verify if a video or picture is supported.

This commit is contained in:
Vitor Pamplona
2025-07-30 12:47:31 -04:00
parent 2c6a084808
commit fa53ff2c78
8 changed files with 176 additions and 156 deletions
@@ -24,25 +24,16 @@ import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.Event
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
import com.vitorpamplona.quartz.nip01Core.core.any
import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate
import com.vitorpamplona.quartz.nip01Core.tags.geohash.geohashes
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
import com.vitorpamplona.quartz.nip22Comments.RootScope
import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag
import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.nip68Picture.tags.LocationTag
import com.vitorpamplona.quartz.nip92IMeta.imetas
import com.vitorpamplona.quartz.nip94FileMetadata.tags.BlurhashTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.FallbackTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.HashSha256Tag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.ImageTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.MagnetTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.MimeTypeTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.ServiceTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.SizeTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.ThumbTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.TorrentInfoHash
import com.vitorpamplona.quartz.nip94FileMetadata.tags.UrlTag
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
import com.vitorpamplona.quartz.utils.TimeUtils
@Immutable
@@ -55,63 +46,21 @@ class PictureEvent(
sig: HexKey,
) : Event(id, pubKey, createdAt, KIND, tags, content, sig),
RootScope {
// ---------------
// current
// --------------
@Transient var iMetas: List<PictureMeta>? = null
fun title() = tags.firstNotNullOfOrNull(TitleTag::parse)
/** old standard didnt use IMetas **/
private fun url() = tags.firstNotNullOfOrNull(UrlTag::parse)
fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse)
private fun urls() = tags.mapNotNull(UrlTag::parse)
fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse)
private fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse)
fun hashtags() = tags.hashtags()
private fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse)
fun geohashes() = tags.geohashes()
private fun size() = tags.firstNotNullOfOrNull(SizeTag::parse)
fun location() = tags.mapNotNull(LocationTag::parse)
private fun dimensions() = tags.firstNotNullOfOrNull(DimensionTag::parse)
private fun magnetURI() = tags.firstNotNullOfOrNull(MagnetTag::parse)
private fun torrentInfoHash() = tags.firstNotNullOfOrNull(TorrentInfoHash::parse)
private fun blurhash() = tags.firstNotNullOfOrNull(BlurhashTag::parse)
private fun hasUrl() = tags.any(UrlTag::isTag)
private fun isOneOf(mimeTypes: Set<String>) = tags.any(MimeTypeTag::isIn, mimeTypes)
private fun image() = tags.firstNotNullOfOrNull(ImageTag::parse)
private fun images() = tags.mapNotNull(ImageTag::parse)
private fun thumb() = tags.firstNotNullOfOrNull(ThumbTag::parse)
private fun service() = tags.firstNotNullOfOrNull(ServiceTag::parse)
private fun fallbacks() = tags.mapNotNull(FallbackTag::parse)
// hack to fix pablo's bug
fun rootImage() =
url()?.let {
PictureMeta(
url = it,
mimeType = mimeType(),
blurhash = blurhash(),
alt = alt(),
hash = hash(),
dimension = dimensions(),
size = size(),
service = service(),
fallback = fallbacks(),
annotations = emptyList(),
)
}
fun imetaTags() = imetas().map { PictureMeta.parse(it) }.plus(rootImage()).filterNotNull()
fun imetaTags() = iMetas ?: imetas().map { PictureMeta.parse(it) }.also { iMetas = it }
companion object {
const val KIND = 20
@@ -142,7 +91,7 @@ class PictureEvent(
createdAt: Long = TimeUtils.now(),
initializer: TagArrayBuilder<PictureEvent>.() -> Unit = {},
) = eventTemplate(KIND, description, createdAt) {
alt(ClassifiedsEvent.ALT_DESCRIPTION)
alt(ALT_DESCRIPTION)
initializer()
}
}
@@ -0,0 +1,41 @@
/**
* Copyright (c) 2025 Vitor Pamplona
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.vitorpamplona.quartz.nip68Picture.tags
import com.vitorpamplona.quartz.nip01Core.core.has
import com.vitorpamplona.quartz.utils.ensure
class LocationTag {
companion object {
const val TAG_NAME = "location"
@JvmStatic
fun parse(tag: Array<String>): String? {
ensure(tag.has(1)) { return null }
ensure(tag[0] == TAG_NAME) { return null }
ensure(tag[1].isNotEmpty()) { return null }
return tag[1]
}
@JvmStatic
fun assemble(locationName: String) = arrayOf(TAG_NAME, locationName)
}
}
@@ -23,29 +23,17 @@ package com.vitorpamplona.quartz.nip71Video
import androidx.compose.runtime.Immutable
import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.any
import com.vitorpamplona.quartz.nip01Core.tags.events.ETag
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hashtags
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
import com.vitorpamplona.quartz.nip22Comments.RootScope
import com.vitorpamplona.quartz.nip23LongContent.tags.PublishedAtTag
import com.vitorpamplona.quartz.nip23LongContent.tags.TitleTag
import com.vitorpamplona.quartz.nip31Alts.alt
import com.vitorpamplona.quartz.nip71Video.tags.DurationTag
import com.vitorpamplona.quartz.nip71Video.tags.SegmentTag
import com.vitorpamplona.quartz.nip92IMeta.imetas
import com.vitorpamplona.quartz.nip94FileMetadata.tags.BlurhashTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.FallbackTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.HashSha256Tag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.ImageTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.MagnetTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.MimeTypeTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.ServiceTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.SizeTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.ThumbTag
import com.vitorpamplona.quartz.nip94FileMetadata.tags.TorrentInfoHash
import com.vitorpamplona.quartz.nip94FileMetadata.tags.UrlTag
@Immutable
abstract class VideoEvent(
@@ -58,59 +46,7 @@ abstract class VideoEvent(
sig: HexKey,
) : BaseAddressableEvent(id, pubKey, createdAt, kind, tags, content, sig),
RootScope {
/** old standard didnt use IMetas **/
private fun url() = tags.firstNotNullOfOrNull(UrlTag::parse)
private fun urls() = tags.mapNotNull(UrlTag::parse)
private fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse)
private fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse)
private fun size() = tags.firstNotNullOfOrNull(SizeTag::parse)
private fun dimensions() = tags.firstNotNullOfOrNull(DimensionTag::parse)
private fun magnetURI() = tags.firstNotNullOfOrNull(MagnetTag::parse)
private fun torrentInfoHash() = tags.firstNotNullOfOrNull(TorrentInfoHash::parse)
private fun blurhash() = tags.firstNotNullOfOrNull(BlurhashTag::parse)
private fun hasUrl() = tags.any(UrlTag::isTag)
private fun isOneOf(mimeTypes: Set<String>) = tags.any(MimeTypeTag::isIn, mimeTypes)
private fun image() = tags.firstNotNullOfOrNull(ImageTag::parse)
private fun images() = tags.mapNotNull(ImageTag::parse)
private fun thumb() = tags.firstNotNullOfOrNull(ThumbTag::parse)
private fun service() = tags.firstNotNullOfOrNull(ServiceTag::parse)
private fun fallbacks() = tags.mapNotNull(FallbackTag::parse)
// 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(),
service = service(),
fallback = fallbacks(),
image = images().map { it.imageUrl },
)
}
// ---------------
// current
// --------------
@Transient var iMetas: List<VideoMeta>? = null
fun title() = tags.firstNotNullOfOrNull(TitleTag::parse)
@@ -126,5 +62,9 @@ abstract class VideoEvent(
fun hashtags() = tags.hashtags()
fun imetaTags() = imetas().map { VideoMeta.parse(it) }.plus(rootVideo()).filterNotNull()
private fun mimeType() = tags.firstNotNullOfOrNull(MimeTypeTag::parse)
private fun hash() = tags.firstNotNullOfOrNull(HashSha256Tag::parse)
fun imetaTags() = iMetas ?: imetas().map { VideoMeta.parse(it) }.also { iMetas = it }
}
@@ -48,7 +48,7 @@ class IMetaTag(
ensure(tag[1].isNotEmpty()) { return null }
val allTags = parseIMeta(tag)
val url = allTags.get(ANCHOR_PROPERTY)?.firstOrNull()
val url = allTags[ANCHOR_PROPERTY]?.firstOrNull()
return if (url != null) {
IMetaTag(url, allTags.minus(ANCHOR_PROPERTY))