diff --git a/amethyst/build.gradle b/amethyst/build.gradle index 34e20e556..e700680f7 100644 --- a/amethyst/build.gradle +++ b/amethyst/build.gradle @@ -332,9 +332,9 @@ dependencies { implementation libs.audiowaveform // Video compression lib - implementation libs.abedElazizShe.image.compressor + implementation libs.abedElazizShe.video.compressor.fork // Image compression lib - implementation libs.zelory.video.compressor + implementation libs.zelory.image.compressor // Cbor for cashuB format implementation libs.kotlinx.serialization.cbor diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/VideoCompressionHelper.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/VideoCompressionHelper.kt index 894b40708..6f6b929df 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/VideoCompressionHelper.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/VideoCompressionHelper.kt @@ -38,8 +38,8 @@ import kotlinx.coroutines.withTimeoutOrNull import java.io.File import java.util.UUID import kotlin.coroutines.resume -import kotlin.math.roundToInt +// TODO: add Auto setting. Focus on small fast streams. 4->1080p, 1080p->720p, 720p and below stay the same resolution. Use existing matrix to determine bitrate. data class VideoInfo( val resolution: VideoResolution, val framerate: Float, @@ -80,18 +80,19 @@ enum class VideoStandard( override fun toString(): String = label } +private const val MBPS_TO_BPS_MULTIPLIER = 1_000_000 + data class CompressionRule( val width: Int, val height: Int, val bitrateMbps: Float, val description: String, ) { - fun getBitrateMbpsInt(framerate: Float): Int { + fun getBitrateBps(framerate: Float): Int { // Apply 1.5x multiplier for 60fps+ videos val multiplier = if (framerate >= 60f) 1.5f else 1.0f - // Library doesn't support float so we have to convert it to int and use 1 as minimum - return (bitrateMbps * multiplier).roundToInt().coerceAtLeast(1) + return (bitrateMbps * multiplier * MBPS_TO_BPS_MULTIPLIER).toInt() } } @@ -144,39 +145,28 @@ object VideoCompressionHelper { ): MediaCompressorResult { val videoInfo = getVideoInfo(uri, applicationContext) - val videoBitrateInMbps = - if (videoInfo != null) { - val bitrateMbpsInt = + val (videoBitrateInBps, resizer) = + videoInfo?.let { info -> + val rule = compressionRules .getValue(mediaQuality) - .getValue(videoInfo.resolution.getStandard()) - .getBitrateMbpsInt(videoInfo.framerate) + .getValue(info.resolution.getStandard()) + + val bitrateBps = rule.getBitrateBps(info.framerate) + Log.d(LOG_TAG, "Bitrate: ${bitrateBps}bps for ${info.resolution.getStandard()} quality=$mediaQuality framerate=${info.framerate}fps.") Log.d( LOG_TAG, - "Bitrate: ${bitrateMbpsInt}Mbps for ${videoInfo.resolution.getStandard()} " + - "quality=$mediaQuality framerate=${videoInfo.framerate}fps.", + "Resizer: ${info.resolution.width}x${info.resolution.height} -> " + + "${rule.width}x${rule.height} (${rule.description})", ) - } else { + val resizer = VideoResizer.limitSize(rule.width.toDouble(), rule.height.toDouble()) + + Pair(bitrateBps, resizer) + } ?: run { Log.w(LOG_TAG, "Video bitrate fallback: 2Mbps (videoInfo unavailable)") - 2 - } - - val resizer = - if (videoInfo != null) { - val rules = - compressionRules - .getValue(mediaQuality) - .getValue(videoInfo.resolution.getStandard()) - Log.d( - LOG_TAG, - "Resizer: ${videoInfo.resolution.width}x${videoInfo.resolution.height} -> " + - "${rules.width}x${rules.height} (${rules.description})", - ) - VideoResizer.limitSize(rules.width.toDouble(), rules.height.toDouble()) - } else { Log.d(LOG_TAG, "Resizer: null (original resolution preserved)") - null + Pair(2 * MBPS_TO_BPS_MULTIPLIER, null) } // Get original file size safely @@ -192,7 +182,7 @@ object VideoCompressionHelper { storageConfiguration = AppSpecificStorageConfiguration(), configureWith = Configuration( - videoBitrateInMbps = videoBitrateInMbps, + videoBitrateInBps = videoBitrateInBps.toLong(), resizer = resizer, videoNames = listOf(UUID.randomUUID().toString()), isMinBitrateCheckEnabled = false, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 896582390..691f31f5f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,7 +33,7 @@ languageId = "17.0.6" lazysodiumAndroid = "5.2.0" lazysodiumJava = "5.2.0" lifecycleRuntimeKtx = "2.9.4" -lightcompressor = "1.3.3" +lightcompressor = "1.4.0" markdown = "e1151c8" media3 = "1.8.0" mockk = "1.14.5" @@ -63,7 +63,7 @@ core = "1.7.0" mavenPublish = "0.34.0" [libraries] -abedElazizShe-image-compressor = { group = "com.github.AbedElazizShe", name = "LightCompressor", version.ref = "lightcompressor" } +abedElazizShe-video-compressor-fork = { group = "com.github.davotoula", name = "LightCompressor-enhanced", version.ref = "lightcompressor" } accompanist-adaptive = { group = "com.google.accompanist", name = "accompanist-adaptive", version.ref = "accompanistAdaptive" } accompanist-permissions = { group = "com.google.accompanist", name = "accompanist-permissions", version.ref = "accompanistAdaptive" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } @@ -145,7 +145,7 @@ vico-charts-compose = { group = "com.patrykandpatrick.vico", name = "compose", v vico-charts-core = { group = "com.patrykandpatrick.vico", name = "core", version.ref = "vico-charts" } vico-charts-m3 = { group = "com.patrykandpatrick.vico", name = "compose-m3", version.ref = "vico-charts" } vico-charts-views = { group = "com.patrykandpatrick.vico", name = "views", version.ref = "vico-charts" } -zelory-video-compressor = { group = "id.zelory", name = "compressor", version.ref = "zelory" } +zelory-image-compressor = { group = "id.zelory", name = "compressor", version.ref = "zelory" } zoomable = { group = "net.engawapg.lib", name = "zoomable", version.ref = "zoomable" } zxing = { group = "com.google.zxing", name = "core", version.ref = "zxing" } zxing-embedded = { group = "com.journeyapps", name = "zxing-android-embedded", version.ref = "zxingAndroidEmbedded" }