I've been able to center the middle of a 16:9 landscape video, crop the video, and then create a 9:16 portrait version of the video similar to how Apple does it in the Photos album.
The only issue is the resulting portrait video isn't centered in the middle of the screen (images below).
How can I get the resulting portrait video in the center of the screen?
func createExportSession(for videoURL: URL) {
let asset = AVURLAsset(url: videoURL)
let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality)!
exporter.videoComposition = turnHorizontalVideoToPortraitVideo(asset: asset)
exporter.outputURL = // ...
exporter.outputFileType = AVFileType.mp4
exporter.shouldOptimizeForNetworkUse = true
exporter.exportAsynchronously { [weak self] in
// ...
// the exporter.url is eventually added to an AVURLAsset and played inside an AVPlayer
}
}
func turnHorizontalVideoToPortraitVideo(asset: AVURLAsset) -> AVVideoComposition {
let track = asset.tracks(withMediaType: AVMediaType.video)[0]
let renderSize = CGSize(width: 720, height: 1280)
var transform1 = track.preferredTransform
transform1 = transform1.concatenating(CGAffineTransform(rotationAngle: CGFloat(90.0 * .pi / 180)))
transform1 = transform1.concatenating(CGAffineTransform(translationX: track.naturalSize.width, y: 0))
let transform2 = CGAffineTransform(translationX: track.naturalSize.height, y: (track.naturalSize.width - track.naturalSize.height) / 2)
let transform3 = transform2.rotated(by: CGFloat(Double.pi/2)).concatenating(transform1)
let translate = CGAffineTransform(translationX: renderSize.width, y: renderSize.height)
let rotateFromUpsideDown = translate.rotated(by: CGFloat(Double.pi)) // without this the portrait video is always upside down
let finalTransform = transform3.concatenating(rotateFromUpsideDown)
let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
transformer.setTransform(finalTransform, at: .zero)
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRange(start: .zero, duration: asset.duration)
instruction.layerInstructions = [transformer]
let videoComposition = AVMutableVideoComposition()
videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
videoComposition.renderSize = renderSize
videoComposition.instructions = [instruction]
return videoComposition
}
The initial horizontal video:
The resulting portrait video after the above code is ran. The video is incorrectly centered on the screen:
This is the way that it should be centered:
If anyone has a better answer, please post it, I'll check and accept it.
Unbeknownst to me the video was in the correct position but the negative black bar space was causing the video to be appear that it was misaligned. Changing the
AVMutableVideoCompositionInstruction()
.backgroundColor
shows the negative black bar space issue in yellow:To fix it for a
.landscapeRight
video, I divided thefinalTransform.ty
in half and subtracted that from a translation-y-value. For a.landscapeLeft
video I added the code below:The result for a
.landscapeRight
video:The result for a
.landscapeRight
video with the negative black bar space in yellow to show how it's now centered: