Objective : I have a Video over which I have a UIView which contains animated GIFs(not locally stored, but using giphy api), Texts, or hand drawings. I want to export this along with the image in a single video.
What I did :
I created a UIView on which the animations are. Then converted that to CALayer and added to video with AVMutableVideoCompotion.
Problem : The UIView with animations is being converted to an Image instead of a video. How can I solve this.
Below is the Program for my export session. Any pointers will be really helpful.
func convertVideoAndSaveTophotoLibrary(videoURL: URL) {
let file = FileManager.shared.getDocumentDirectory(path: currentFilename)
FileManager.shared.clearPreviousFiles(withPath: file.path)
// File to composit
let asset = AVURLAsset(url: videoURL as URL)
let composition = AVMutableComposition.init()
composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)
let clipVideoTrack = asset.tracks(withMediaType: AVMediaType.video)[0]
// Rotate to potrait
let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: clipVideoTrack)
let videoTransform:CGAffineTransform = clipVideoTrack.preferredTransform
//fix orientation
var videoAssetOrientation_ = UIImage.Orientation.up
var isVideoAssetPortrait_ = false
if videoTransform.a == 0 && videoTransform.b == 1.0 && videoTransform.c == -1.0 && videoTransform.d == 0 {
videoAssetOrientation_ = UIImage.Orientation.right
isVideoAssetPortrait_ = true
}
if videoTransform.a == 0 && videoTransform.b == -1.0 && videoTransform.c == 1.0 && videoTransform.d == 0 {
videoAssetOrientation_ = UIImage.Orientation.left
isVideoAssetPortrait_ = true
}
if videoTransform.a == 1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == 1.0 {
videoAssetOrientation_ = UIImage.Orientation.up
}
if videoTransform.a == -1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == -1.0 {
videoAssetOrientation_ = UIImage.Orientation.down;
}
transformer.setTransform(clipVideoTrack.preferredTransform, at: CMTime.zero)
transformer.setOpacity(0.0, at: asset.duration)
//adjust the render size if neccessary
var naturalSize: CGSize
if(isVideoAssetPortrait_){
naturalSize = CGSize(width: clipVideoTrack.naturalSize.height, height: clipVideoTrack.naturalSize.width)
} else {
naturalSize = clipVideoTrack.naturalSize;
}
var renderWidth: CGFloat!
var renderHeight: CGFloat!
renderWidth = naturalSize.width
renderHeight = naturalSize.height
let parentlayer = CALayer()
let videoLayer = CALayer()
let watermarkLayer = CALayer()
let videoComposition = AVMutableVideoComposition()
videoComposition.renderSize = CGSize(width: renderWidth, height: renderHeight)
videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
videoComposition.renderScale = 1.0
//---------------------->>>>>> converting uiview to uiimage
watermarkLayer.contents = canvasView.asImage().cgImage
parentlayer.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: naturalSize)
videoLayer.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: naturalSize)
watermarkLayer.frame = CGRect(origin: CGPoint(x: 0, y: 0), size: naturalSize)
parentlayer.addSublayer(videoLayer)
parentlayer.addSublayer(watermarkLayer)
//---------------------->>>>>> Add view to video
videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayers: [videoLayer], in: parentlayer)
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: CMTimeMakeWithSeconds(60, preferredTimescale: 30))
instruction.layerInstructions = [transformer]
videoComposition.instructions = [instruction]
let exporter = AVAssetExportSession.init(asset: asset, presetName: AVAssetExportPresetHighestQuality)
exporter?.outputFileType = AVFileType.mp4
exporter?.outputURL = file
exporter?.videoComposition = videoComposition
exporter?.shouldOptimizeForNetworkUse = true
exporter!.exportAsynchronously(completionHandler: {() -> Void in
if exporter?.status == .completed {
let outputURL: URL? = exporter?.outputURL
self.saveToPhotoLibrary(url: outputURL!)
}
})
}
Converting UIView to UIimage
extension UIView {
func asImage() -> UIImage {
let renderer = UIGraphicsImageRenderer(bounds: bounds)
return renderer.image { rendererContext in
layer.render(in: rendererContext.cgContext)
}
}
}
Code for Adding the GIF(I am using the Giphy API here), so the gif is downloaded and then added
func didSelectMedia(giphyViewController: GiphyViewController, media: GPHMedia) {
addMedia(media: media)
giphyViewController.dismiss(animated: true) { [weak self] in
self?.giphy = nil
}
}
// GPHMediaView is a subclass of UIImageView
func addMedia(media: GPHMedia) {
let mediaView = GPHMediaView()
mediaView.media = media
mediaView.contentMode = .scaleAspectFill
mediaView.frame.size = CGSize(width: 150, height: 150)
mediaView.center = canvasView.center
canvasView.addSubview(mediaView)
print(mediaView.frame)
self.addGesturesTo(mediaView)
}
What I am getting: The cat over the video is a gif. But sadly all i get is one frame. Now I know that is because I am converting the view to image. But that's the solution I need to know. How do I have the gif merged to the video.

You have two ways to archive this. First you can convert gif to video and add it to composition, but you lose alpha channel. Second way and more relevant is to add CAKeyframeAnimation on gif layer. To do this you should get all image frames from gif and put it all to key CAKeyframeAnimation.values and set duration which equal to framesCount * framesPerSecond.