How to merge multiple videos with different frame rates?

How to merge multiple videos with different frame rates?

I have requirement is that user have multiple clips and app gives 3 feature. user can make slow motion, user can make it faster. after changing speed user can merge them to make single video and save to device.

for example
clip 1, clip 2 and clip 3 record in normal speed and then convert clip 1 in slow motion, clip 2 is in normal speed and clip 3 in fast speed and then when user merge it the those three clips will be combine together and make it in one clip and user can share in social network.

recording video with AVFoundation or select video from gallery

func convertVideoWithSpeed(completion:()->()) {

    if RecordedSegment.segments.count > 0 {
        self.exportVideoWithMode(RecordedSegment.segments.first!,title: "clip_\(counter).mp4", completion: { [unowned self] (path) in
            RecordedSegment.segments.removeFirst()
            self.counter = self.counter + 1
            self.mergedVideArray.append("clip_\(self.counter).mp4")
            self.convertVideoWithSpeed(completion)
        })
    } else {
        var arr1 = [NSURL]()
        for track in self.mergedVideArray {
            let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
            var finalURL = documentsURL.URLByAppendingPathComponent(track)
            arr1.append(finalURL)
        }

        self.mergeVideos(self.mergedVideArray, completion: { 
            completion()
        })
    }
}

Converting video frame rates for different clips

func exportVideoWithMode(segment:RecordedSegment,title:String,completion:(path:String)->()) {
    let mixComposition = AVMutableComposition()
    let videoTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID())
    let audioTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID())
    let startTimer = kCMTimeZero
    print(RecordedSegment.segments)
    var size = CGSizeZero
    let astTrack = AVAsset(URL: NSURL(string: segment.path!)!)
    size = astTrack.tracksWithMediaType(AVMediaTypeVideo)[0].naturalSize
    do {
        try videoTrack.insertTimeRange(CMTimeRangeMake(startTimer, astTrack.duration), ofTrack: astTrack.tracksWithMediaType(AVMediaTypeVideo)[0] , atTime: startTimer)
        try audioTrack.insertTimeRange(CMTimeRangeMake(startTimer, astTrack.duration), ofTrack: astTrack.tracksWithMediaType(AVMediaTypeAudio)[0] , atTime: startTimer)
    } catch _ {
        print("Failed to load first track")
    }

    let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
    let finalURL = documentsURL.URLByAppendingPathComponent(title)

    let instruction = AVMutableVideoCompositionInstruction()
    let layerInstruct = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
    instruction.layerInstructions = [layerInstruct]
    let videoComposition = AVMutableVideoComposition()
    videoComposition.instructions = [instruction]
    if segment.mode == .Slow {
        videoComposition.frameDuration = CMTimeMake(1, 15)
    } else if segment.mode == .Fast {
        videoComposition.frameDuration = CMTimeMake(1, 30)
    } else {
        videoComposition.frameDuration = CMTimeMake(1, 30)
    }
    videoComposition.renderSize = size
    videoComposition.renderScale = 1
    instruction.timeRange = CMTimeRangeMake(kCMTimeZero, astTrack.duration)

    guard let exportSession = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) else { return }
    exportSession.outputURL = finalURL
    exportSession.outputFileType = AVFileTypeQuickTimeMovie
    exportSession.shouldOptimizeForNetworkUse = true
    exportSession.videoComposition = videoComposition

    if NSFileManager.defaultManager().fileExistsAtPath(finalURL.path!) {
        do {
            try NSFileManager.defaultManager().removeItemAtURL(finalURL)
        } catch {

        }
    }

    // 6 - Perform the Export
    exportSession.exportAsynchronouslyWithCompletionHandler() {

        let error = exportSession.error?.code
        print(exportSession.error)
        if exportSession.status == .Cancelled {
            print("Export was cancelled")
            GlobalUtility.hideActivityIndi(self)
        } else if exportSession.status == .Completed {
            print("completed")
            let asset = AVAsset(URL: finalURL)
            let track = asset.tracksWithMediaType(AVMediaTypeVideo)[0]
            print("==============\(track.nominalFrameRate)")
            completion(path: finalURL.path!)
        } else if error == nil{
            completion(path: finalURL.path!)
        }else{
            if exportSession.status == .Cancelled {
                print("Export was cancelled")
                GlobalUtility.hideActivityIndi(self)
            }
            GlobalUtility.hideActivityIndi(self)
        }
    }
}

merging them to one video

func mergeVideos(mergePaths:[String],completion:()->()) {
    var count = 0
    let mixComposition = AVMutableComposition()
    let videoTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID())
    let audioTrack = mixComposition.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID())
    var startTimer = kCMTimeZero
    print(RecordedSegment.segments)
    for track in mergePaths {
        let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
        var finalURL = documentsURL.URLByAppendingPathComponent(track)
        if NSFileManager.defaultManager().fileExistsAtPath(finalURL.path!) {
            let astTrack = AVAsset(URL: finalURL)
            let video = astTrack.tracksWithMediaType(AVMediaTypeVideo)
            let audio = astTrack.tracksWithMediaType(AVMediaTypeAudio)
            if audio.count > 0 && video.count > 0 {
                do {
                    try videoTrack.insertTimeRange(CMTimeRangeMake(startTimer, astTrack.duration), ofTrack: video[0] , atTime: startTimer)
                    try audioTrack.insertTimeRange(CMTimeRangeMake(startTimer, astTrack.duration), ofTrack: audio[0] , atTime: startTimer)
                    startTimer = (videoTrack.asset?.duration)!
                } catch _ {
                    print("Failed to load first track")
                }
            } else {
                print("file not exist")
            }
        } else {
            print("tracks not exist")
        }
    }
    //let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
    let finalURL = self.recordSession.outputUrl
    count = count + 1

    guard let exportSession = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) else { return }
    exportSession.outputURL = finalURL
    exportSession.outputFileType = AVFileTypeQuickTimeMovie
    exportSession.shouldOptimizeForNetworkUse = true

    if NSFileManager.defaultManager().fileExistsAtPath(self.recordSession.outputUrl.path!) {
        do {
            try NSFileManager.defaultManager().removeItemAtURL(self.recordSession.outputUrl)
        } catch {

        }
    }

    // 6 - Perform the Export
    exportSession.exportAsynchronouslyWithCompletionHandler() {

        let error = exportSession.error?.code
        print(exportSession.error)
        if exportSession.status == .Cancelled {
            print("Export was cancelled")
            GlobalUtility.hideActivityIndi(self)
        } else if exportSession.status == .Completed {
            print("completed")
            completion()
        } else if error == nil{
            completion()
        }else{
            if exportSession.status == .Cancelled {
                print("Export was cancelled")
                GlobalUtility.hideActivityIndi(self)
            }
            GlobalUtility.hideActivityIndi(self)
        }
    }
}
  • Nik Frolov

    Hi, i’m trying to implement your code, but video didnt slowed.

    How did you capture slowed video?