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.
- Create an export session using the
moviecomposition you made earlier. - Set the save location to some file URL on your device.
- Wait for the exporter to finish and display the result.
AVAsset can take a long time and is asynchronous. You will want to display a spinner or a message to your user.
0 Comments
Leave a Comment