Swift

Merge Multiple Videos in Swift

AVFoundation: This class has the purpose of arranging different assets and types of assets into a single asset for playback or processing. Asset types can include audio and video subtitles, metadata and text. AVCompos...

J
Joynal Abedin
4
AVFoundation: This class has the purpose of arranging different assets and types of assets into a single asset for playback or processing. Asset types can include audio and video subtitles, metadata and text. AVComposition is a subclass of AVAsset Try to remember that the AVTrack is a wrapper around the actual pixel, sound-wave, or other data and that AVAsset is a collection of AVTrack objects. When we want to combine and manipulae AVAsset objects, an AVComposition helps us do that. Changes made to any AVAsset by an AVComposition will not impact the original media file. Apple keeps the editing feature of an AVComposition separate from the playback features by creating an AVMutableComposition object. Each track in our composition will have a start time and a duration. The composition will ensure that all of the data it holds dispalys at the right time and that the tracks stay in sync. At the simplest, a video clip is a single AVTrack of audio data and a single AVTrack of video data.
//
//  VideoEditor.swift
//  MergeMultipleVideoDemo
//
//  Created by Joynal Abedin on 11/12/22.
//
import UIKit
import AVFoundation
import Photos

class VideoEditor {
    
    let mixComposition = AVMutableComposition()
    let mainComposition = AVMutableVideoComposition()
    let mainInstruction = AVMutableVideoCompositionInstruction()
    var allVideoInstruction = [AVMutableVideoCompositionLayerInstruction]()
    var startDuration: CMTime = .zero
    var progressTimer = Timer()
    
    func makeVideoComposition(fromVideoAt videoAsset: [AVAsset], onComplete: @escaping (AVPlayerItem?) -> Void) {
        
        for i in 0.. AVMutableVideoCompositionLayerInstruction {
        let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
        let transform = assetTrack.preferredTransform
        
        instruction.setTransform(transform, at: .zero)
        
        return instruction
    }
    
    //Export final video
    func exportVideo(mixComposition composition: AVMutableComposition, mainComposition videoComposition: AVMutableVideoComposition) {
        //export code here
        print(\"export pressed\")
        //  create new file to receive data
        let dirPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let docsDir = dirPaths[0] as NSString
        let movieFilePath = docsDir.appendingPathComponent(\"result.mov\")
        let movieDestinationUrl = NSURL(fileURLWithPath: movieFilePath)
        
        // use AVAssetExportSession to export video
        let assetExport = AVAssetExportSession(asset: composition, presetName:AVAssetExportPresetHighestQuality)
        assetExport?.videoComposition = videoComposition
        //assetExport?.audioMix = mix
        assetExport?.outputFileType = AVFileType.mov
        
        // Check exist and remove old file
        FileManager.default.removeItemIfExisted(movieDestinationUrl as URL)
        
        //exporting progress value
        guard assetExport != nil else {return}
        progressTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
            let p = Double(assetExport!.progress)
            print(\"progress: \(p)\")
            if(assetExport!.progress >= 0.99)
            {
                self.progressTimer.invalidate()
            }
        }
        
        assetExport?.outputURL = movieDestinationUrl as URL
        assetExport?.exportAsynchronously(completionHandler: {
            switch assetExport!.status {
            case AVAssetExportSession.Status.failed:
                print(\"failed\")
                print(assetExport?.error ?? \"unknown error\")
            case AVAssetExportSession.Status.cancelled:
                print(\"cancelled\")
                print(assetExport?.error ?? \"unknown error\")
            case AVAssetExportSession.Status.completed:
                print(\"✅export complete and saved\")
                PHPhotoLibrary.shared().performChanges({
                    PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: movieDestinationUrl as URL)
                }) { saved, error in
                    if saved {
                        print(\"Saved\")
                    }
                }
            default:
                print(\"Movie complete\")
                
                PHPhotoLibrary.shared().performChanges({
                    PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: movieDestinationUrl as URL)
                }) { saved, error in
                    if saved {
                        print(\"Saved\")
                    }
                }
                
            }
        })
    }
    
}

//check existing item
extension FileManager {
    func removeItemIfExisted(_ url:URL) -> Void {
        if FileManager.default.fileExists(atPath: url.path) {
            do {
                try FileManager.default.removeItem(atPath: url.path)
            }
            catch {
                print(\"Failed to delete file\")
            }
        }
    }
}
In this above code, first of all we created a new, empty AVMutableComposition and then adds one tracks. One track will be able to hold .video assets. AVFoundation supports many kinds of audio and video formats but  the .video track cannot hold .audio formats. The kCMPersistendTrackID_Invalid signalds that you want a new , unique track id generated. After creation, the AVMutableComposition has a start time of CMTime.zero and a duration of CMTime.zero. Use the insertTimeRange(_ timeRange: CMTimeRange, of track: AVAssetTrack, at startTime: CMTime) throws function on the audio and again on the video track to add data from the clip. In order to add data to the tracks, you load an AVAsset, determine what range of time will go into the new composition, and then call the insert function. Alternatively you may want to export the new composition as a new movie file. Again, because the composition is an AVAsset using a standard AVAssetExportSession will write the movie to disk.
  1. Create an export session using the movie composition you made earlier.
  2. Set the save location to some file URL on your device.
  3. Wait for the exporter to finish and display the result.
Don't forget that exporting an AVAsset can take a long time and is asynchronous. You will want to display a spinner or a message to your user.
J

Written by Joynal Abedin

Passionate about technology, code, and sharing knowledge.

0 Comments

Leave a Comment