Update to use lightcompressor library version that supports h265

Add h265 toggle to NewMediaView.kt and ImageVideoDescription.kt
Touch many files to add optional h265 boolean (default is false)
This commit is contained in:
davotoula
2025-09-30 22:02:18 +02:00
parent c936e91ec8
commit a6e306f3de
16 changed files with 88 additions and 38 deletions

View File

@@ -151,6 +151,12 @@ android {
signingConfig = signingConfigs.debug signingConfig = signingConfigs.debug
} }
} }
// TODO: remove this when lightcompressor uses one MP4 parser only
packaging {
resources {
resources.pickFirsts.add('builddef.lst')
}
}
flavorDimensions = ["channel"] flavorDimensions = ["channel"]

View File

@@ -45,6 +45,7 @@ class MediaCompressor {
contentType: String?, contentType: String?,
mediaQuality: CompressorQuality, mediaQuality: CompressorQuality,
applicationContext: Context, applicationContext: Context,
useH265: Boolean = false,
): MediaCompressorResult { ): MediaCompressorResult {
// Skip compression if user selected uncompressed // Skip compression if user selected uncompressed
if (mediaQuality == CompressorQuality.UNCOMPRESSED) { if (mediaQuality == CompressorQuality.UNCOMPRESSED) {
@@ -57,7 +58,7 @@ class MediaCompressor {
// branch into compression based on content type // branch into compression based on content type
return when { return when {
contentType?.startsWith("video", ignoreCase = true) == true -> { contentType?.startsWith("video", ignoreCase = true) == true -> {
VideoCompressionHelper.compressVideo(uri, contentType, applicationContext, mediaQuality) VideoCompressionHelper.compressVideo(uri, contentType, applicationContext, mediaQuality, useH265)
} }
contentType?.startsWith("image", ignoreCase = true) == true && contentType?.startsWith("image", ignoreCase = true) == true &&
!contentType.contains("gif") && !contentType.contains("gif") &&

View File

@@ -46,6 +46,8 @@ class MultiOrchestrator(
fun first() = list.first() fun first() = list.first()
fun hasVideo() = list.any { it.media.mimeType?.startsWith("video", ignoreCase = true) == true }
suspend fun upload( suspend fun upload(
alt: String?, alt: String?,
contentWarningReason: String?, contentWarningReason: String?,
@@ -53,6 +55,7 @@ class MultiOrchestrator(
server: ServerName, server: ServerName,
account: Account, account: Account,
context: Context, context: Context,
useH265: Boolean = false,
): Result { ): Result {
coroutineScope { coroutineScope {
val jobs = val jobs =
@@ -67,6 +70,7 @@ class MultiOrchestrator(
server, server,
account, account,
context, context,
useH265,
) )
} }
} }
@@ -85,6 +89,7 @@ class MultiOrchestrator(
server: ServerName, server: ServerName,
account: Account, account: Account,
context: Context, context: Context,
useH265: Boolean = false,
): Result { ): Result {
coroutineScope { coroutineScope {
val jobs = val jobs =
@@ -100,6 +105,7 @@ class MultiOrchestrator(
server, server,
account, account,
context, context,
useH265,
) )
} }
} }

View File

@@ -288,9 +288,10 @@ class UploadOrchestrator {
mimeType: String?, mimeType: String?,
compressionQuality: CompressorQuality, compressionQuality: CompressorQuality,
context: Context, context: Context,
useH265: Boolean = false,
) = if (compressionQuality != CompressorQuality.UNCOMPRESSED) { ) = if (compressionQuality != CompressorQuality.UNCOMPRESSED) {
updateState(0.02, UploadingState.Compressing) updateState(0.02, UploadingState.Compressing)
MediaCompressor().compress(uri, mimeType, compressionQuality, context.applicationContext) MediaCompressor().compress(uri, mimeType, compressionQuality, context.applicationContext, useH265)
} else { } else {
MediaCompressorResult(uri, mimeType, null) MediaCompressorResult(uri, mimeType, null)
} }
@@ -304,8 +305,9 @@ class UploadOrchestrator {
server: ServerName, server: ServerName,
account: Account, account: Account,
context: Context, context: Context,
useH265: Boolean = false,
): UploadingFinalState { ): UploadingFinalState {
val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context) val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context, useH265)
return when (server.type) { return when (server.type) {
ServerType.NIP95 -> uploadNIP95(compressed.uri, compressed.contentType, null, null, context) ServerType.NIP95 -> uploadNIP95(compressed.uri, compressed.contentType, null, null, context)
@@ -324,8 +326,9 @@ class UploadOrchestrator {
server: ServerName, server: ServerName,
account: Account, account: Account,
context: Context, context: Context,
useH265: Boolean = false,
): UploadingFinalState { ): UploadingFinalState {
val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context) val compressed = compressIfNeeded(uri, mimeType, compressionQuality, context, useH265)
val encrypted = EncryptFiles().encryptFile(context, compressed.uri, encrypt) val encrypted = EncryptFiles().encryptFile(context, compressed.uri, encrypt)
return when (server.type) { return when (server.type) {

View File

@@ -29,6 +29,7 @@ import android.text.format.Formatter.formatFileSize
import android.util.Log import android.util.Log
import android.widget.Toast import android.widget.Toast
import com.abedelazizshe.lightcompressorlibrary.CompressionListener import com.abedelazizshe.lightcompressorlibrary.CompressionListener
import com.abedelazizshe.lightcompressorlibrary.VideoCodec
import com.abedelazizshe.lightcompressorlibrary.VideoCompressor import com.abedelazizshe.lightcompressorlibrary.VideoCompressor
import com.abedelazizshe.lightcompressorlibrary.config.AppSpecificStorageConfiguration import com.abedelazizshe.lightcompressorlibrary.config.AppSpecificStorageConfiguration
import com.abedelazizshe.lightcompressorlibrary.config.Configuration import com.abedelazizshe.lightcompressorlibrary.config.Configuration
@@ -141,6 +142,7 @@ object VideoCompressionHelper {
contentType: String?, contentType: String?,
applicationContext: Context, applicationContext: Context,
mediaQuality: CompressorQuality, mediaQuality: CompressorQuality,
useH265: Boolean = false,
timeoutMs: Long = 60_000L, // configurable, default 60s timeoutMs: Long = 60_000L, // configurable, default 60s
): MediaCompressorResult { ): MediaCompressorResult {
val videoInfo = getVideoInfo(uri, applicationContext) val videoInfo = getVideoInfo(uri, applicationContext)
@@ -186,6 +188,7 @@ object VideoCompressionHelper {
resizer = resizer, resizer = resizer,
videoNames = listOf(UUID.randomUUID().toString()), videoNames = listOf(UUID.randomUUID().toString()),
isMinBitrateCheckEnabled = false, isMinBitrateCheckEnabled = false,
videoCodec = if (useH265) VideoCodec.H265 else VideoCodec.H264,
), ),
listener = listener =
object : CompressionListener { object : CompressionListener {

View File

@@ -261,7 +261,7 @@ fun EditPostView(
ImageVideoDescription( ImageVideoDescription(
it, it,
accountViewModel.account.settings.defaultFileServer, accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality -> onAdd = { alt, server, sensitiveContent, mediaQuality, _ ->
postViewModel.upload(alt, sensitiveContent, mediaQuality, false, server, accountViewModel.toastManager::toast, context) postViewModel.upload(alt, sensitiveContent, mediaQuality, false, server, accountViewModel.toastManager::toast, context)
if (server.type != ServerType.NIP95) { if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server) accountViewModel.account.settings.changeDefaultFileServer(server)

View File

@@ -62,6 +62,9 @@ open class NewMediaModel : ViewModel() {
// 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED // 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED
var mediaQualitySlider by mutableIntStateOf(1) var mediaQualitySlider by mutableIntStateOf(1)
// Codec selection: false = H264, true = H265
var useH265Codec by mutableStateOf(false)
open fun load( open fun load(
account: Account, account: Account,
uris: ImmutableList<SelectedMedia>, uris: ImmutableList<SelectedMedia>,
@@ -111,6 +114,7 @@ open class NewMediaModel : ViewModel() {
serverToUse, serverToUse,
myAccount, myAccount,
context, context,
useH265Codec,
) )
if (results.allGood) { if (results.allGood) {

View File

@@ -261,4 +261,18 @@ fun ImageVideoPost(
steps = 2, steps = 2,
) )
} }
// Only show H.265 codec option if there are videos in the upload
if (postViewModel.multiOrchestrator?.hasVideo() == true) {
SettingSwitchItem(
title = R.string.video_codec_h265_label,
description = R.string.video_codec_h265_description,
modifier =
Modifier
.fillMaxWidth()
.padding(top = 8.dp),
checked = postViewModel.useH265Codec,
onCheckedChange = { postViewModel.useH265Codec = it },
)
}
} }

View File

@@ -78,7 +78,7 @@ import kotlinx.collections.immutable.toImmutableList
fun ImageVideoDescription( fun ImageVideoDescription(
uris: MultiOrchestrator, uris: MultiOrchestrator,
defaultServer: ServerName, defaultServer: ServerName,
onAdd: (String, ServerName, Boolean, Int) -> Unit, onAdd: (String, ServerName, Boolean, Int, Boolean) -> Unit,
onDelete: (SelectedMediaProcessing) -> Unit, onDelete: (SelectedMediaProcessing) -> Unit,
onCancel: () -> Unit, onCancel: () -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@@ -91,7 +91,7 @@ fun ImageVideoDescription(
uris: MultiOrchestrator, uris: MultiOrchestrator,
defaultServer: ServerName, defaultServer: ServerName,
includeNIP95: Boolean, includeNIP95: Boolean,
onAdd: (String, ServerName, Boolean, Int) -> Unit, onAdd: (String, ServerName, Boolean, Int, Boolean) -> Unit,
onDelete: (SelectedMediaProcessing) -> Unit, onDelete: (SelectedMediaProcessing) -> Unit,
onCancel: () -> Unit, onCancel: () -> Unit,
accountViewModel: AccountViewModel, accountViewModel: AccountViewModel,
@@ -128,6 +128,9 @@ fun ImageVideoDescription(
// 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED // 0 = Low, 1 = Medium, 2 = High, 3=UNCOMPRESSED
var mediaQualitySlider by remember { mutableIntStateOf(1) } var mediaQualitySlider by remember { mutableIntStateOf(1) }
// Codec selection: false = H264, true = H265
var useH265Codec by remember { mutableStateOf(false) }
Column( Column(
modifier = modifier =
Modifier Modifier
@@ -294,10 +297,6 @@ fun ImageVideoDescription(
} }
} }
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth(),
) {
Column(horizontalAlignment = Alignment.CenterHorizontally) { Column(horizontalAlignment = Alignment.CenterHorizontally) {
Box(modifier = Modifier.fillMaxWidth()) { Box(modifier = Modifier.fillMaxWidth()) {
Text( Text(
@@ -320,6 +319,18 @@ fun ImageVideoDescription(
steps = 2, steps = 2,
) )
} }
if (uris.first().media.isVideo() == true) {
SettingSwitchItem(
title = R.string.video_codec_h265_label,
description = R.string.video_codec_h265_description,
modifier =
Modifier
.fillMaxWidth()
.padding(top = 8.dp),
checked = useH265Codec,
onCheckedChange = { useH265Codec = it },
)
} }
Button( Button(
@@ -327,7 +338,7 @@ fun ImageVideoDescription(
Modifier Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 10.dp), .padding(vertical = 10.dp),
onClick = { onAdd(message, selectedServer, sensitiveContent, mediaQualitySlider) }, onClick = { onAdd(message, selectedServer, sensitiveContent, mediaQualitySlider, useH265Codec) },
shape = QuoteBorder, shape = QuoteBorder,
colors = colors =
ButtonDefaults.buttonColors( ButtonDefaults.buttonColors(

View File

@@ -285,7 +285,7 @@ private fun GenericCommentPostBody(
ImageVideoDescription( ImageVideoDescription(
it, it,
accountViewModel.account.settings.defaultFileServer, accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality -> onAdd = { alt, server, sensitiveContent, mediaQuality, _ ->
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context)
if (server.type != ServerType.NIP95) { if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server) accountViewModel.account.settings.changeDefaultFileServer(server)

View File

@@ -278,7 +278,7 @@ fun GroupDMScreenContent(
ImageVideoDescription( ImageVideoDescription(
selectedFiles, selectedFiles,
accountViewModel.account.settings.defaultFileServer, accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality -> onAdd = { alt, server, sensitiveContent, mediaQuality, _ ->
postViewModel.uploadAndHold( postViewModel.uploadAndHold(
accountViewModel.toastManager::toast, accountViewModel.toastManager::toast,
context, context,

View File

@@ -266,7 +266,7 @@ private fun NewProductBody(
uris = it, uris = it,
defaultServer = accountViewModel.account.settings.defaultFileServer, defaultServer = accountViewModel.account.settings.defaultFileServer,
includeNIP95 = false, includeNIP95 = false,
onAdd = { alt, server, sensitiveContent, mediaQuality -> onAdd = { alt, server, sensitiveContent, mediaQuality, _ ->
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context)
if (server.type != ServerType.NIP95) { if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server) accountViewModel.account.settings.changeDefaultFileServer(server)

View File

@@ -326,7 +326,7 @@ private fun NewPostScreenBody(
ImageVideoDescription( ImageVideoDescription(
it, it,
accountViewModel.account.settings.defaultFileServer, accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality -> onAdd = { alt, server, sensitiveContent, mediaQuality, _ ->
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context)
if (server.type != ServerType.NIP95) { if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server) accountViewModel.account.settings.changeDefaultFileServer(server)

View File

@@ -231,7 +231,7 @@ fun PublicMessageScreenContent(
ImageVideoDescription( ImageVideoDescription(
it, it,
accountViewModel.account.settings.defaultFileServer, accountViewModel.account.settings.defaultFileServer,
onAdd = { alt, server, sensitiveContent, mediaQuality -> onAdd = { alt, server, sensitiveContent, mediaQuality, _ ->
postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context) postViewModel.upload(alt, if (sensitiveContent) "" else null, mediaQuality, server, accountViewModel.toastManager::toast, context)
if (server.type != ServerType.NIP95) { if (server.type != ServerType.NIP95) {
accountViewModel.account.settings.changeDefaultFileServer(server) accountViewModel.account.settings.changeDefaultFileServer(server)

View File

@@ -1039,6 +1039,8 @@
<string name="media_compression_quality_medium">Medium</string> <string name="media_compression_quality_medium">Medium</string>
<string name="media_compression_quality_high">High</string> <string name="media_compression_quality_high">High</string>
<string name="media_compression_quality_uncompressed">Uncompressed</string> <string name="media_compression_quality_uncompressed">Uncompressed</string>
<string name="video_codec_h265_label">Use H.265/HEVC Codec</string>
<string name="video_codec_h265_description">Better quality at smaller file sizes but not all devices support H.265 playback.</string>
<string name="edit_draft">Edit draft</string> <string name="edit_draft">Edit draft</string>

View File

@@ -33,7 +33,7 @@ languageId = "17.0.6"
lazysodiumAndroid = "5.2.0" lazysodiumAndroid = "5.2.0"
lazysodiumJava = "5.2.0" lazysodiumJava = "5.2.0"
lifecycleRuntimeKtx = "2.9.4" lifecycleRuntimeKtx = "2.9.4"
lightcompressor = "1.4.0" lightcompressor = "51defaaa8d"
markdown = "e1151c8" markdown = "e1151c8"
media3 = "1.8.0" media3 = "1.8.0"
mockk = "1.14.5" mockk = "1.14.5"