diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index 47580cabf..c5c576b4a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -179,6 +179,8 @@ import com.vitorpamplona.quartz.nip90Dvms.NIP90UserDiscoveryResponseEvent import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent +import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent +import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent import com.vitorpamplona.quartz.utils.Hex import com.vitorpamplona.quartz.utils.LargeCache @@ -599,6 +601,18 @@ object LocalCache : ILocalCache { wasVerified: Boolean, ) = consumeRegularEvent(event, relay, wasVerified) + fun consume( + event: VoiceEvent, + relay: NormalizedRelayUrl?, + wasVerified: Boolean, + ) = consumeRegularEvent(event, relay, wasVerified) + + fun consume( + event: VoiceReplyEvent, + relay: NormalizedRelayUrl?, + wasVerified: Boolean, + ) = consumeRegularEvent(event, relay, wasVerified) + @Suppress("DEPRECATION") fun consume( event: TorrentCommentEvent, @@ -2816,6 +2830,8 @@ object LocalCache : ILocalCache { is TrustedRelayListEvent -> consume(event, relay, wasVerified) is VideoHorizontalEvent -> consume(event, relay, wasVerified) is VideoVerticalEvent -> consume(event, relay, wasVerified) + is VoiceEvent -> consume(event, relay, wasVerified) + is VoiceReplyEvent -> consume(event, relay, wasVerified) is WikiNoteEvent -> consume(event, relay, wasVerified) else -> { Log.w("Event Not Supported", event.toJson()) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/RenderVideoPlayer.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/RenderVideoPlayer.kt index 458ad57f4..1a9e66706 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/RenderVideoPlayer.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/RenderVideoPlayer.kt @@ -40,7 +40,6 @@ import com.vitorpamplona.amethyst.service.playback.composable.controls.RenderCon import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.LoadedMediaItem import com.vitorpamplona.amethyst.service.playback.composable.wavefront.Waveform import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.quartz.experimental.audio.header.tags.WaveformTag @Composable @OptIn(UnstableApi::class) @@ -50,7 +49,7 @@ fun RenderVideoPlayer( thumbData: VideoThumb?, showControls: Boolean = true, contentScale: ContentScale, - waveform: WaveformTag? = null, + waveform: WaveformData? = null, borderModifier: Modifier, videoModifier: Modifier, onControllerVisibilityChanged: ((Boolean) -> Unit)? = null, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/VideoView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/VideoView.kt index 011d82504..8b31f9892 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/VideoView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/VideoView.kt @@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.size import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment @@ -42,9 +43,13 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.Size75dp import com.vitorpamplona.amethyst.ui.theme.imageModifier import com.vitorpamplona.amethyst.ui.theme.videoGalleryModifier -import com.vitorpamplona.quartz.experimental.audio.header.tags.WaveformTag import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag +@Immutable +class WaveformData( + val wave: List, +) + @Composable fun VideoView( videoUri: String, @@ -54,7 +59,7 @@ fun VideoView( roundedCorner: Boolean, gallery: Boolean = false, contentScale: ContentScale, - waveform: WaveformTag? = null, + waveform: WaveformData? = null, artworkUri: String? = null, authorName: String? = null, dimensions: DimensionTag? = null, @@ -85,7 +90,7 @@ fun VideoView( thumb: VideoThumb? = null, borderModifier: Modifier, contentScale: ContentScale, - waveform: WaveformTag? = null, + waveform: WaveformData? = null, artworkUri: String? = null, authorName: String? = null, dimensions: DimensionTag? = null, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/VideoViewInner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/VideoViewInner.kt index 61eac519f..03aca3d3d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/VideoViewInner.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/VideoViewInner.kt @@ -29,7 +29,6 @@ import androidx.compose.ui.layout.ContentScale import com.vitorpamplona.amethyst.service.playback.composable.mainVideo.VideoPlayerActiveMutex import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.quartz.experimental.audio.header.tags.WaveformTag public val DEFAULT_MUTED_SETTING = mutableStateOf(true) @@ -43,7 +42,7 @@ fun VideoViewInner( showControls: Boolean = true, contentScale: ContentScale, borderModifier: Modifier, - waveform: WaveformTag? = null, + waveform: WaveformData? = null, artworkUri: String? = null, authorName: String? = null, nostrUriCallback: String? = null, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/wavefront/Waveform.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/wavefront/Waveform.kt index 3307cbf99..c0ef0c865 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/wavefront/Waveform.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/playback/composable/wavefront/Waveform.kt @@ -38,15 +38,15 @@ import androidx.media3.common.Player import androidx.media3.session.MediaController import com.linc.audiowaveform.infiniteLinearGradient import com.vitorpamplona.amethyst.service.playback.composable.MediaControllerState +import com.vitorpamplona.amethyst.service.playback.composable.WaveformData import com.vitorpamplona.amethyst.ui.components.AudioWaveformReadOnly -import com.vitorpamplona.quartz.experimental.audio.header.tags.WaveformTag import kotlinx.coroutines.delay import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.flow @Composable fun Waveform( - waveform: WaveformTag, + waveform: WaveformData, mediaControllerState: MediaControllerState, modifier: Modifier, ) { @@ -94,7 +94,7 @@ private fun pollCurrentDuration(controller: MediaController) = @Composable fun DrawWaveform( - waveform: WaveformTag, + waveform: WaveformData, waveformProgress: MutableFloatState, modifier: Modifier, ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/VoiceTrack.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/VoiceTrack.kt new file mode 100644 index 000000000..523314093 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/VoiceTrack.kt @@ -0,0 +1,105 @@ +/** + * 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.amethyst.ui.note.types + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.playback.composable.VideoView +import com.vitorpamplona.amethyst.service.playback.composable.WaveformData +import com.vitorpamplona.amethyst.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags +import com.vitorpamplona.quartz.nip14Subject.subject +import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent + +@Composable +fun RenderVoiceTrack( + note: Note, + contentScale: ContentScale, + accountViewModel: AccountViewModel, + nav: INav, +) { + val noteEvent = note.event as? BaseVoiceEvent ?: return + + AudioVoiceHeader(noteEvent, note, contentScale, accountViewModel, nav) +} + +@Composable +fun AudioVoiceHeader( + noteEvent: BaseVoiceEvent, + note: Note, + contentScale: ContentScale, + accountViewModel: AccountViewModel, + nav: INav, +) { + val waveform = + remember(noteEvent) { + noteEvent + .iMetaTags() + .firstOrNull() + ?.waveform + ?.let { WaveformData(it) } + } + val media = + remember(noteEvent) { + noteEvent.content.ifBlank { null } ?: noteEvent.iMetaTags().firstOrNull()?.url + } + + if (media == null) return + + Column(modifier = Modifier.fillMaxWidth().padding(top = 5.dp), horizontalAlignment = Alignment.CenterHorizontally) { + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + VideoView( + videoUri = media, + mimeType = null, + waveform = waveform, + title = noteEvent.subject(), + authorName = note.author?.toBestDisplayName(), + roundedCorner = true, + contentScale = contentScale, + accountViewModel = accountViewModel, + nostrUriCallback = note.toNostrUri(), + ) + } + + val callbackUri = remember(note) { note.toNostrUri() } + + if (noteEvent.hasHashtags()) { + Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + DisplayUncitedHashtags(noteEvent, callbackUri, accountViewModel, nav) + } + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/dal/HomeConversationsFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/dal/HomeConversationsFeedFilter.kt index 5c940a8fa..0ff7d6fab 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/dal/HomeConversationsFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/dal/HomeConversationsFeedFilter.kt @@ -34,6 +34,7 @@ import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent import com.vitorpamplona.quartz.nip22Comments.CommentEvent import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent import com.vitorpamplona.quartz.nip53LiveActivities.chat.LiveActivitiesChatMessageEvent +import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent class HomeConversationsFeedFilter( val account: Account, @@ -79,6 +80,7 @@ class HomeConversationsFeedFilter( event is PollNoteEvent || event is ChannelMessageEvent || event is CommentEvent || + event is VoiceReplyEvent || event is LiveActivitiesChatMessageEvent ) && filterParams.match(event) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/dal/HomeNewThreadFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/dal/HomeNewThreadFeedFilter.kt index e5a0441fe..806516e96 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/dal/HomeNewThreadFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/dal/HomeNewThreadFeedFilter.kt @@ -40,6 +40,7 @@ import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent +import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent class HomeNewThreadFeedFilter( val account: Account, @@ -100,6 +101,7 @@ class HomeNewThreadFeedFilter( noteEvent is InteractiveStoryPrologueEvent || noteEvent is CommentEvent || noteEvent is AudioTrackEvent || + noteEvent is VoiceEvent || noteEvent is AudioHeaderEvent ) && filterParams.match(noteEvent, it.relays) && diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/nip01Core/FilterHomePostsByGeohashes.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/nip01Core/FilterHomePostsByGeohashes.kt index 95a28c771..a5e07906d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/nip01Core/FilterHomePostsByGeohashes.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/nip01Core/FilterHomePostsByGeohashes.kt @@ -35,6 +35,8 @@ import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent +import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent +import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent import kotlin.collections.flatten val HomePostsByGeohashKinds = @@ -48,6 +50,8 @@ val HomePostsByGeohashKinds = InteractiveStoryPrologueEvent.KIND, WikiNoteEvent.KIND, CommentEvent.KIND, + VoiceEvent.KIND, + VoiceReplyEvent.KIND, ) fun filterHomePostsByGeohashes( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/nip01Core/FilterHomePostsByHashtags.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/nip01Core/FilterHomePostsByHashtags.kt index b94679fb4..8e9eddd2b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/nip01Core/FilterHomePostsByHashtags.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/nip01Core/FilterHomePostsByHashtags.kt @@ -37,6 +37,8 @@ import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent +import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent +import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent val HomePostsBuHashtagsKinds = listOf( @@ -50,6 +52,8 @@ val HomePostsBuHashtagsKinds = InteractiveStoryPrologueEvent.KIND, CommentEvent.KIND, WikiNoteEvent.KIND, + VoiceEvent.KIND, + VoiceReplyEvent.KIND, ) fun filterHomePostsByHashtags( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/nip65Follows/FilterHomePostsByAuthors.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/nip65Follows/FilterHomePostsByAuthors.kt index 80c915ead..b9de07388 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/nip65Follows/FilterHomePostsByAuthors.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/datasource/nip65Follows/FilterHomePostsByAuthors.kt @@ -40,6 +40,8 @@ import com.vitorpamplona.quartz.nip53LiveActivities.streaming.LiveActivitiesEven import com.vitorpamplona.quartz.nip54Wiki.WikiNoteEvent import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent +import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent +import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent import kotlin.math.max val HomePostsNewThreadKinds = @@ -61,6 +63,8 @@ val HomePostsConversationKinds = CommentEvent.KIND, LiveActivitiesEvent.KIND, EphemeralChatEvent.KIND, + VoiceEvent.KIND, + VoiceReplyEvent.KIND, ) fun filterNewHomePostsByAuthors( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt index ae82881ca..63861a764 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/threadview/ThreadFeedView.kt @@ -229,6 +229,7 @@ import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent +import com.vitorpamplona.quartz.nipA0VoiceMessages.BaseVoiceEvent import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runBlocking @@ -547,6 +548,8 @@ private fun FullBleedNoteCompose( VideoDisplay(baseNote, makeItShort = false, canPreview = true, backgroundColor = backgroundColor, ContentScale.FillWidth, accountViewModel = accountViewModel, nav = nav) } else if (noteEvent is PictureEvent) { PictureDisplay(baseNote, roundedCorner = true, ContentScale.FillWidth, PaddingValues(vertical = Size5dp), backgroundColor, accountViewModel = accountViewModel, nav) + } else if (noteEvent is BaseVoiceEvent) { + VoiceEventDisplay(baseNote, makeItShort = false, canPreview = true, backgroundColor = backgroundColor, ContentScale.FillWidth, accountViewModel = accountViewModel, nav = nav) } else if (noteEvent is FileHeaderEvent) { FileHeaderDisplay(baseNote, roundedCorner = true, ContentScale.FillWidth, accountViewModel = accountViewModel) } else if (noteEvent is FileStorageHeaderEvent) { diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/EventFactory.kt b/quartz/src/main/java/com/vitorpamplona/quartz/EventFactory.kt index 2a0fa369e..82a941199 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/EventFactory.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/EventFactory.kt @@ -125,6 +125,8 @@ import com.vitorpamplona.quartz.nip94FileMetadata.FileHeaderEvent import com.vitorpamplona.quartz.nip96FileStorage.config.FileServersEvent import com.vitorpamplona.quartz.nip98HttpAuth.HTTPAuthorizationEvent import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent +import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceEvent +import com.vitorpamplona.quartz.nipA0VoiceMessages.VoiceReplyEvent import com.vitorpamplona.quartz.nipB7Blossom.BlossomAuthorizationEvent import com.vitorpamplona.quartz.nipB7Blossom.BlossomServersEvent @@ -282,6 +284,8 @@ class EventFactory { TrustedRelayListEvent.KIND -> TrustedRelayListEvent(id, pubKey, createdAt, tags, content, sig) VideoHorizontalEvent.KIND -> VideoHorizontalEvent(id, pubKey, createdAt, tags, content, sig) VideoVerticalEvent.KIND -> VideoVerticalEvent(id, pubKey, createdAt, tags, content, sig) + VoiceEvent.KIND -> VoiceEvent(id, pubKey, createdAt, tags, content, sig) + VoiceReplyEvent.KIND -> VoiceReplyEvent(id, pubKey, createdAt, tags, content, sig) WikiNoteEvent.KIND -> WikiNoteEvent(id, pubKey, createdAt, tags, content, sig) else -> { factories[kind]?.build(id, pubKey, createdAt, tags, content, sig) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/AudioMeta.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/AudioMeta.kt new file mode 100644 index 000000000..c74d41ff6 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/AudioMeta.kt @@ -0,0 +1,51 @@ +/** + * 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.nipA0VoiceMessages + +import com.vitorpamplona.quartz.nip92IMeta.IMetaTag +import com.vitorpamplona.quartz.nip92IMeta.IMetaTagBuilder +import com.vitorpamplona.quartz.nipA0VoiceMessages.tags.WaveformTag + +data class AudioMeta( + val url: String, + val hash: String? = null, + val duration: Int? = null, + val waveform: List? = null, +) { + fun toIMetaArray(): Array = + IMetaTagBuilder(url) + .apply { + hash?.let { hash(it) } + duration?.let { duration(it) } + waveform?.let { waveform(it) } + }.build() + .toTagArray() + + companion object { + fun parse(iMeta: IMetaTag): AudioMeta = + AudioMeta( + iMeta.url, + iMeta.hash()?.firstOrNull(), + iMeta.duration()?.firstOrNull()?.toIntOrNull(), + iMeta.waveform()?.firstOrNull()?.let { WaveformTag.parseWave(it) }, + ) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/BaseVoiceEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/BaseVoiceEvent.kt new file mode 100644 index 000000000..6fb98c5b9 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/BaseVoiceEvent.kt @@ -0,0 +1,56 @@ +/** + * 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.nipA0VoiceMessages + +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.signers.eventTemplate +import com.vitorpamplona.quartz.nip31Alts.alt +import com.vitorpamplona.quartz.nip92IMeta.imetas +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +open class BaseVoiceEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + kind: Int, + tags: Array>, + content: String, + sig: HexKey, +) : Event(id, pubKey, createdAt, kind, tags, content, sig) { + fun iMetaTags() = imetas().map { AudioMeta.parse(it) } + + companion object { + fun build( + voiceMessage: AudioMeta, + kind: Int, + alt: String, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = eventTemplate(kind, voiceMessage.url, createdAt) { + alt(alt) + initializer() + } + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagBuilderExt.kt new file mode 100644 index 000000000..226d110ec --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagBuilderExt.kt @@ -0,0 +1,36 @@ +/** + * 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.nipA0VoiceMessages + +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip92IMeta.IMetaTagBuilder +import com.vitorpamplona.quartz.nipA0VoiceMessages.tags.DurationTag +import com.vitorpamplona.quartz.nipA0VoiceMessages.tags.HashSha256Tag +import com.vitorpamplona.quartz.nipA0VoiceMessages.tags.WaveformTag + +/** + * Contains the IMeta tags that are used by Picture events. + */ +fun IMetaTagBuilder.hash(hash: HexKey) = add(HashSha256Tag.TAG_NAME, hash) + +fun IMetaTagBuilder.duration(size: Int) = add(DurationTag.TAG_NAME, size.toString()) + +fun IMetaTagBuilder.waveform(wave: List) = add(WaveformTag.TAG_NAME, WaveformTag.assembleWave(wave)) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagExt.kt new file mode 100644 index 000000000..d65ab34e9 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/IMetaTagExt.kt @@ -0,0 +1,32 @@ +/** + * 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.nipA0VoiceMessages + +import com.vitorpamplona.quartz.nip92IMeta.IMetaTag +import com.vitorpamplona.quartz.nipA0VoiceMessages.tags.DurationTag +import com.vitorpamplona.quartz.nipA0VoiceMessages.tags.HashSha256Tag +import com.vitorpamplona.quartz.nipA0VoiceMessages.tags.WaveformTag + +fun IMetaTag.hash() = properties.get(HashSha256Tag.TAG_NAME) + +fun IMetaTag.duration() = properties.get(DurationTag.TAG_NAME) + +fun IMetaTag.waveform() = properties.get(WaveformTag.TAG_NAME) diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/TagArrayBuilderExt.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/TagArrayBuilderExt.kt new file mode 100644 index 000000000..1854a71bf --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/TagArrayBuilderExt.kt @@ -0,0 +1,42 @@ +/** + * 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.nipA0VoiceMessages + +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip94FileMetadata.tags.HashSha256Tag + +fun TagArrayBuilder.audioIMeta( + url: String, + hash: String? = null, + duration: Int? = null, + waveform: List? = null, +) = audioIMeta(AudioMeta(url, hash, duration, waveform)) + +fun TagArrayBuilder.audioIMeta(imeta: AudioMeta): TagArrayBuilder { + add(imeta.toIMetaArray()) + imeta.hash?.let { add(HashSha256Tag.assemble(it)) } + return this +} + +fun TagArrayBuilder.audioIMeta(audioUrls: List): TagArrayBuilder { + audioUrls.forEach { audioIMeta(it) } + return this +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceEvent.kt new file mode 100644 index 000000000..a43185712 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceEvent.kt @@ -0,0 +1,47 @@ +/** + * 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.nipA0VoiceMessages + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +class VoiceEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : BaseVoiceEvent(id, pubKey, createdAt, KIND, tags, content, sig) { + companion object { + const val KIND = 1222 + const val ALT_DESCRIPTION = "Voice message" + + fun build( + voiceMessage: AudioMeta, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = build(voiceMessage, KIND, ALT_DESCRIPTION, createdAt, initializer) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceReplyEvent.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceReplyEvent.kt new file mode 100644 index 000000000..5d43098cd --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/VoiceReplyEvent.kt @@ -0,0 +1,47 @@ +/** + * 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.nipA0VoiceMessages + +import androidx.compose.runtime.Immutable +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.utils.TimeUtils + +@Immutable +class VoiceReplyEvent( + id: HexKey, + pubKey: HexKey, + createdAt: Long, + tags: Array>, + content: String, + sig: HexKey, +) : BaseVoiceEvent(id, pubKey, createdAt, KIND, tags, content, sig) { + companion object { + const val KIND = 1244 + const val ALT_DESCRIPTION = "Voice reply" + + fun build( + voiceMessage: AudioMeta, + createdAt: Long = TimeUtils.now(), + initializer: TagArrayBuilder.() -> Unit = {}, + ) = build(voiceMessage, KIND, ALT_DESCRIPTION, createdAt, initializer) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/DurationTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/DurationTag.kt new file mode 100644 index 000000000..af56d81f2 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/DurationTag.kt @@ -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.nipA0VoiceMessages.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class DurationTag { + companion object { + const val TAG_NAME = "duration" + + @JvmStatic + fun parse(tag: Array): Int? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + return tag[1].toIntOrNull() + } + + @JvmStatic + fun assemble(seconds: Int) = arrayOf(TAG_NAME, seconds.toString()) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/HashSha256Tag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/HashSha256Tag.kt new file mode 100644 index 000000000..9f42b8ad7 --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/HashSha256Tag.kt @@ -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.nipA0VoiceMessages.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class HashSha256Tag { + companion object { + const val TAG_NAME = "sha256" + + @JvmStatic + fun parse(tag: Array): 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(hash: String) = arrayOf(TAG_NAME, hash) + } +} diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/WaveformTag.kt b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/WaveformTag.kt new file mode 100644 index 000000000..d4366fcba --- /dev/null +++ b/quartz/src/main/java/com/vitorpamplona/quartz/nipA0VoiceMessages/tags/WaveformTag.kt @@ -0,0 +1,60 @@ +/** + * 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.nipA0VoiceMessages.tags + +import com.vitorpamplona.quartz.nip01Core.core.has +import com.vitorpamplona.quartz.utils.ensure + +class WaveformTag( + val wave: List, +) { + fun toTagArray() = assemble(wave) + + companion object { + const val TAG_NAME = "waveform" + + @JvmStatic + fun parse(tag: Array): WaveformTag? = parseWave(tag)?.let { WaveformTag(it) } + + @JvmStatic + fun parseWave(tag: Array): List? { + ensure(tag.has(1)) { return null } + ensure(tag[0] == TAG_NAME) { return null } + ensure(tag[1].isNotEmpty()) { return null } + + val wave = tag[1].split(" ").mapNotNull { it.toIntOrNull() } + if (wave.isEmpty()) return null + return wave + } + + fun parseWave(wave: String): List? { + val wave = wave.split(" ").mapNotNull { it.toIntOrNull() } + if (wave.isEmpty()) return null + return wave + } + + @JvmStatic + fun assembleWave(wave: List) = wave.joinToString(" ") + + @JvmStatic + fun assemble(wave: List) = arrayOf(TAG_NAME, assembleWave(wave)) + } +}