From 997f0006a3f0ef5d13927a6743a18a804fd05422 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Wed, 11 Mar 2026 15:05:30 +0200 Subject: [PATCH] fix videos with an orientation --- .../blueskyvideocompress/VideoCompressor.kt | 36 +++++++++++-------- .../ios/VideoCompressor.swift | 29 ++++++++------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/modules/expo-bluesky-video-compress/android/src/main/java/expo/modules/blueskyvideocompress/VideoCompressor.kt b/modules/expo-bluesky-video-compress/android/src/main/java/expo/modules/blueskyvideocompress/VideoCompressor.kt index 68b1ff1dcd..3368b974c7 100644 --- a/modules/expo-bluesky-video-compress/android/src/main/java/expo/modules/blueskyvideocompress/VideoCompressor.kt +++ b/modules/expo-bluesky-video-compress/android/src/main/java/expo/modules/blueskyvideocompress/VideoCompressor.kt @@ -168,11 +168,16 @@ class VideoCompressor( encoder.start() // 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( videoFormat.getString(MediaFormat.KEY_MIME) ?: "video/avc" ) - decoder.configure(decoderFormat, outputSurface.surface, null, 0) + decoder.configure(videoFormat, outputSurface.surface, null, 0) decoder.start() extractor.selectTrack(videoTrackIndex) @@ -411,26 +416,29 @@ class VideoCompressor( rotation: Int, maxSize: Int ): Pair { - // 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 sourceWidth = if (isRotated) height else width - val sourceHeight = if (isRotated) width else height + val displayWidth = if (isRotated) height else width + val displayHeight = if (isRotated) width else height - // If within bounds, keep original (rounded to even) - if (sourceWidth <= maxSize && sourceHeight <= maxSize) { - return Pair(roundToEven(sourceWidth), roundToEven(sourceHeight)) + // If display dimensions within bounds, keep original storage size (rounded to even) + if (displayWidth <= maxSize && displayHeight <= maxSize) { + return Pair(roundToEven(width), roundToEven(height)) } - // Scale down maintaining aspect ratio - val scale = if (sourceWidth > sourceHeight) { - maxSize.toFloat() / sourceWidth.toFloat() + // Scale based on display dimensions + val scale = if (displayWidth > displayHeight) { + maxSize.toFloat() / displayWidth.toFloat() } else { - maxSize.toFloat() / sourceHeight.toFloat() + maxSize.toFloat() / displayHeight.toFloat() } + // Return storage dimensions (unrotated) return Pair( - roundToEven((sourceWidth * scale).toInt()), - roundToEven((sourceHeight * scale).toInt()) + roundToEven((width * scale).toInt()), + roundToEven((height * scale).toInt()) ) } diff --git a/modules/expo-bluesky-video-compress/ios/VideoCompressor.swift b/modules/expo-bluesky-video-compress/ios/VideoCompressor.swift index f71e93e3c7..6c90b4f7ce 100644 --- a/modules/expo-bluesky-video-compress/ios/VideoCompressor.swift +++ b/modules/expo-bluesky-video-compress/ios/VideoCompressor.swift @@ -316,32 +316,35 @@ class VideoCompressor { transform: CGAffineTransform, maxSize: 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 sourceWidth = isRotated ? naturalSize.height : naturalSize.width - let sourceHeight = isRotated ? naturalSize.width : naturalSize.height + let displayWidth = isRotated ? naturalSize.height : naturalSize.width + let displayHeight = isRotated ? naturalSize.width : naturalSize.height let maxDimension = CGFloat(maxSize) - // If already within bounds, keep original size (rounded to even) - if sourceWidth <= maxDimension && sourceHeight <= maxDimension { + // If display dimensions within bounds, keep original storage size (rounded to even) + if displayWidth <= maxDimension && displayHeight <= maxDimension { return ( - width: roundToEven(Int(sourceWidth)), - height: roundToEven(Int(sourceHeight)) + width: roundToEven(Int(naturalSize.width)), + height: roundToEven(Int(naturalSize.height)) ) } - // Scale down maintaining aspect ratio + // Scale based on display dimensions let scale: CGFloat - if sourceWidth > sourceHeight { - scale = maxDimension / sourceWidth + if displayWidth > displayHeight { + scale = maxDimension / displayWidth } else { - scale = maxDimension / sourceHeight + scale = maxDimension / displayHeight } + // Return storage dimensions (unrotated) return ( - width: roundToEven(Int(sourceWidth * scale)), - height: roundToEven(Int(sourceHeight * scale)) + width: roundToEven(Int(naturalSize.width * scale)), + height: roundToEven(Int(naturalSize.height * scale)) ) }