mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 20:06:37 +01:00
refactor:
Safe file size lookup using OpenableColumns.SIZE if (continuation.isActive) before resuming. Better logging levels (Log.e for errors, Log.w for warnings). Configurable compression timeout
This commit is contained in:
@@ -125,89 +125,96 @@ class VideoCompressionHelper {
|
|||||||
contentType: String?,
|
contentType: String?,
|
||||||
applicationContext: Context,
|
applicationContext: Context,
|
||||||
mediaQuality: CompressorQuality,
|
mediaQuality: CompressorQuality,
|
||||||
|
timeoutMs: Long = 60_000L, // configurable, default 60s
|
||||||
): MediaCompressorResult {
|
): MediaCompressorResult {
|
||||||
val videoInfo = getVideoInfo(uri, applicationContext)
|
val videoInfo = getVideoInfo(uri, applicationContext)
|
||||||
|
|
||||||
val videoBitrateInMbps =
|
val videoBitrateInMbps =
|
||||||
if (videoInfo != null) {
|
if (videoInfo != null) {
|
||||||
val baseBitrate = compressionRules.getValue(mediaQuality).getValue(videoInfo.resolution.getStandardName()).getBitrateMbpsInt()
|
val baseBitrate =
|
||||||
// Apply 1.5x multiplier for 60fps or higher videos
|
compressionRules
|
||||||
val adjustedBitrate =
|
.getValue(mediaQuality)
|
||||||
|
.getValue(videoInfo.resolution.getStandardName())
|
||||||
|
.getBitrateMbpsInt()
|
||||||
|
|
||||||
|
// Apply 1.5x multiplier for 60fps+
|
||||||
|
val adjusted =
|
||||||
if (videoInfo.framerate >= 60f) {
|
if (videoInfo.framerate >= 60f) {
|
||||||
(baseBitrate * 1.5f).roundToInt()
|
(baseBitrate * 1.5f).roundToInt()
|
||||||
} else {
|
} else {
|
||||||
baseBitrate
|
baseBitrate
|
||||||
}
|
}
|
||||||
Log.d("VideoCompressionHelper", "Video bitrate calculated: ${adjustedBitrate}Mbps for ${videoInfo.resolution.getStandardName()} quality=$mediaQuality framerate=${videoInfo.framerate}fps")
|
|
||||||
adjustedBitrate
|
Log.d(
|
||||||
|
"VideoCompressionHelper",
|
||||||
|
"Bitrate: ${adjusted}Mbps for ${videoInfo.resolution.getStandardName()} " +
|
||||||
|
"quality=$mediaQuality framerate=${videoInfo.framerate}fps",
|
||||||
|
)
|
||||||
|
adjusted
|
||||||
} else {
|
} else {
|
||||||
// Default/fallback logic when videoInfo is null
|
Log.w("VideoCompressionHelper", "Video bitrate fallback: 2Mbps (videoInfo unavailable)")
|
||||||
Log.d("VideoCompressionHelper", "Video bitrate fallback: 2Mbps (videoInfo unavailable)")
|
|
||||||
2
|
2
|
||||||
}
|
}
|
||||||
|
|
||||||
val resizer =
|
val resizer =
|
||||||
if (videoInfo != null) {
|
if (videoInfo != null) {
|
||||||
val rules = compressionRules.getValue(mediaQuality).getValue(videoInfo.resolution.getStandardName())
|
val rules =
|
||||||
Log.d("VideoCompressionHelper", "Video resizer: ${videoInfo.resolution.width}x${videoInfo.resolution.height} -> ${rules.width}x${rules.height} (${rules.description})")
|
compressionRules
|
||||||
|
.getValue(mediaQuality)
|
||||||
|
.getValue(videoInfo.resolution.getStandardName())
|
||||||
|
Log.d(
|
||||||
|
"VideoCompressionHelper",
|
||||||
|
"Resizer: ${videoInfo.resolution.width}x${videoInfo.resolution.height} -> " +
|
||||||
|
"${rules.width}x${rules.height} (${rules.description})",
|
||||||
|
)
|
||||||
VideoResizer.limitSize(rules.width.toDouble(), rules.height.toDouble())
|
VideoResizer.limitSize(rules.width.toDouble(), rules.height.toDouble())
|
||||||
} else {
|
} else {
|
||||||
// null VideoResizer should result in unchanged resolution
|
Log.d("VideoCompressionHelper", "Resizer: null (original resolution preserved)")
|
||||||
Log.d("VideoCompressionHelper", "Video resizer: null (original resolution preserved)")
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get original file size for compression reporting
|
// Get original file size safely
|
||||||
val originalSize =
|
val originalSize = applicationContext.getFileSize(uri)
|
||||||
try {
|
|
||||||
applicationContext.contentResolver.openInputStream(uri)?.use { inputStream ->
|
|
||||||
inputStream.available().toLong()
|
|
||||||
} ?: 0L
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w("VideoCompressionHelper", "Failed to get original file size: ${e.message}")
|
|
||||||
0L
|
|
||||||
}
|
|
||||||
|
|
||||||
val result =
|
val result =
|
||||||
withTimeoutOrNull(30000) {
|
withTimeoutOrNull(timeoutMs) {
|
||||||
suspendCancellableCoroutine { continuation ->
|
suspendCancellableCoroutine { continuation ->
|
||||||
VideoCompressor.start(
|
VideoCompressor.start(
|
||||||
// => This is required
|
|
||||||
context = applicationContext,
|
context = applicationContext,
|
||||||
// => Source can be provided as content uris
|
|
||||||
uris = listOf(uri),
|
uris = listOf(uri),
|
||||||
isStreamable = true,
|
isStreamable = true,
|
||||||
// THIS STORAGE
|
|
||||||
// sharedStorageConfiguration = SharedStorageConfiguration(
|
|
||||||
// saveAt = SaveLocation.movies, // => default is movies
|
|
||||||
// videoName = "compressed_video" // => required name
|
|
||||||
// ),
|
|
||||||
// OR AND NOT BOTH
|
|
||||||
storageConfiguration = AppSpecificStorageConfiguration(),
|
storageConfiguration = AppSpecificStorageConfiguration(),
|
||||||
configureWith =
|
configureWith =
|
||||||
Configuration(
|
Configuration(
|
||||||
videoBitrateInMbps = videoBitrateInMbps,
|
videoBitrateInMbps = videoBitrateInMbps,
|
||||||
resizer = resizer,
|
resizer = resizer,
|
||||||
// => required name
|
|
||||||
videoNames = listOf(UUID.randomUUID().toString()),
|
videoNames = listOf(UUID.randomUUID().toString()),
|
||||||
isMinBitrateCheckEnabled = false,
|
isMinBitrateCheckEnabled = false,
|
||||||
),
|
),
|
||||||
listener =
|
listener =
|
||||||
object : CompressionListener {
|
object : CompressionListener {
|
||||||
|
override fun onStart(index: Int) {}
|
||||||
|
|
||||||
override fun onProgress(
|
override fun onProgress(
|
||||||
index: Int,
|
index: Int,
|
||||||
percent: Float,
|
percent: Float,
|
||||||
) {
|
) {}
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart(index: Int) {}
|
|
||||||
|
|
||||||
override fun onSuccess(
|
override fun onSuccess(
|
||||||
index: Int,
|
index: Int,
|
||||||
size: Long,
|
size: Long,
|
||||||
path: String?,
|
path: String?,
|
||||||
) {
|
) {
|
||||||
if (path != null) {
|
if (path == null) {
|
||||||
|
applicationContext.notifyUser(
|
||||||
|
"Video compression succeeded, but path was null",
|
||||||
|
"VideoCompressionHelper",
|
||||||
|
Log.WARN,
|
||||||
|
)
|
||||||
|
if (continuation.isActive) continuation.resume(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val reductionPercent =
|
val reductionPercent =
|
||||||
if (originalSize > 0) {
|
if (originalSize > 0) {
|
||||||
((originalSize - size) * 100.0 / originalSize).toInt()
|
((originalSize - size) * 100.0 / originalSize).toInt()
|
||||||
@@ -215,25 +222,42 @@ class VideoCompressionHelper {
|
|||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check: if compressed file is larger than original, return original
|
// Sanity check: compression not smaller than original
|
||||||
if (originalSize > 0 && size >= originalSize) {
|
if (originalSize > 0 && size >= originalSize) {
|
||||||
Log.d("VideoCompressionHelper", "Compressed file ($size bytes) is larger than original ($originalSize bytes). Using original file.")
|
applicationContext.notifyUser(
|
||||||
applicationContext.showToast("Video compression didn't reduce size. Using original file.")
|
"Compressed file larger than original. Using original.",
|
||||||
continuation.resume(MediaCompressorResult(uri, contentType, null))
|
"VideoCompressionHelper",
|
||||||
|
Log.WARN,
|
||||||
|
)
|
||||||
|
if (continuation.isActive) {
|
||||||
|
continuation.resume(
|
||||||
|
MediaCompressorResult(uri, contentType, null),
|
||||||
|
)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Show compression result
|
||||||
if (originalSize > 0 && size > 0) {
|
if (originalSize > 0 && size > 0) {
|
||||||
val sizeLabel = formatFileSize(applicationContext, size)
|
val sizeLabel = formatFileSize(applicationContext, size)
|
||||||
val percentLabel = if (reductionPercent >= 0) "-$reductionPercent%" else "+${-reductionPercent}%"
|
val percentLabel =
|
||||||
|
if (reductionPercent >= 0) "-$reductionPercent%" else "+${-reductionPercent}%"
|
||||||
applicationContext.showToast("Video compressed: $sizeLabel ($percentLabel)")
|
applicationContext.notifyUser(
|
||||||
|
"Video compressed: $sizeLabel ($percentLabel)",
|
||||||
|
"VideoCompressionHelper",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Log.d("VideoCompressionHelper", "Video compression success. Original size [$originalSize] -> Compressed size [$size] ($reductionPercent% reduction)")
|
|
||||||
continuation.resume(MediaCompressorResult(Uri.fromFile(File(path)), contentType, size))
|
Log.d(
|
||||||
} else {
|
"VideoCompressionHelper",
|
||||||
Log.d("VideoCompressionHelper", "Video compression successful, but returned null path")
|
"Compression success: Original [$originalSize] -> " +
|
||||||
continuation.resume(null)
|
"Compressed [$size] ($reductionPercent% reduction)",
|
||||||
|
)
|
||||||
|
|
||||||
|
if (continuation.isActive) {
|
||||||
|
continuation.resume(
|
||||||
|
MediaCompressorResult(Uri.fromFile(File(path)), contentType, size),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,13 +265,17 @@ class VideoCompressionHelper {
|
|||||||
index: Int,
|
index: Int,
|
||||||
failureMessage: String,
|
failureMessage: String,
|
||||||
) {
|
) {
|
||||||
Log.d("VideoCompressionHelper", "Video compression failed: $failureMessage")
|
applicationContext.notifyUser(
|
||||||
// keeps going with original video
|
"Video compression failed: $failureMessage",
|
||||||
continuation.resume(null)
|
"VideoCompressionHelper",
|
||||||
|
Log.ERROR,
|
||||||
|
)
|
||||||
|
if (continuation.isActive) continuation.resume(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCancelled(index: Int) {
|
override fun onCancelled(index: Int) {
|
||||||
continuation.resume(null)
|
Log.w("VideoCompressionHelper", "Video compression cancelled")
|
||||||
|
if (continuation.isActive) continuation.resume(null)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -257,13 +285,31 @@ class VideoCompressionHelper {
|
|||||||
return result ?: MediaCompressorResult(uri, contentType, null)
|
return result ?: MediaCompressorResult(uri, contentType, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.showToast(
|
private fun Context.getFileSize(uri: Uri): Long =
|
||||||
|
try {
|
||||||
|
contentResolver.query(uri, arrayOf(android.provider.OpenableColumns.SIZE), null, null, null)?.use { cursor ->
|
||||||
|
val sizeIndex = cursor.getColumnIndex(android.provider.OpenableColumns.SIZE)
|
||||||
|
if (cursor.moveToFirst()) cursor.getLong(sizeIndex) else 0L
|
||||||
|
} ?: 0L
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w("VideoCompressionHelper", "Failed to get file size: ${e.message}")
|
||||||
|
0L
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Context.notifyUser(
|
||||||
message: String,
|
message: String,
|
||||||
|
logTag: String,
|
||||||
|
logLevel: Int = Log.DEBUG,
|
||||||
duration: Int = Toast.LENGTH_LONG,
|
duration: Int = Toast.LENGTH_LONG,
|
||||||
) {
|
) {
|
||||||
Handler(Looper.getMainLooper()).post {
|
Handler(Looper.getMainLooper()).post {
|
||||||
Toast.makeText(this, message, duration).show()
|
Toast.makeText(this, message, duration).show()
|
||||||
}
|
}
|
||||||
|
when (logLevel) {
|
||||||
|
Log.ERROR -> Log.e(logTag, message)
|
||||||
|
Log.WARN -> Log.w(logTag, message)
|
||||||
|
else -> Log.d(logTag, message)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getVideoInfo(
|
private fun getVideoInfo(
|
||||||
|
|||||||
Reference in New Issue
Block a user