Search code examples
iosswiftvideoavassetexportsessionavmutablecomposition

AVAssetExportSession does not honour videocomposition instructions


I am trying to apply AVMutableVideoCompositionLayerInstruction on an AVMutableComposition for a video. The problem is it does not honour the instruction when the video is saved using AVAssetExportSession. The weird part is, the same composition works with AVPlayer (AVPlayer honours the instruction).

Here's the code:

        let path = Bundle.main.path(forResource: "flame", ofType: "mp4")
        let url = NSURL(fileURLWithPath: path!)
        let asset = AVAsset(url: url as URL)

        let mutableComposition = AVMutableComposition()

        let type = AVMediaTypeVideo
        let prefTrackID = kCMPersistentTrackID_Invalid

        let sourceVideoAssetTrack: AVAssetTrack = asset.tracks(withMediaType: type).first!
        let sourceAudioAssetTrack: AVAssetTrack = asset.tracks(withMediaType: AVMediaTypeAudio).first!

        let videoCompositionTrack1 = mutableComposition.addMutableTrack(withMediaType: type, preferredTrackID: prefTrackID)


        do {
            let range = CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(60,600))
            try videoCompositionTrack1.insertTimeRange(range, of: sourceVideoAssetTrack, at: kCMTimeZero)
        }catch { print(error) }

        let firstTransform = videoCompositionTrack1.preferredTransform;

        let fromLayer = AVMutableVideoCompositionLayerInstruction(assetTrack: videoCompositionTrack1)
        fromLayer.setTransform(firstTransform, at: kCMTimeZero)
        fromLayer.setCropRectangle(CGRect.init(x: 5, y: 5, width: 200, height: 200), at: kCMTimeZero)

        let instruction = AVMutableVideoCompositionInstruction()
        instruction.layerInstructions = [fromLayer]
        instruction.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(60,600))

        videoComposition = AVMutableVideoComposition()
        videoComposition!.instructions = [instruction]
        videoComposition!.renderSize = CGSize.init(width: 300, height: 300)
        videoComposition!.frameDuration = CMTimeMake(1, 30)


        if(true){ // just to switch between the saving and playing modes
            var exportPath: NSString = NSTemporaryDirectory().appendingFormat("/video.mov")
            var exportUrl: NSURL = NSURL.fileURL(withPath: exportPath as String) as NSURL

            var exporter = AVAssetExportSession(asset: mutableComposition, presetName: AVAssetExportPresetMediumQuality)!
            exporter.outputURL = exportUrl as URL
            exporter.videoComposition = videoComposition!

            exporter.outputFileType = AVFileTypeMPEG4
            exporter.shouldOptimizeForNetworkUse = true
            exporter.canPerformMultiplePassesOverSourceMediaData = true

            exporter.exportAsynchronously(completionHandler: {
                PHPhotoLibrary.shared().performChanges({
                    PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: exportUrl as URL)
                }) { completed, error in
                    if completed {
                        print("Video is saved!")
                    }
                }
            })
        }
        else{
            let playerItem = AVPlayerItem(asset: mutableComposition)
            playerItem.videoComposition = videoComposition!
            player = AVPlayer(playerItem: playerItem)
            playerLayer = AVPlayerLayer(player: player)
            playerLayer.frame = self.view.frame
            self.view.layer.addSublayer(playerLayer)
            player.play()
        }

AVPlayer honours cropRectangle instruction

AVPlayer honours cropRectangle instruction as seen above

Saved video is same as the original video

Saved video is same as the original video.

I am building this on iOS 9. What am I doing wrong?


Solution

  • When applying a transform on a AVMutableVideoCompositionLayerInstruction you can get the desired transform needed from the AVAsset's preferredTransform

    EDIT: Turns out it was an export error for existing file, either use a unique name when trying to write e.g.

    String(Date) + ".mov"
    

    Or delete before trying to write