1

I am trying to crop the video in landscape mode using AVKit. I have tried several similar questions on Stackoverflow but none is working as expected. This is what I have tried so far.

self.cropOfficialRect = CGRect(x: 0, y: (reader.size.height/2)-100-self.currentPosition.height, width: UIScreen.main.bounds.width, height: 200)
let assest = AVAsset(url: self.videoURL)
let videoTrack = assest.tracks(withMediaType: .video)[0]
let originalSize = videoTrack.naturalSize
let cropRectIsPortrait = originalSize.width <= originalSize.height

if cropRectIsPortrait{
    self.videoViewScale = max(originalSize.width / reader.size.width, originalSize.height / reader.size.height)
}
else{
}

extension AVAsset {
    
    func cropVideoTrack(at index: Int, cropRect: CGRect, outputURL: URL, completion: @escaping (Result<Void, Swift.Error>) -> Void) {
                
        enum Orientation {
            case up, down, right, left
        }
        
        func orientation(for track: AVAssetTrack) -> Orientation {
            let t = track.preferredTransform
            
            if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0) {             // Portrait
                return .up
            } else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0) {      // PortraitUpsideDown
                return .down
            } else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0) {       // LandscapeRight
                return .right
            } else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0) {     // LandscapeLeft
                return .left
            } else {
                return .up
            }
        }
        
        let videoTrack = tracks(withMediaType: .video)[index]
        let originalSize = videoTrack.naturalSize
        let trackOrientation = orientation(for: videoTrack)
        let cropRectIsPortrait = cropRect.width <= cropRect.height
                    
        let videoComposition = AVMutableVideoComposition()
        videoComposition.renderSize = cropRect.size
        videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
        
        let layerinstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
        let instruction = AVMutableVideoCompositionInstruction()
        instruction.timeRange = CMTimeRange(start: .zero, duration: CMTime(seconds: 10000, preferredTimescale: 30))
        layerinstruction.setCropRectangle(cropRect, at: .zero)//cropping Rectangle
        
        let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
        
        var finalTransform: CGAffineTransform = CGAffineTransform.identity // setup a transform that grows the video, effectively causing a crop
        
        if trackOrientation == .up {
            if !cropRectIsPortrait { // center video rect vertically
                finalTransform = finalTransform
                    //.translatedBy(x: originalSize.height, y: -(originalSize.width - cropRect.size.height) / 2)
                    .translatedBy(x: originalSize.height, y: 0)
                    .rotated(by: CGFloat(90.0.radians))
            } else {
                finalTransform = finalTransform
                    .rotated(by: CGFloat(90.0.radians))
                    .translatedBy(x: 0, y: -originalSize.height)
            }
            
        } else if trackOrientation == .down {
            if !cropRectIsPortrait { // center video rect vertically (NOTE: did not test this case, since camera doesn't support .portraitUpsideDown in this app)
                finalTransform = finalTransform
                    .translatedBy(x: -originalSize.height, y: (originalSize.width - cropRect.size.height) / 2)
                    .rotated(by: CGFloat(-90.0.radians))
            } else {
                finalTransform = finalTransform
                    .rotated(by: CGFloat(-90.0.radians))
                    .translatedBy(x: -originalSize.width, y: -(originalSize.height - cropRect.size.height) / 2)
            }
            
        } else if trackOrientation == .right {
            if cropRectIsPortrait {
                finalTransform = finalTransform.translatedBy(x: -(originalSize.width - cropRect.size.width) / 2, y: 0)
            } else {
                //finalTransform = CGAffineTransform(
                finalTransform = CGAffineTransform(translationX: -(originalSize.width - cropRect.size.width) / 2, y: -cropRect.minY)
            }
            
        } else if trackOrientation == .left {
            if cropRectIsPortrait { // center video rect horizontally
                finalTransform = finalTransform
                    .rotated(by: CGFloat(-180.0.radians))
                    .translatedBy(x: -originalSize.width + (originalSize.width - cropRect.size.width) / 2, y: -originalSize.height)
            } else {
                finalTransform = finalTransform
                    .rotated(by: CGFloat(-180.0.radians))
                    .translatedBy(x: -originalSize.width, y: -originalSize.height)
            }
        }
        
        layerinstruction.setTransform(finalTransform, at: .zero)
        instruction.layerInstructions = [layerinstruction]
        videoComposition.instructions = [instruction]
        
        let exporter = AVAssetExportSession(asset: self, presetName: AVAssetExportPresetHighestQuality)
        exporter?.videoComposition = videoComposition
        exporter?.outputURL = outputURL
        exporter?.outputFileType = AVFileType.mov
        
        exporter?.exportAsynchronously(completionHandler: { [weak exporter] in
            DispatchQueue.main.async {
                if let error = exporter?.error {
                    completion(.failure(error))
                } else {
                    completion(.success(()))
                }
            }
        })
    }
}

Above code is working perfectly fine for cropping of portrait videos but if the video is in the ratio 16:9 then it is not working as expected.

James Z
  • 12,209
  • 10
  • 24
  • 44
Harjot Singh
  • 535
  • 7
  • 24

0 Answers0