Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift: Square video composition

I am following the below code for square video composition

func completeWithVideoAtURL(input: NSURL) {
    let asset = AVAsset(url: input as URL)
    let output = NSURL(fileURLWithPath: NSHomeDirectory() + "/Documents/Video.mp4")

    let session = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetMediumQuality)!
    session.videoComposition = self.squareVideoCompositionForAsset(asset: asset)
    session.outputURL = output as URL
    session.outputFileType = AVFileTypeMPEG4
    session.shouldOptimizeForNetworkUse = true        
    session.exportAsynchronously(completionHandler: { () -> Void in
        DispatchQueue.main.async(execute: { () -> Void in
            // do something with the output
            print("\(output)")
            PHPhotoLibrary.shared().performChanges({
                PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: output as URL)
            }) { saved, error in
                if saved {
                    print("saved to gallery")
                }
            }
        })
    })
}

func squareVideoCompositionForAsset(asset: AVAsset) -> AVVideoComposition {
    let track = asset.tracks(withMediaType: AVMediaTypeVideo)[0]
    let length = max(track.naturalSize.width, track.naturalSize.height)

    var transform = track.preferredTransform

    let size = track.naturalSize
    let scale: CGFloat = (transform.a == -1 && transform.b == 0 && transform.c == 0 && transform.d == -1) ? -1 : 1 // check for inversion

    transform = transform.translatedBy(x: scale * -(size.width - length) / 2, y: scale * -(size.height - length) / 2)

    let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
    transformer.setTransform(transform, at: kCMTimeZero)

    let instruction = AVMutableVideoCompositionInstruction()
    instruction.timeRange = CMTimeRange(start: kCMTimeZero, duration: kCMTimePositiveInfinity)
    instruction.layerInstructions = [transformer]

    let composition = AVMutableVideoComposition()
    composition.frameDuration = CMTime(value: 1, timescale: 30)
    composition.renderSize = CGSize(width: length, height: length)
    composition.instructions = [instruction]

    return composition
}

From the squareVideoCompositionForAsset() function I take the max value for length between track.naturalSize.width & track.naturalSize.height cause I don't want to crop any partial part of the video. If I take min value, for portrait video it cropped the upper & lower portion of the video and for landscape video it cropped some left & right portion of the video.

  • For landscape video, output is okay

enter image description here

  • but for portrait video, output is like following image

enter image description here

the video gets left sided. Is it possible to center the video? Any assistance would be great and sorry for long explanation.

like image 862
pigeon_39 Avatar asked Aug 04 '26 22:08

pigeon_39


2 Answers

instead of this line

let scale: CGFloat = (transform.a == -1 && transform.b == 0 && transform.c == 0 && transform.d == -1) ? -1 : 1

I just used this

    var scale = CGFloat()
    if (transform.a == 0 && transform.b == 1 && transform.c == -1 && transform.d == 0) {
        scale = -1
    }
    else if (transform.a == 0 && transform.b == -1 && transform.c == 1 && transform.d == 0) {
        scale = -1
    }
    else if (transform.a == 1 && transform.b == 0 && transform.c == 0 && transform.d == 1) {
        scale = 1
    }
    else if (transform.a == -1 && transform.b == 0 && transform.c == 0 && transform.d == -1) {
        scale = 1
    }

and it worked like a charm

like image 150
pigeon_39 Avatar answered Aug 07 '26 12:08

pigeon_39


Swift 4.2

func suqareCropVideo(videoURL: URL, withSide sideLength: CGFloat, completion: @escaping (_ resultURL: URL?, _ error: Error?) -> ()) {

    let asset = AVAsset(url: videoURL)
    if let assetVideoTrack = asset.tracks(withMediaType: .video).last {

        let originalSize = assetVideoTrack.naturalSize
        var scale: CGFloat
        if originalSize.width < originalSize.height {
            scale = sideLength / originalSize.width
        } else {
            scale = sideLength / originalSize.height
        }

        let scaledSize = CGSize(width: originalSize.width * scale, height: originalSize.height * scale)
        let topLeft = CGPoint(x: sideLength * 0.5 - scaledSize.width * 0.5, y: sideLength  * 0.5 - scaledSize.height * 0.5)

        let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: assetVideoTrack)

        var orientationTransform = assetVideoTrack.preferredTransform

        if (orientationTransform.tx == originalSize.width || orientationTransform.tx == originalSize.height) {
            orientationTransform.tx = sideLength
        }

        if (orientationTransform.ty == originalSize.width || orientationTransform.ty == originalSize.height) {
            orientationTransform.ty = sideLength
        }

        let transform = CGAffineTransform(scaleX: scale, y: scale).concatenating(CGAffineTransform(translationX: topLeft.x, y: topLeft.y)).concatenating(orientationTransform)

        layerInstruction.setTransform(transform, at: .zero)


        let instruction = AVMutableVideoCompositionInstruction()
        instruction.layerInstructions = [layerInstruction]
        instruction.timeRange = assetVideoTrack.timeRange

        let videoComposition = AVMutableVideoComposition()
        videoComposition.renderSize = CGSize(width: sideLength, height: sideLength)
        videoComposition.renderScale = 1.0
        videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
        videoComposition.instructions = [instruction]

        if let export = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) {
            export.videoComposition = videoComposition
            export.outputURL = NSURL.fileURL(withPath: "\(NSTemporaryDirectory())\(NSUUID().uuidString).mp4")
            export.outputFileType = AVFileType.mp4
            export.shouldOptimizeForNetworkUse = true
            export.exportAsynchronously {
                DispatchQueue.main.async {
                    if export.status == .completed {
                        completion(export.outputURL, nil)
                    } else {
                        completion(nil, export.error)
                    }
                }
            }
        } else {
            completion(nil, nil)
        }
    }

}
like image 38
Abhijeet Rai Avatar answered Aug 07 '26 12:08

Abhijeet Rai



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!