Skip to content

Instantly share code, notes, and snippets.

@7kashif
Last active July 17, 2025 18:26
Show Gist options
  • Save 7kashif/93329858b3bc86881fcdbc41118bb2b8 to your computer and use it in GitHub Desktop.
Save 7kashif/93329858b3bc86881fcdbc41118bb2b8 to your computer and use it in GitHub Desktop.
A video cropper using media 3 libraries.
import android.content.Context
import android.media.MediaCodecList
import android.os.Build
import androidx.annotation.OptIn
import androidx.media3.common.Effect
import androidx.media3.common.MediaItem
import androidx.media3.common.MimeTypes
import androidx.media3.common.util.Clock
import androidx.media3.common.util.UnstableApi
import androidx.media3.effect.Crop
import androidx.media3.transformer.AudioEncoderSettings
import androidx.media3.transformer.Composition
import androidx.media3.transformer.DefaultAssetLoaderFactory
import androidx.media3.transformer.DefaultDecoderFactory
import androidx.media3.transformer.DefaultEncoderFactory
import androidx.media3.transformer.EditedMediaItem
import androidx.media3.transformer.Effects
import androidx.media3.transformer.ExportException
import androidx.media3.transformer.ExportResult
import androidx.media3.transformer.Transformer
import androidx.media3.transformer.Transformer.Listener
import androidx.media3.transformer.VideoEncoderSettings
import com.google.common.collect.ImmutableList
import java.io.File
class VideoCropper(
private val context: Context
) {
@OptIn(UnstableApi::class)
fun crop(
inputFile: File,
outputFile: File,
crop: Crop,
onSuccess: () -> Unit,
onFailure: (Throwable) -> Unit
) {
val decoderFactory = DefaultDecoderFactory.Builder(context)
.setEnableDecoderFallback(true)
.build()
val videoSettings = VideoEncoderSettings.Builder()
.setBitrate(12_00_000)
.build()
val audioSettings = AudioEncoderSettings.Builder()
.setBitrate(128_000)
.build()
val encoderFactory = DefaultEncoderFactory.Builder(context)
.setRequestedVideoEncoderSettings(videoSettings)
.setRequestedAudioEncoderSettings(audioSettings)
.setVideoEncoderSelector { mimeType ->
val mediaCodecList = MediaCodecList(MediaCodecList.REGULAR_CODECS)
val encoderInfos = mediaCodecList.codecInfos
.filter { it.isEncoder && it.supportedTypes.contains(mimeType) }
ImmutableList.copyOf(encoderInfos.sortedWith(compareBy {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
when {
it.name.contains("qcom", true) -> 0 // Qualcomm hardware
it.name.contains("exynos", true) -> 1 // Samsung hardware
it.isHardwareAccelerated -> 2 // Other hardware encoders
else -> 3 // Software encoders
}
} else {
when {
it.name.contains("qcom", true) -> 0 // Qualcomm hardware
it.name.contains("exynos", true) -> 1 // Samsung hardware
else -> 3 // Software encoders
}
}
}))
}
.setEnableFallback(true)
.build()
val assetLoaderFactory = DefaultAssetLoaderFactory(context, decoderFactory, Clock.DEFAULT)
// Build the Transformer with a CropTransformation
val transformer = Transformer.Builder(context)
.setAssetLoaderFactory(assetLoaderFactory)
.setEncoderFactory(encoderFactory)
.setAudioMimeType(MimeTypes.AUDIO_AAC)
.setVideoMimeType(MimeTypes.VIDEO_MP4)
.addListener(
object : Listener {
override fun onCompleted(composition: Composition, exportResult: ExportResult) {
super.onCompleted(composition, exportResult)
onSuccess()
}
override fun onError(
composition: Composition,
exportResult: ExportResult,
exportException: ExportException
) {
super.onError(composition, exportResult, exportException)
onFailure(exportException)
}
}
)
.build()
val mediaItem = MediaItem.fromUri(inputFile.absolutePath)
// Create EditedMediaItem with proper crop values
val editedMediaItem = EditedMediaItem.Builder(mediaItem)
.setEffects(
Effects(
listOf(), // No audio effects
listOf<Effect>(crop)
)
)
.build()
transformer.start(
editedMediaItem,
outputFile.absolutePath
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment