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
+6
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"]
@@ -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") &&
@@ -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,
) )
} }
} }
@@ -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) {
@@ -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 {
@@ -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)
@@ -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) {
@@ -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 },
)
}
} }
@@ -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,32 +297,40 @@ fun ImageVideoDescription(
} }
} }
Row( Column(horizontalAlignment = Alignment.CenterHorizontally) {
verticalAlignment = Alignment.CenterVertically, Box(modifier = Modifier.fillMaxWidth()) {
modifier = Modifier.fillMaxWidth(), Text(
) { text =
Column(horizontalAlignment = Alignment.CenterHorizontally) { when (mediaQualitySlider) {
Box(modifier = Modifier.fillMaxWidth()) { 0 -> stringRes(R.string.media_compression_quality_low)
Text( 1 -> stringRes(R.string.media_compression_quality_medium)
text = 2 -> stringRes(R.string.media_compression_quality_high)
when (mediaQualitySlider) { 3 -> stringRes(R.string.media_compression_quality_uncompressed)
0 -> stringRes(R.string.media_compression_quality_low) else -> stringRes(R.string.media_compression_quality_medium)
1 -> stringRes(R.string.media_compression_quality_medium) },
2 -> stringRes(R.string.media_compression_quality_high) modifier = Modifier.align(Alignment.Center),
3 -> stringRes(R.string.media_compression_quality_uncompressed)
else -> stringRes(R.string.media_compression_quality_medium)
},
modifier = Modifier.align(Alignment.Center),
)
}
Slider(
value = mediaQualitySlider.toFloat(),
onValueChange = { mediaQualitySlider = it.toInt() },
valueRange = 0f..3f,
steps = 2,
) )
} }
Slider(
value = mediaQualitySlider.toFloat(),
onValueChange = { mediaQualitySlider = it.toInt() },
valueRange = 0f..3f,
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(
@@ -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)
@@ -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,
@@ -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)
@@ -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)
@@ -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)
+2
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>
+1 -1
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"