fix videos with an orientation

This commit is contained in:
Samuel Newman
2026-03-11 15:05:30 +02:00
parent 14b74a2986
commit 997f0006a3
2 changed files with 38 additions and 27 deletions
@@ -168,11 +168,16 @@ class VideoCompressor(
encoder.start() encoder.start()
// Set up video decoder // Set up video decoder
val decoderFormat = videoFormat // Strip rotation from decoder format so the decoder outputs frames in storage
// orientation. Some decoders apply rotation themselves when KEY_ROTATION is set,
// which double-rotates since we handle it via muxer.setOrientationHint().
if (videoFormat.containsKey(MediaFormat.KEY_ROTATION)) {
videoFormat.setInteger(MediaFormat.KEY_ROTATION, 0)
}
val decoder = MediaCodec.createDecoderByType( val decoder = MediaCodec.createDecoderByType(
videoFormat.getString(MediaFormat.KEY_MIME) ?: "video/avc" videoFormat.getString(MediaFormat.KEY_MIME) ?: "video/avc"
) )
decoder.configure(decoderFormat, outputSurface.surface, null, 0) decoder.configure(videoFormat, outputSurface.surface, null, 0)
decoder.start() decoder.start()
extractor.selectTrack(videoTrackIndex) extractor.selectTrack(videoTrackIndex)
@@ -411,26 +416,29 @@ class VideoCompressor(
rotation: Int, rotation: Int,
maxSize: Int maxSize: Int
): Pair<Int, Int> { ): Pair<Int, Int> {
// Apply rotation to get display dimensions // Use display dimensions (rotated) to determine the scale factor,
// but return storage dimensions (unrotated) for the encoder.
// The muxer's orientationHint handles rotation separately.
val isRotated = rotation == 90 || rotation == 270 val isRotated = rotation == 90 || rotation == 270
val sourceWidth = if (isRotated) height else width val displayWidth = if (isRotated) height else width
val sourceHeight = if (isRotated) width else height val displayHeight = if (isRotated) width else height
// If within bounds, keep original (rounded to even) // If display dimensions within bounds, keep original storage size (rounded to even)
if (sourceWidth <= maxSize && sourceHeight <= maxSize) { if (displayWidth <= maxSize && displayHeight <= maxSize) {
return Pair(roundToEven(sourceWidth), roundToEven(sourceHeight)) return Pair(roundToEven(width), roundToEven(height))
} }
// Scale down maintaining aspect ratio // Scale based on display dimensions
val scale = if (sourceWidth > sourceHeight) { val scale = if (displayWidth > displayHeight) {
maxSize.toFloat() / sourceWidth.toFloat() maxSize.toFloat() / displayWidth.toFloat()
} else { } else {
maxSize.toFloat() / sourceHeight.toFloat() maxSize.toFloat() / displayHeight.toFloat()
} }
// Return storage dimensions (unrotated)
return Pair( return Pair(
roundToEven((sourceWidth * scale).toInt()), roundToEven((width * scale).toInt()),
roundToEven((sourceHeight * scale).toInt()) roundToEven((height * scale).toInt())
) )
} }
@@ -316,32 +316,35 @@ class VideoCompressor {
transform: CGAffineTransform, transform: CGAffineTransform,
maxSize: Int maxSize: Int
) -> (width: Int, height: Int) { ) -> (width: Int, height: Int) {
// Apply transform to get actual display dimensions // Use display dimensions (rotated) to determine the scale factor,
// but return storage dimensions (unrotated) for the encoder.
// The writer's transform handles rotation separately.
let isRotated = abs(transform.b) == 1.0 && abs(transform.c) == 1.0 let isRotated = abs(transform.b) == 1.0 && abs(transform.c) == 1.0
let sourceWidth = isRotated ? naturalSize.height : naturalSize.width let displayWidth = isRotated ? naturalSize.height : naturalSize.width
let sourceHeight = isRotated ? naturalSize.width : naturalSize.height let displayHeight = isRotated ? naturalSize.width : naturalSize.height
let maxDimension = CGFloat(maxSize) let maxDimension = CGFloat(maxSize)
// If already within bounds, keep original size (rounded to even) // If display dimensions within bounds, keep original storage size (rounded to even)
if sourceWidth <= maxDimension && sourceHeight <= maxDimension { if displayWidth <= maxDimension && displayHeight <= maxDimension {
return ( return (
width: roundToEven(Int(sourceWidth)), width: roundToEven(Int(naturalSize.width)),
height: roundToEven(Int(sourceHeight)) height: roundToEven(Int(naturalSize.height))
) )
} }
// Scale down maintaining aspect ratio // Scale based on display dimensions
let scale: CGFloat let scale: CGFloat
if sourceWidth > sourceHeight { if displayWidth > displayHeight {
scale = maxDimension / sourceWidth scale = maxDimension / displayWidth
} else { } else {
scale = maxDimension / sourceHeight scale = maxDimension / displayHeight
} }
// Return storage dimensions (unrotated)
return ( return (
width: roundToEven(Int(sourceWidth * scale)), width: roundToEven(Int(naturalSize.width * scale)),
height: roundToEven(Int(sourceHeight * scale)) height: roundToEven(Int(naturalSize.height * scale))
) )
} }