Merge pull request #2817 from davotoula/fix/gallery-forward-thumbhash
fix(gallery): forward imeta hashes through gallery
This commit is contained in:
+11
-1
@@ -70,6 +70,7 @@ fun VideoView(
|
|||||||
alwaysShowVideo: Boolean = false,
|
alwaysShowVideo: Boolean = false,
|
||||||
thumbhash: String? = null,
|
thumbhash: String? = null,
|
||||||
isLiveStream: Boolean = false,
|
isLiveStream: Boolean = false,
|
||||||
|
hash: String? = null,
|
||||||
) {
|
) {
|
||||||
val borderModifier =
|
val borderModifier =
|
||||||
if (roundedCorner) {
|
if (roundedCorner) {
|
||||||
@@ -80,7 +81,7 @@ fun VideoView(
|
|||||||
Modifier
|
Modifier
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoView(videoUri, mimeType, title, thumb, borderModifier, contentScale, waveform, artworkUri, authorName, dimensions, blurhash, nostrUriCallback, onDialog, alwaysShowVideo, accountViewModel = accountViewModel, thumbhash = thumbhash, isLiveStream = isLiveStream)
|
VideoView(videoUri, mimeType, title, thumb, borderModifier, contentScale, waveform, artworkUri, authorName, dimensions, blurhash, nostrUriCallback, onDialog, alwaysShowVideo, accountViewModel = accountViewModel, thumbhash = thumbhash, isLiveStream = isLiveStream, hash = hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -103,6 +104,7 @@ fun VideoView(
|
|||||||
isLiveStream: Boolean = false,
|
isLiveStream: Boolean = false,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
thumbhash: String? = null,
|
thumbhash: String? = null,
|
||||||
|
hash: String? = null,
|
||||||
) {
|
) {
|
||||||
val initialAutoStart = if (alwaysShowVideo) true else accountViewModel.settings.startVideoPlayback()
|
val initialAutoStart = if (alwaysShowVideo) true else accountViewModel.settings.startVideoPlayback()
|
||||||
// Reset the manual-show toggle when the video URI changes so a recycled feed slot
|
// Reset the manual-show toggle when the video URI changes so a recycled feed slot
|
||||||
@@ -160,6 +162,10 @@ fun VideoView(
|
|||||||
isLiveStream = isLiveStream,
|
isLiveStream = isLiveStream,
|
||||||
onZoom = onDialog,
|
onZoom = onDialog,
|
||||||
hasBlurhash = false,
|
hasBlurhash = false,
|
||||||
|
blurhash = blurhash,
|
||||||
|
dim = dimensions,
|
||||||
|
hash = hash,
|
||||||
|
thumbhash = thumbhash,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
showControls = showControls,
|
showControls = showControls,
|
||||||
)
|
)
|
||||||
@@ -207,6 +213,10 @@ fun VideoView(
|
|||||||
isLiveStream = isLiveStream,
|
isLiveStream = isLiveStream,
|
||||||
onZoom = onDialog,
|
onZoom = onDialog,
|
||||||
hasBlurhash = true,
|
hasBlurhash = true,
|
||||||
|
blurhash = blurhash,
|
||||||
|
dim = dimensions,
|
||||||
|
hash = hash,
|
||||||
|
thumbhash = thumbhash,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
showControls = showControls,
|
showControls = showControls,
|
||||||
)
|
)
|
||||||
|
|||||||
+9
@@ -31,6 +31,7 @@ import com.vitorpamplona.amethyst.service.playback.composable.controls.VideoQual
|
|||||||
import com.vitorpamplona.amethyst.service.playback.composable.mainVideo.VideoPlayerActiveMutex
|
import com.vitorpamplona.amethyst.service.playback.composable.mainVideo.VideoPlayerActiveMutex
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem
|
import com.vitorpamplona.amethyst.service.playback.composable.mediaitem.GetMediaItem
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||||
|
|
||||||
val DEFAULT_MUTED_SETTING = mutableStateOf(true)
|
val DEFAULT_MUTED_SETTING = mutableStateOf(true)
|
||||||
|
|
||||||
@@ -54,6 +55,10 @@ fun VideoViewInner(
|
|||||||
onZoom: (() -> Unit)? = null,
|
onZoom: (() -> Unit)? = null,
|
||||||
hasBlurhash: Boolean = false,
|
hasBlurhash: Boolean = false,
|
||||||
isFullscreen: Boolean = false,
|
isFullscreen: Boolean = false,
|
||||||
|
blurhash: String? = null,
|
||||||
|
dim: DimensionTag? = null,
|
||||||
|
hash: String? = null,
|
||||||
|
thumbhash: String? = null,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
) {
|
) {
|
||||||
// keeps a copy of the value to avoid recompositions here when the DEFAULT value changes
|
// keeps a copy of the value to avoid recompositions here when the DEFAULT value changes
|
||||||
@@ -76,6 +81,10 @@ fun VideoViewInner(
|
|||||||
keepPlaying = true,
|
keepPlaying = true,
|
||||||
waveformData = waveform,
|
waveformData = waveform,
|
||||||
isLiveStream = isLiveStream,
|
isLiveStream = isLiveStream,
|
||||||
|
blurhash = blurhash,
|
||||||
|
dim = dim,
|
||||||
|
hash = hash,
|
||||||
|
thumbhash = thumbhash,
|
||||||
) { mediaItem ->
|
) { mediaItem ->
|
||||||
GetVideoController(
|
GetVideoController(
|
||||||
mediaItem = mediaItem,
|
mediaItem = mediaItem,
|
||||||
|
|||||||
+7
-3
@@ -303,9 +303,9 @@ fun RenderTopButtons(
|
|||||||
popupExpanded = shareDialogVisible,
|
popupExpanded = shareDialogVisible,
|
||||||
videoUri = mediaData.videoUri,
|
videoUri = mediaData.videoUri,
|
||||||
postNostrUri = mediaData.callbackUri,
|
postNostrUri = mediaData.callbackUri,
|
||||||
blurhash = null,
|
blurhash = mediaData.blurhash,
|
||||||
dim = null,
|
dim = mediaData.dim,
|
||||||
hash = null,
|
hash = mediaData.hash,
|
||||||
mimeType = mediaData.mimeType,
|
mimeType = mediaData.mimeType,
|
||||||
onDismiss = { shareDialogVisible.value = false },
|
onDismiss = { shareDialogVisible.value = false },
|
||||||
content =
|
content =
|
||||||
@@ -316,6 +316,10 @@ fun RenderTopButtons(
|
|||||||
authorName = mediaData.authorName,
|
authorName = mediaData.authorName,
|
||||||
description = mediaData.title,
|
description = mediaData.title,
|
||||||
uri = mediaData.callbackUri,
|
uri = mediaData.callbackUri,
|
||||||
|
blurhash = mediaData.blurhash,
|
||||||
|
dim = mediaData.dim,
|
||||||
|
hash = mediaData.hash,
|
||||||
|
thumbhash = mediaData.thumbhash,
|
||||||
),
|
),
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
)
|
)
|
||||||
|
|||||||
+9
@@ -25,6 +25,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import com.vitorpamplona.amethyst.commons.compose.produceCachedState
|
import com.vitorpamplona.amethyst.commons.compose.produceCachedState
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||||
|
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||||
|
|
||||||
val mediaItemCache = MediaItemCache()
|
val mediaItemCache = MediaItemCache()
|
||||||
|
|
||||||
@@ -41,6 +42,10 @@ fun GetMediaItem(
|
|||||||
keepPlaying: Boolean = false,
|
keepPlaying: Boolean = false,
|
||||||
waveformData: WaveformData? = null,
|
waveformData: WaveformData? = null,
|
||||||
isLiveStream: Boolean = false,
|
isLiveStream: Boolean = false,
|
||||||
|
blurhash: String? = null,
|
||||||
|
dim: DimensionTag? = null,
|
||||||
|
hash: String? = null,
|
||||||
|
thumbhash: String? = null,
|
||||||
inner: @Composable (LoadedMediaItem) -> Unit,
|
inner: @Composable (LoadedMediaItem) -> Unit,
|
||||||
) {
|
) {
|
||||||
val data =
|
val data =
|
||||||
@@ -57,6 +62,10 @@ fun GetMediaItem(
|
|||||||
keepPlaying = keepPlaying,
|
keepPlaying = keepPlaying,
|
||||||
waveformData = waveformData,
|
waveformData = waveformData,
|
||||||
isLiveStream = isLiveStream,
|
isLiveStream = isLiveStream,
|
||||||
|
blurhash = blurhash,
|
||||||
|
dim = dim,
|
||||||
|
hash = hash,
|
||||||
|
thumbhash = thumbhash,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.service.playback.composable.mediaitem
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
import com.vitorpamplona.amethyst.service.playback.composable.WaveformData
|
||||||
|
import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class MediaItemData(
|
data class MediaItemData(
|
||||||
@@ -40,6 +41,13 @@ data class MediaItemData(
|
|||||||
// (e.g. multi-rendition NIP-71 videos) must leave this false so that
|
// (e.g. multi-rendition NIP-71 videos) must leave this false so that
|
||||||
// ExoPlayer's SimpleCache can cache their immutable segments.
|
// ExoPlayer's SimpleCache can cache their immutable segments.
|
||||||
val isLiveStream: Boolean = false,
|
val isLiveStream: Boolean = false,
|
||||||
|
// Source-imeta visual fields — carried through so the player's "Add to Gallery" /
|
||||||
|
// "Share" actions can publish a richer ProfileGalleryEntryEvent. Optional; null when
|
||||||
|
// the surface that constructed this MediaItemData didn't have them.
|
||||||
|
val blurhash: String? = null,
|
||||||
|
val dim: DimensionTag? = null,
|
||||||
|
val hash: String? = null,
|
||||||
|
val thumbhash: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|||||||
+14
-3
@@ -48,6 +48,8 @@ data class PreviewHashes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
object PreviewMetadataCalculator {
|
object PreviewMetadataCalculator {
|
||||||
|
private const val LOG_TAG = "PreviewMetadataCalc"
|
||||||
|
|
||||||
private fun isImage(mimeType: String?) = mimeType?.startsWith("image/", ignoreCase = true) == true
|
private fun isImage(mimeType: String?) = mimeType?.startsWith("image/", ignoreCase = true) == true
|
||||||
|
|
||||||
private fun isVideo(mimeType: String?) = mimeType?.startsWith("video/", ignoreCase = true) == true
|
private fun isVideo(mimeType: String?) = mimeType?.startsWith("video/", ignoreCase = true) == true
|
||||||
@@ -117,7 +119,7 @@ object PreviewMetadataCalculator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.w("PreviewMetadataCalc", "Failed to compute metadata from uri", e)
|
Log.w(LOG_TAG, "Failed to compute metadata from uri", e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,8 +135,14 @@ object PreviewMetadataCalculator {
|
|||||||
private fun processBitmap(bitmap: Bitmap?): PreviewHashes =
|
private fun processBitmap(bitmap: Bitmap?): PreviewHashes =
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
try {
|
try {
|
||||||
val blurhash = runCatching { BlurhashWrapper(bitmap.toBlurhash()) }.getOrNull()
|
val blurhash =
|
||||||
val thumbhash = runCatching { ThumbhashWrapper(bitmap.toThumbhash()) }.getOrNull()
|
runCatching { BlurhashWrapper(bitmap.toBlurhash()) }
|
||||||
|
.onFailure { Log.w(LOG_TAG, "blurhash generation failed", it) }
|
||||||
|
.getOrNull()
|
||||||
|
val thumbhash =
|
||||||
|
runCatching { ThumbhashWrapper(bitmap.toThumbhash()) }
|
||||||
|
.onFailure { Log.w(LOG_TAG, "thumbhash generation failed", it) }
|
||||||
|
.getOrNull()
|
||||||
PreviewHashes(
|
PreviewHashes(
|
||||||
blurhash = blurhash,
|
blurhash = blurhash,
|
||||||
thumbhash = thumbhash,
|
thumbhash = thumbhash,
|
||||||
@@ -153,6 +161,9 @@ object PreviewMetadataCalculator {
|
|||||||
): PreviewHashes {
|
): PreviewHashes {
|
||||||
val dim = retriever.prepareDimFromVideo() ?: dimPrecomputed
|
val dim = retriever.prepareDimFromVideo() ?: dimPrecomputed
|
||||||
val thumb = retriever.getThumbnail()
|
val thumb = retriever.getThumbnail()
|
||||||
|
if (thumb == null) {
|
||||||
|
Log.w(LOG_TAG) { "video frame extraction returned null; no blurhash/thumbhash will be generated" }
|
||||||
|
}
|
||||||
val hashes = processBitmap(thumb)
|
val hashes = processBitmap(thumb)
|
||||||
val finalDim = if (dim?.hasSize() == true) dim else hashes.dim
|
val finalDim = if (dim?.hasSize() == true) dim else hashes.dim
|
||||||
return hashes.copy(dim = finalDim)
|
return hashes.copy(dim = finalDim)
|
||||||
|
|||||||
+5
-1
@@ -226,6 +226,7 @@ fun ZoomableContentView(
|
|||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
thumbhash = content.thumbhash,
|
thumbhash = content.thumbhash,
|
||||||
isLiveStream = content.isLiveStream,
|
isLiveStream = content.isLiveStream,
|
||||||
|
hash = content.hash,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -961,7 +962,10 @@ fun ShareMediaAction(
|
|||||||
// still — the .m3u8 URL itself is a text manifest. Only
|
// still — the .m3u8 URL itself is a text manifest. Only
|
||||||
// MediaUrlVideo carries an artworkUri.
|
// MediaUrlVideo carries an artworkUri.
|
||||||
val posterUrl = (content as? MediaUrlVideo)?.artworkUri
|
val posterUrl = (content as? MediaUrlVideo)?.artworkUri
|
||||||
accountViewModel.addMediaToGallery(n19.hex, videoUri, n19.relay.getOrNull(0), blurhash, dim, hash, mimeType, image = posterUrl)
|
val thumbhashFromContent =
|
||||||
|
(content as? MediaUrlVideo)?.thumbhash
|
||||||
|
?: (content as? MediaUrlImage)?.thumbhash
|
||||||
|
accountViewModel.addMediaToGallery(n19.hex, videoUri, n19.relay.getOrNull(0), blurhash, dim, hash, mimeType, thumbhash = thumbhashFromContent, image = posterUrl)
|
||||||
accountViewModel.toastManager.toast(R.string.media_added, R.string.media_added_to_profile_gallery)
|
accountViewModel.toastManager.toast(R.string.media_added, R.string.media_added_to_profile_gallery)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -896,8 +896,9 @@ class AccountViewModel(
|
|||||||
dim: DimensionTag?,
|
dim: DimensionTag?,
|
||||||
hash: String?,
|
hash: String?,
|
||||||
mimeType: String?,
|
mimeType: String?,
|
||||||
|
thumbhash: String? = null,
|
||||||
image: String? = null,
|
image: String? = null,
|
||||||
) = launchSigner { account.addToGallery(hex, url, relay, blurhash, dim, hash, mimeType, image = image) }
|
) = launchSigner { account.addToGallery(hex, url, relay, blurhash, dim, hash, mimeType, thumbhash = thumbhash, image = image) }
|
||||||
|
|
||||||
fun removeFromMediaGallery(note: Note) = launchSigner { account.removeFromGallery(note) }
|
fun removeFromMediaGallery(note: Note) = launchSigner { account.removeFromGallery(note) }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user