- 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:
@@ -1976,14 +1976,12 @@ class Account(
|
|||||||
if (headerInfo.dim.height > headerInfo.dim.width) {
|
if (headerInfo.dim.height > headerInfo.dim.width) {
|
||||||
VideoVerticalEvent.create(
|
VideoVerticalEvent.create(
|
||||||
url = url,
|
url = url,
|
||||||
magnetUri = magnetUri,
|
|
||||||
mimeType = headerInfo.mimeType,
|
mimeType = headerInfo.mimeType,
|
||||||
hash = headerInfo.hash,
|
hash = headerInfo.hash,
|
||||||
size = headerInfo.size.toString(),
|
size = headerInfo.size,
|
||||||
dimensions = headerInfo.dim,
|
dimensions = headerInfo.dim,
|
||||||
blurhash = headerInfo.blurHash,
|
blurhash = headerInfo.blurHash,
|
||||||
alt = alt,
|
alt = alt,
|
||||||
originalHash = originalHash,
|
|
||||||
sensitiveContent = sensitiveContent,
|
sensitiveContent = sensitiveContent,
|
||||||
signer = signer,
|
signer = signer,
|
||||||
) { event ->
|
) { event ->
|
||||||
@@ -1992,14 +1990,12 @@ class Account(
|
|||||||
} else {
|
} else {
|
||||||
VideoHorizontalEvent.create(
|
VideoHorizontalEvent.create(
|
||||||
url = url,
|
url = url,
|
||||||
magnetUri = magnetUri,
|
|
||||||
mimeType = headerInfo.mimeType,
|
mimeType = headerInfo.mimeType,
|
||||||
hash = headerInfo.hash,
|
hash = headerInfo.hash,
|
||||||
size = headerInfo.size.toString(),
|
size = headerInfo.size,
|
||||||
dimensions = headerInfo.dim,
|
dimensions = headerInfo.dim,
|
||||||
blurhash = headerInfo.blurHash,
|
blurhash = headerInfo.blurHash,
|
||||||
alt = alt,
|
alt = alt,
|
||||||
originalHash = originalHash,
|
|
||||||
sensitiveContent = sensitiveContent,
|
sensitiveContent = sensitiveContent,
|
||||||
signer = signer,
|
signer = signer,
|
||||||
) { event ->
|
) { event ->
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import com.vitorpamplona.quartz.events.MuteListEvent
|
|||||||
import com.vitorpamplona.quartz.events.PeopleListEvent
|
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||||
import com.vitorpamplona.quartz.events.PictureEvent
|
import com.vitorpamplona.quartz.events.PictureEvent
|
||||||
import com.vitorpamplona.quartz.events.VideoHorizontalEvent
|
import com.vitorpamplona.quartz.events.VideoHorizontalEvent
|
||||||
|
import com.vitorpamplona.quartz.events.VideoMeta
|
||||||
import com.vitorpamplona.quartz.events.VideoVerticalEvent
|
import com.vitorpamplona.quartz.events.VideoVerticalEvent
|
||||||
|
|
||||||
class VideoFeedFilter(
|
class VideoFeedFilter(
|
||||||
@@ -66,6 +67,29 @@ class VideoFeedFilter(
|
|||||||
return collection.filterTo(HashSet()) { acceptableEvent(it, params) }
|
return collection.filterTo(HashSet()) { acceptableEvent(it, params) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun acceptableUrls(
|
||||||
|
baseUrls: List<String>,
|
||||||
|
mimeType: String?,
|
||||||
|
): Boolean {
|
||||||
|
// we don't have an youtube player
|
||||||
|
val urls = baseUrls.filter { !it.contains("youtu.be") }
|
||||||
|
|
||||||
|
val isSupportedMimeType = mimeType?.let { SUPPORTED_VIDEO_FEED_MIME_TYPES_SET.contains(it) } ?: false
|
||||||
|
|
||||||
|
return urls.isNotEmpty() && (urls.any { isImageOrVideoUrl(it) } || isSupportedMimeType)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun acceptableiMetas(iMetas: List<VideoMeta>): Boolean =
|
||||||
|
iMetas.any {
|
||||||
|
!it.url.contains("youtu.be") && (isImageOrVideoUrl(it.url) || (it.mimeType == null || SUPPORTED_VIDEO_FEED_MIME_TYPES_SET.contains(it.mimeType)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun acceptanceEvent(noteEvent: FileHeaderEvent) = acceptableUrls(noteEvent.urls(), noteEvent.mimeType())
|
||||||
|
|
||||||
|
fun acceptanceEvent(noteEvent: VideoVerticalEvent) = acceptableiMetas(noteEvent.imetaTags())
|
||||||
|
|
||||||
|
fun acceptanceEvent(noteEvent: VideoHorizontalEvent) = acceptableiMetas(noteEvent.imetaTags())
|
||||||
|
|
||||||
fun acceptableEvent(
|
fun acceptableEvent(
|
||||||
note: Note,
|
note: Note,
|
||||||
params: FilterByListParams,
|
params: FilterByListParams,
|
||||||
@@ -77,9 +101,9 @@ class VideoFeedFilter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(noteEvent is FileHeaderEvent && noteEvent.hasUrl() && (noteEvent.urls().any { isImageOrVideoUrl(it) } || noteEvent.isOneOf(SUPPORTED_VIDEO_FEED_MIME_TYPES_SET))) ||
|
(noteEvent is FileHeaderEvent && acceptanceEvent(noteEvent)) ||
|
||||||
(noteEvent is VideoVerticalEvent && noteEvent.hasUrl() && (noteEvent.urls().any { isImageOrVideoUrl(it) } || noteEvent.isOneOf(SUPPORTED_VIDEO_FEED_MIME_TYPES_SET))) ||
|
(noteEvent is VideoVerticalEvent && acceptanceEvent(noteEvent)) ||
|
||||||
(noteEvent is VideoHorizontalEvent && noteEvent.hasUrl() && (noteEvent.urls().any { isImageOrVideoUrl(it) } || noteEvent.isOneOf(SUPPORTED_VIDEO_FEED_MIME_TYPES_SET))) ||
|
(noteEvent is VideoHorizontalEvent && acceptanceEvent(noteEvent)) ||
|
||||||
(noteEvent is FileStorageHeaderEvent && noteEvent.isOneOf(SUPPORTED_VIDEO_FEED_MIME_TYPES_SET)) ||
|
(noteEvent is FileStorageHeaderEvent && noteEvent.isOneOf(SUPPORTED_VIDEO_FEED_MIME_TYPES_SET)) ||
|
||||||
noteEvent is PictureEvent
|
noteEvent is PictureEvent
|
||||||
) &&
|
) &&
|
||||||
|
|||||||
@@ -71,45 +71,41 @@ fun VideoDisplay(
|
|||||||
nav: INav,
|
nav: INav,
|
||||||
) {
|
) {
|
||||||
val event = (note.event as? VideoEvent) ?: return
|
val event = (note.event as? VideoEvent) ?: return
|
||||||
val fullUrl = event.url() ?: return
|
val imeta = event.imetaTags().firstOrNull() ?: return
|
||||||
|
|
||||||
val title = event.title()
|
val title = event.title()
|
||||||
val summary = event.content.ifBlank { null }?.takeIf { title != it }
|
val summary = event.content.ifBlank { null }?.takeIf { title != it }
|
||||||
val image = event.thumb() ?: event.image()
|
val image = imeta.image.firstOrNull()
|
||||||
val isYouTube = fullUrl.contains("youtube.com") || fullUrl.contains("youtu.be")
|
val isYouTube = imeta.url.contains("youtube.com") || imeta.url.contains("youtu.be")
|
||||||
val tags = remember(note) { note.event?.tags()?.toImmutableListOfLists() ?: EmptyTagList }
|
val tags = remember(note) { note.event?.tags()?.toImmutableListOfLists() ?: EmptyTagList }
|
||||||
|
|
||||||
val content by
|
val content by
|
||||||
remember(note) {
|
remember(note) {
|
||||||
val blurHash = event.blurhash()
|
|
||||||
val hash = event.hash()
|
|
||||||
val dimensions = event.dimensions()
|
|
||||||
val description = event.content.ifBlank { null } ?: event.alt()
|
val description = event.content.ifBlank { null } ?: event.alt()
|
||||||
val isImage = event.mimeType()?.startsWith("image/") == true || RichTextParser.isImageUrl(fullUrl)
|
val isImage = imeta.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(imeta.url)
|
||||||
val uri = note.toNostrUri()
|
val uri = note.toNostrUri()
|
||||||
val mimeType = event.mimeType()
|
|
||||||
|
|
||||||
mutableStateOf<BaseMediaContent>(
|
mutableStateOf<BaseMediaContent>(
|
||||||
if (isImage) {
|
if (isImage) {
|
||||||
MediaUrlImage(
|
MediaUrlImage(
|
||||||
url = fullUrl,
|
url = imeta.url,
|
||||||
description = description,
|
description = description,
|
||||||
hash = hash,
|
hash = imeta.hash,
|
||||||
blurhash = blurHash,
|
blurhash = imeta.blurhash,
|
||||||
dim = dimensions,
|
dim = imeta.dimension,
|
||||||
uri = uri,
|
uri = uri,
|
||||||
mimeType = mimeType,
|
mimeType = imeta.mimeType,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
MediaUrlVideo(
|
MediaUrlVideo(
|
||||||
url = fullUrl,
|
url = imeta.url,
|
||||||
description = description,
|
description = description,
|
||||||
hash = hash,
|
hash = imeta.hash,
|
||||||
dim = dimensions,
|
dim = imeta.dimension,
|
||||||
uri = uri,
|
uri = uri,
|
||||||
authorName = note.author?.toBestDisplayName(),
|
authorName = note.author?.toBestDisplayName(),
|
||||||
artworkUri = event.thumb() ?: event.image(),
|
artworkUri = imeta.image.firstOrNull(),
|
||||||
mimeType = mimeType,
|
mimeType = imeta.mimeType,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -126,7 +122,7 @@ fun VideoDisplay(
|
|||||||
if (isYouTube) {
|
if (isYouTube) {
|
||||||
val uri = LocalUriHandler.current
|
val uri = LocalUriHandler.current
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.clickable { runCatching { uri.openUri(fullUrl) } },
|
modifier = Modifier.clickable { runCatching { uri.openUri(imeta.url) } },
|
||||||
) {
|
) {
|
||||||
image?.let {
|
image?.let {
|
||||||
AsyncImage(
|
AsyncImage(
|
||||||
|
|||||||
@@ -42,39 +42,34 @@ fun JustVideoDisplay(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
val event = (note.event as? VideoEvent) ?: return
|
val event = (note.event as? VideoEvent) ?: return
|
||||||
val fullUrl = event.url() ?: return
|
val imeta = event.imetaTags().getOrNull(0) ?: return
|
||||||
|
|
||||||
val content by
|
val content by
|
||||||
remember(note) {
|
remember(note) {
|
||||||
val blurHash = event.blurhash()
|
val description = event.content.ifEmpty { null } ?: imeta.alt ?: event.alt()
|
||||||
val hash = event.hash()
|
val isImage = imeta.mimeType?.startsWith("image/") == true || RichTextParser.isImageUrl(imeta.url)
|
||||||
val dimensions = event.dimensions()
|
|
||||||
val description = event.content.ifEmpty { null } ?: event.alt()
|
|
||||||
val isImage = event.mimeType()?.startsWith("image/") == true || RichTextParser.isImageUrl(fullUrl)
|
|
||||||
val uri = note.toNostrUri()
|
|
||||||
val mimeType = event.mimeType()
|
|
||||||
|
|
||||||
mutableStateOf<BaseMediaContent>(
|
mutableStateOf<BaseMediaContent>(
|
||||||
if (isImage) {
|
if (isImage) {
|
||||||
MediaUrlImage(
|
MediaUrlImage(
|
||||||
url = fullUrl,
|
url = imeta.url,
|
||||||
description = description,
|
description = description,
|
||||||
hash = hash,
|
hash = imeta.hash,
|
||||||
blurhash = blurHash,
|
blurhash = imeta.blurhash,
|
||||||
dim = dimensions,
|
dim = imeta.dimension,
|
||||||
uri = uri,
|
uri = note.toNostrUri(),
|
||||||
mimeType = mimeType,
|
mimeType = imeta.mimeType,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
MediaUrlVideo(
|
MediaUrlVideo(
|
||||||
url = fullUrl,
|
url = imeta.url,
|
||||||
description = description,
|
description = description,
|
||||||
hash = hash,
|
hash = imeta.hash,
|
||||||
blurhash = blurHash,
|
blurhash = imeta.blurhash,
|
||||||
dim = dimensions,
|
dim = imeta.dimension,
|
||||||
uri = uri,
|
uri = note.toNostrUri(),
|
||||||
authorName = note.author?.toBestDisplayName(),
|
authorName = note.author?.toBestDisplayName(),
|
||||||
mimeType = mimeType,
|
mimeType = imeta.mimeType,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
+1
-1
@@ -67,7 +67,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
val EMAIL_PATTERN = Pattern.compile(".+@.+\\.[a-z]+")
|
val EMAIL_PATTERN: Pattern = Pattern.compile(".+@.+\\.[a-z]+")
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class AccountStateViewModel : ViewModel() {
|
class AccountStateViewModel : ViewModel() {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ class RichTextParser {
|
|||||||
} else {
|
} else {
|
||||||
val removedParamsFromUrl = removeQueryParamsForExtensionComparison(fullUrl)
|
val removedParamsFromUrl = removeQueryParamsForExtensionComparison(fullUrl)
|
||||||
isImage = imageExtensions.any { removedParamsFromUrl.endsWith(it) }
|
isImage = imageExtensions.any { removedParamsFromUrl.endsWith(it) }
|
||||||
isVideo = imageExtensions.any { removedParamsFromUrl.endsWith(it) }
|
isVideo = videoExtensions.any { removedParamsFromUrl.endsWith(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (isImage) {
|
return if (isImage) {
|
||||||
@@ -344,8 +344,11 @@ class RichTextParser {
|
|||||||
"^((http|https)://)?([A-Za-z0-9-_]+(\\.[A-Za-z0-9-_]+)+)(:[0-9]+)?(/[^?#]*)?(\\?[^#]*)?(#.*)?"
|
"^((http|https)://)?([A-Za-z0-9-_]+(\\.[A-Za-z0-9-_]+)+)(:[0-9]+)?(/[^?#]*)?(\\?[^#]*)?(#.*)?"
|
||||||
.toRegex(RegexOption.IGNORE_CASE)
|
.toRegex(RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
val imageExtensions = listOf("png", "jpg", "gif", "bmp", "jpeg", "webp", "svg", "avif")
|
val imageExt = listOf("png", "jpg", "gif", "bmp", "jpeg", "webp", "svg", "avif")
|
||||||
val videoExtensions = listOf("mp4", "avi", "wmv", "mpg", "amv", "webm", "mov", "mp3", "m3u8")
|
val videoExt = listOf("mp4", "avi", "wmv", "mpg", "amv", "webm", "mov", "mp3", "m3u8")
|
||||||
|
|
||||||
|
val imageExtensions = imageExt + imageExt.map { it.uppercase() }
|
||||||
|
val videoExtensions = videoExt + videoExt.map { it.uppercase() }
|
||||||
|
|
||||||
val base64contentPattern = Pattern.compile("data:image/(${imageExtensions.joinToString(separator = "|") { it } });base64,([a-zA-Z0-9+/]+={0,2})")
|
val base64contentPattern = Pattern.compile("data:image/(${imageExtensions.joinToString(separator = "|") { it } });base64,([a-zA-Z0-9+/]+={0,2})")
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ package com.vitorpamplona.quartz.events
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.quartz.encoders.HexKey
|
import com.vitorpamplona.quartz.encoders.HexKey
|
||||||
|
import com.vitorpamplona.quartz.encoders.Nip92MediaAttachments.Companion.IMETA
|
||||||
import com.vitorpamplona.quartz.signers.NostrSigner
|
import com.vitorpamplona.quartz.signers.NostrSigner
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
import com.vitorpamplona.quartz.utils.filter
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
abstract class VideoEvent(
|
abstract class VideoEvent(
|
||||||
@@ -36,52 +38,73 @@ abstract class VideoEvent(
|
|||||||
sig: HexKey,
|
sig: HexKey,
|
||||||
) : BaseAddressableEvent(id, pubKey, createdAt, kind, tags, content, sig),
|
) : BaseAddressableEvent(id, pubKey, createdAt, kind, tags, content, sig),
|
||||||
RootScope {
|
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 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 title() = tags.firstOrNull { it.size > 1 && it[0] == TITLE }?.get(1)
|
||||||
|
|
||||||
fun summary() = tags.firstOrNull { it.size > 1 && it[0] == SUMMARY }?.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 duration() = tags.firstOrNull { it.size > 1 && it[0] == DURATION }?.get(1)
|
||||||
|
|
||||||
fun thumb() = tags.firstOrNull { it.size > 1 && it[0] == THUMB }?.get(1)
|
|
||||||
|
|
||||||
fun hasUrl() = tags.any { it.size > 1 && it[0] == URL }
|
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]) }
|
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 {
|
companion object {
|
||||||
private const val URL = "url"
|
private const val URL = "url"
|
||||||
private const val ENCRYPTION_KEY = "aes-256-gcm"
|
|
||||||
private const val MIME_TYPE = "m"
|
private const val MIME_TYPE = "m"
|
||||||
private const val FILE_SIZE = "size"
|
private const val FILE_SIZE = "size"
|
||||||
private const val DIMENSION = "dim"
|
private const val DIMENSION = "dim"
|
||||||
private const val HASH = "x"
|
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 BLUR_HASH = "blurhash"
|
||||||
private const val ORIGINAL_HASH = "ox"
|
|
||||||
private const val ALT = "alt"
|
private const val ALT = "alt"
|
||||||
private const val TITLE = "title"
|
private const val TITLE = "title"
|
||||||
private const val PUBLISHED_AT = "published_at"
|
|
||||||
private const val SUMMARY = "summary"
|
private const val SUMMARY = "summary"
|
||||||
private const val DURATION = "duration"
|
private const val DURATION = "duration"
|
||||||
private const val IMAGE = "image"
|
private const val IMAGE = "image"
|
||||||
@@ -91,47 +114,129 @@ abstract class VideoEvent(
|
|||||||
kind: Int,
|
kind: Int,
|
||||||
dTag: String,
|
dTag: String,
|
||||||
url: String,
|
url: String,
|
||||||
magnetUri: String? = null,
|
|
||||||
mimeType: String? = null,
|
mimeType: String? = null,
|
||||||
alt: String? = null,
|
alt: String? = null,
|
||||||
hash: String? = null,
|
hash: String? = null,
|
||||||
size: String? = null,
|
size: Int? = null,
|
||||||
|
duration: Int? = null,
|
||||||
dimensions: Dimension? = null,
|
dimensions: Dimension? = null,
|
||||||
blurhash: String? = null,
|
blurhash: String? = null,
|
||||||
originalHash: String? = null,
|
|
||||||
magnetURI: String? = null,
|
|
||||||
torrentInfoHash: String? = null,
|
|
||||||
sensitiveContent: Boolean? = null,
|
sensitiveContent: Boolean? = null,
|
||||||
|
service: String? = null,
|
||||||
altDescription: String,
|
altDescription: String,
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
onReady: (T) -> Unit,
|
onReady: (T) -> Unit,
|
||||||
) {
|
) {
|
||||||
val tags =
|
val video =
|
||||||
listOfNotNull(
|
VideoMeta(
|
||||||
arrayOf("d", dTag),
|
url,
|
||||||
arrayOf(URL, url),
|
mimeType,
|
||||||
magnetUri?.let { arrayOf(MAGNET_URI, it) },
|
blurhash,
|
||||||
mimeType?.let { arrayOf(MIME_TYPE, it) },
|
dimensions,
|
||||||
alt?.ifBlank { null }?.let { arrayOf(ALT, it) } ?: arrayOf("alt", altDescription),
|
alt,
|
||||||
hash?.let { arrayOf(HASH, it) },
|
hash,
|
||||||
size?.let { arrayOf(FILE_SIZE, it) },
|
size,
|
||||||
dimensions?.let { arrayOf(DIMENSION, it.toString()) },
|
service,
|
||||||
blurhash?.let { arrayOf(BLUR_HASH, it) },
|
emptyList(),
|
||||||
originalHash?.let { arrayOf(ORIGINAL_HASH, it) },
|
emptyList(),
|
||||||
magnetURI?.let { arrayOf(MAGNET_URI, it) },
|
|
||||||
torrentInfoHash?.let { arrayOf(TORRENT_INFOHASH, it) },
|
|
||||||
sensitiveContent?.let {
|
|
||||||
if (it) {
|
|
||||||
arrayOf("content-warning", "")
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 ?: ""
|
val content = alt ?: ""
|
||||||
signer.sign<T>(createdAt, kind, tags.toTypedArray(), content, onReady)
|
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>>,
|
tags: Array<Array<String>>,
|
||||||
content: String,
|
content: String,
|
||||||
sig: HexKey,
|
sig: HexKey,
|
||||||
) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig) {
|
) : VideoEvent(id, pubKey, createdAt, KIND, tags, content, sig),
|
||||||
|
RootScope {
|
||||||
companion object {
|
companion object {
|
||||||
const val KIND = 34235
|
const val KIND = 34235
|
||||||
const val ALT_DESCRIPTION = "Horizontal Video"
|
const val ALT_DESCRIPTION = "Horizontal Video"
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
url: String,
|
url: String,
|
||||||
magnetUri: String? = null,
|
|
||||||
mimeType: String? = null,
|
mimeType: String? = null,
|
||||||
alt: String? = null,
|
alt: String? = null,
|
||||||
hash: String? = null,
|
hash: String? = null,
|
||||||
size: String? = null,
|
size: Int? = null,
|
||||||
|
duration: Int? = null,
|
||||||
dimensions: Dimension? = null,
|
dimensions: Dimension? = null,
|
||||||
blurhash: String? = null,
|
blurhash: String? = null,
|
||||||
originalHash: String? = null,
|
|
||||||
magnetURI: String? = null,
|
|
||||||
torrentInfoHash: String? = null,
|
|
||||||
sensitiveContent: Boolean? = null,
|
sensitiveContent: Boolean? = null,
|
||||||
|
service: String? = null,
|
||||||
dTag: String = UUID.randomUUID().toString(),
|
dTag: String = UUID.randomUUID().toString(),
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
onReady: (VideoHorizontalEvent) -> Unit,
|
onReady: (VideoHorizontalEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
create(
|
create(
|
||||||
KIND,
|
kind = KIND,
|
||||||
dTag,
|
dTag = dTag,
|
||||||
url,
|
url = url,
|
||||||
magnetUri,
|
mimeType = mimeType,
|
||||||
mimeType,
|
alt = alt,
|
||||||
alt,
|
hash = hash,
|
||||||
hash,
|
size = size,
|
||||||
size,
|
duration = duration,
|
||||||
dimensions,
|
dimensions = dimensions,
|
||||||
blurhash,
|
blurhash = blurhash,
|
||||||
originalHash,
|
sensitiveContent = sensitiveContent,
|
||||||
magnetURI,
|
service = service,
|
||||||
torrentInfoHash,
|
altDescription = ALT_DESCRIPTION,
|
||||||
sensitiveContent,
|
signer = signer,
|
||||||
ALT_DESCRIPTION,
|
createdAt = createdAt,
|
||||||
signer,
|
onReady = onReady,
|
||||||
createdAt,
|
|
||||||
onReady,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,41 +42,37 @@ class VideoVerticalEvent(
|
|||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
url: String,
|
url: String,
|
||||||
magnetUri: String? = null,
|
|
||||||
mimeType: String? = null,
|
mimeType: String? = null,
|
||||||
alt: String? = null,
|
alt: String? = null,
|
||||||
hash: String? = null,
|
hash: String? = null,
|
||||||
size: String? = null,
|
size: Int? = null,
|
||||||
|
duration: Int? = null,
|
||||||
dimensions: Dimension? = null,
|
dimensions: Dimension? = null,
|
||||||
blurhash: String? = null,
|
blurhash: String? = null,
|
||||||
originalHash: String? = null,
|
|
||||||
magnetURI: String? = null,
|
|
||||||
torrentInfoHash: String? = null,
|
|
||||||
sensitiveContent: Boolean? = null,
|
sensitiveContent: Boolean? = null,
|
||||||
|
service: String? = null,
|
||||||
dTag: String = UUID.randomUUID().toString(),
|
dTag: String = UUID.randomUUID().toString(),
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
createdAt: Long = TimeUtils.now(),
|
createdAt: Long = TimeUtils.now(),
|
||||||
onReady: (VideoVerticalEvent) -> Unit,
|
onReady: (VideoVerticalEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
create(
|
create(
|
||||||
KIND,
|
kind = KIND,
|
||||||
dTag,
|
dTag = dTag,
|
||||||
url,
|
url = url,
|
||||||
magnetUri,
|
mimeType = mimeType,
|
||||||
mimeType,
|
alt = alt,
|
||||||
alt,
|
hash = hash,
|
||||||
hash,
|
size = size,
|
||||||
size,
|
duration = duration,
|
||||||
dimensions,
|
dimensions = dimensions,
|
||||||
blurhash,
|
blurhash = blurhash,
|
||||||
originalHash,
|
sensitiveContent = sensitiveContent,
|
||||||
magnetURI,
|
service = service,
|
||||||
torrentInfoHash,
|
altDescription = ALT_DESCRIPTION,
|
||||||
sensitiveContent,
|
signer = signer,
|
||||||
ALT_DESCRIPTION,
|
createdAt = createdAt,
|
||||||
signer,
|
onReady = onReady,
|
||||||
createdAt,
|
|
||||||
onReady,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user