Crop video in landscape in Swift - ios

I am trying to crop the video in landscape mode using AVKit. I have tried several similar questions on Stackoverflow but none is working as expected. This is what I have tried so far.
self.cropOfficialRect = CGRect(x: 0, y: (reader.size.height/2)-100-self.currentPosition.height, width: UIScreen.main.bounds.width, height: 200)
let assest = AVAsset(url: self.videoURL)
let videoTrack = assest.tracks(withMediaType: .video)[0]
let originalSize = videoTrack.naturalSize
let cropRectIsPortrait = originalSize.width <= originalSize.height
if cropRectIsPortrait{
self.videoViewScale = max(originalSize.width / reader.size.width, originalSize.height / reader.size.height)
}
else{
}
extension AVAsset {
func cropVideoTrack(at index: Int, cropRect: CGRect, outputURL: URL, completion: #escaping (Result<Void, Swift.Error>) -> Void) {
enum Orientation {
case up, down, right, left
}
func orientation(for track: AVAssetTrack) -> Orientation {
let t = track.preferredTransform
if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0) { // Portrait
return .up
} else if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0) { // PortraitUpsideDown
return .down
} else if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0) { // LandscapeRight
return .right
} else if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0) { // LandscapeLeft
return .left
} else {
return .up
}
}
let videoTrack = tracks(withMediaType: .video)[index]
let originalSize = videoTrack.naturalSize
let trackOrientation = orientation(for: videoTrack)
let cropRectIsPortrait = cropRect.width <= cropRect.height
let videoComposition = AVMutableVideoComposition()
videoComposition.renderSize = cropRect.size
videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
let layerinstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRange(start: .zero, duration: CMTime(seconds: 10000, preferredTimescale: 30))
layerinstruction.setCropRectangle(cropRect, at: .zero)//cropping Rectangle
let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
var finalTransform: CGAffineTransform = CGAffineTransform.identity // setup a transform that grows the video, effectively causing a crop
if trackOrientation == .up {
if !cropRectIsPortrait { // center video rect vertically
finalTransform = finalTransform
//.translatedBy(x: originalSize.height, y: -(originalSize.width - cropRect.size.height) / 2)
.translatedBy(x: originalSize.height, y: 0)
.rotated(by: CGFloat(90.0.radians))
} else {
finalTransform = finalTransform
.rotated(by: CGFloat(90.0.radians))
.translatedBy(x: 0, y: -originalSize.height)
}
} else if trackOrientation == .down {
if !cropRectIsPortrait { // center video rect vertically (NOTE: did not test this case, since camera doesn't support .portraitUpsideDown in this app)
finalTransform = finalTransform
.translatedBy(x: -originalSize.height, y: (originalSize.width - cropRect.size.height) / 2)
.rotated(by: CGFloat(-90.0.radians))
} else {
finalTransform = finalTransform
.rotated(by: CGFloat(-90.0.radians))
.translatedBy(x: -originalSize.width, y: -(originalSize.height - cropRect.size.height) / 2)
}
} else if trackOrientation == .right {
if cropRectIsPortrait {
finalTransform = finalTransform.translatedBy(x: -(originalSize.width - cropRect.size.width) / 2, y: 0)
} else {
//finalTransform = CGAffineTransform(
finalTransform = CGAffineTransform(translationX: -(originalSize.width - cropRect.size.width) / 2, y: -cropRect.minY)
}
} else if trackOrientation == .left {
if cropRectIsPortrait { // center video rect horizontally
finalTransform = finalTransform
.rotated(by: CGFloat(-180.0.radians))
.translatedBy(x: -originalSize.width + (originalSize.width - cropRect.size.width) / 2, y: -originalSize.height)
} else {
finalTransform = finalTransform
.rotated(by: CGFloat(-180.0.radians))
.translatedBy(x: -originalSize.width, y: -originalSize.height)
}
}
layerinstruction.setTransform(finalTransform, at: .zero)
instruction.layerInstructions = [layerinstruction]
videoComposition.instructions = [instruction]
let exporter = AVAssetExportSession(asset: self, presetName: AVAssetExportPresetHighestQuality)
exporter?.videoComposition = videoComposition
exporter?.outputURL = outputURL
exporter?.outputFileType = AVFileType.mov
exporter?.exportAsynchronously(completionHandler: { [weak exporter] in
DispatchQueue.main.async {
if let error = exporter?.error {
completion(.failure(error))
} else {
completion(.success(()))
}
}
})
}
}
Above code is working perfectly fine for cropping of portrait videos but if the video is in the ratio 16:9 then it is not working as expected.

Related

Merging video with original orientation

I have a link I can DM for a minimum working example!
Recording Videos
For recording, the AVCaptureConnection for an AVCaptureSession, I set isVideoMirrored to true when using the front camera and false when using the back camera. All in portrait orientation.
Saving Videos
When I save videos, I perform an AVAssetExportSession. If I used the front camera, I want to maintain the isVideoMirrored = true, so I create an AVMutableComposition to set the AVAsset video track's preferredTransform to CGAffineTransform(scaleX: -1.0, y: 1.0).rotated(by: CGFloat(Double.pi/2)). For the back camera, I export the AVAsset as outputted.
Part of my saving code:
if didCaptureWithFrontCamera {
let composition = AVMutableComposition()
let assetVideoTrack = asset.tracks(withMediaType: .video).last!
let assetAudioTrack = asset.tracks(withMediaType: .audio).last!
let compositionVideoTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
let compositionAudioTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
try? compositionVideoTrack?.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: asset.duration), of: assetVideoTrack, at: CMTime.zero)
try? compositionAudioTrack?.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: asset.duration), of: assetAudioTrack, at: CMTime.zero)
compositionVideoTrack?.preferredTransform = CGAffineTransform(scaleX: -1.0, y: 1.0).rotated(by: CGFloat(Double.pi/2))
guard let exportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPreset1280x720) else {
handler(nil)
return
}
exportSession.outputURL = outputURL
exportSession.outputFileType = .mp4
exportSession.shouldOptimizeForNetworkUse = true
exportSession.exportAsynchronously { handler(exportSession) }
} else {
guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPreset1280x720) else {
handler(nil)
return
}
exportSession.outputURL = outputURL
exportSession.outputFileType = .mp4
exportSession.shouldOptimizeForNetworkUse = true
exportSession.exportAsynchronously { handler(exportSession) }
}
Merging Videos
Later, to view the saved videos, I want to merge them together as a single video and maintain each by their original orientation via AVMutableComposition.
What partially has worked is setting the video track of AVMutableComposition to the preferredTransform property of the video track of an individual AVAsset video. The only problem is that a single orientation is applied to all the videos (i.e. mirroring isn't applied in a back camera recorded video and the same is applied to the front camera video too).
From solutions I've come across it appears I need to apply AVMutableVideoCompositionInstruction, but in trying to do so, the AVAssetExportSession doesn't seem to factor in the videoComposition instructions at all.
Any guidance would be extremely appreciated as I haven't been able to solve it for the life of me...
My attempted merge code:
func merge(videos: [AVURLAsset], for date: Date, completion: #escaping (_ url: URL, _ asset: AVAssetExportSession)->()) {
let videoComposition = AVMutableComposition()
var lastTime: CMTime = .zero
var count = 0
var instructions = [AVMutableVideoCompositionInstruction]()
let renderSize = CGSize(width: 720, height: 1280)
guard let videoCompositionTrack = videoComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return }
guard let audioCompositionTrack = videoComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return }
for video in videos {
if let videoTrack = video.tracks(withMediaType: .video)[safe: 0] {
//this is the only thing that seems to work, but work not in the way i'd hope where each video keeps its original orientation
//videoCompositionTrack.preferredTransform = videoTrack.preferredTransform
if let audioTrack = video.tracks(withMediaType: .audio)[safe: 0] {
do {
try videoCompositionTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: video.duration), of: videoTrack, at: lastTime)
try audioCompositionTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: video.duration), of: audioTrack, at: lastTime)
let layerInstruction = videoCompositionInstruction(videoTrack, asset: video, count: count)
let videoCompositionInstruction = AVMutableVideoCompositionInstruction()
videoCompositionInstruction.timeRange = CMTimeRangeMake(start: lastTime, duration: video.duration)
videoCompositionInstruction.layerInstructions = [layerInstruction]
instructions.append(videoCompositionInstruction)
} catch {
return
}
lastTime = CMTimeAdd(lastTime, video.duration)
count += 1
} else {
do {
try videoCompositionTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: video.duration), of: videoTrack, at: lastTime)
let layerInstruction = videoCompositionInstruction(videoTrack, asset: video, count: count)
let videoCompositionInstruction = AVMutableVideoCompositionInstruction()
videoCompositionInstruction.timeRange = CMTimeRangeMake(start: lastTime, duration: video.duration)
videoCompositionInstruction.layerInstructions = [layerInstruction]
instructions.append(videoCompositionInstruction)
} catch {
return
}
lastTime = CMTimeAdd(lastTime, video.duration)
count += 1
}
}
}
let mutableVideoComposition = AVMutableVideoComposition()
mutableVideoComposition.instructions = instructions
mutableVideoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
mutableVideoComposition.renderSize = renderSize
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .short
let date = dateFormatter.string(from: date)
let mergedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + "merged-\(date)" + ".mp4")
guard let exporter = AVAssetExportSession(asset: videoComposition, presetName: AVAssetExportPresetHighestQuality) else { return }
exporter.outputURL = mergedURL
exporter.outputFileType = .mp4
exporter.videoComposition = mutableVideoComposition
exporter.shouldOptimizeForNetworkUse = true
completion(mergedURL, exporter)
}
func videoCompositionInstruction(_ firstTrack: AVAssetTrack, asset: AVAsset, count: Int) -> AVMutableVideoCompositionLayerInstruction {
let renderSize = CGSize(width: 720, height: 1280)
let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: firstTrack)
let assetTrack = asset.tracks(withMediaType: .video)[0]
let t = assetTrack.fixedPreferredTransform // new transform fix
let assetInfo = orientationFromTransform(t)
if assetInfo.isPortrait {
let scaleToFitRatio = renderSize.width / assetTrack.naturalSize.height
let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio)
var finalTransform = assetTrack.fixedPreferredTransform.concatenating(scaleFactor)
if assetInfo.orientation == .rightMirrored || assetInfo.orientation == .leftMirrored {
finalTransform = finalTransform.translatedBy(x: -t.ty, y: 0)
}
instruction.setTransform(t, at: CMTime.zero)
} else {
let renderRect = CGRect(x: 0, y: 0, width: renderSize.width, height: renderSize.height)
let videoRect = CGRect(origin: .zero, size: assetTrack.naturalSize).applying(assetTrack.fixedPreferredTransform)
let scale = renderRect.width / videoRect.width
let transform = CGAffineTransform(scaleX: renderRect.width / videoRect.width, y: (videoRect.height * scale) / assetTrack.naturalSize.height)
let translate = CGAffineTransform(translationX: .zero, y: ((renderSize.height - (videoRect.height * scale))) / 2)
instruction.setTransform(assetTrack.fixedPreferredTransform.concatenating(transform).concatenating(translate), at: .zero)
}
if count == 0 {
instruction.setOpacity(0.0, at: asset.duration)
}
return instruction
}
func orientationFromTransform(_ transform: CGAffineTransform) -> (orientation: UIImage.Orientation, isPortrait: Bool) {
var assetOrientation = UIImage.Orientation.up
var isPortrait = false
if transform.a == 0 && transform.b == 1.0 && transform.c == -1.0 && transform.d == 0 {
assetOrientation = .right
isPortrait = true
} else if transform.a == 0 && transform.b == 1.0 && transform.c == 1.0 && transform.d == 0 {
assetOrientation = .rightMirrored
isPortrait = true
} else if transform.a == 0 && transform.b == -1.0 && transform.c == 1.0 && transform.d == 0 {
assetOrientation = .left
isPortrait = true
} else if transform.a == 0 && transform.b == -1.0 && transform.c == -1.0 && transform.d == 0 {
assetOrientation = .leftMirrored
isPortrait = true
} else if transform.a == 1.0 && transform.b == 0 && transform.c == 0 && transform.d == 1.0 {
assetOrientation = .up
} else if transform.a == -1.0 && transform.b == 0 && transform.c == 0 && transform.d == -1.0 {
assetOrientation = .down
}
return (assetOrientation, isPortrait)
}
extension AVAssetTrack {
var fixedPreferredTransform: CGAffineTransform {
var t = preferredTransform
switch(t.a, t.b, t.c, t.d) {
case (1, 0, 0, 1):
t.tx = 0
t.ty = 0
case (1, 0, 0, -1):
t.tx = 0
t.ty = naturalSize.height
case (-1, 0, 0, 1):
t.tx = naturalSize.width
t.ty = 0
case (-1, 0, 0, -1):
t.tx = naturalSize.width
t.ty = naturalSize.height
case (0, -1, 1, 0):
t.tx = 0
t.ty = naturalSize.width
case (0, 1, -1, 0):
t.tx = naturalSize.height
t.ty = 0
case (0, 1, 1, 0):
t.tx = 0
t.ty = 0
case (0, -1, -1, 0):
t.tx = naturalSize.height
t.ty = naturalSize.width
default:
break
}
return t
}
}
Assuming your transformations are correct, I updated your merge function.
The main change is using a single AVMutableVideoCompositionInstruction with multiple AVMutableVideoCompositionLayerInstruction, and passing the correct CMTime value to for the layer instruction to be executed at.
func merge(videos: [AVURLAsset],
for date: Date,
completion: #escaping (_ url: URL, _ asset: AVAssetExportSession)->()) {
let videoComposition = AVMutableComposition()
guard let videoCompositionTrack = videoComposition.addMutableTrack(withMediaType: .video,
preferredTrackID: Int32(kCMPersistentTrackID_Invalid)),
let audioCompositionTrack = videoComposition.addMutableTrack(withMediaType: .audio,
preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
else { return }
var lastTime: CMTime = .zero
var layerInstructions = [AVMutableVideoCompositionLayerInstruction]()
for video in videos {
guard let videoTrack = video.tracks(withMediaType: .video)[safe: 0] else { return }
// add audio track if available
if let audioTrack = video.tracks(withMediaType: .audio)[safe: 0] {
do {
try audioCompositionTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: video.duration),
of: audioTrack,
at: lastTime)
} catch {
return
}
}
// add video track
do {
try videoCompositionTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: video.duration),
of: videoTrack,
at: lastTime)
let layerInstruction = makeVideoCompositionInstruction(videoTrack,
asset: video,
atTime: lastTime)
layerInstructions.append(layerInstruction)
} catch {
return
}
lastTime = CMTimeAdd(lastTime, video.duration)
} // end for..in videos
let renderSize = CGSize(width: 720, height: 1280)
let videoInstruction = AVMutableVideoCompositionInstruction()
videoInstruction.timeRange = CMTimeRangeMake(start: .zero, duration: lastTime)
videoInstruction.layerInstructions = layerInstructions
let mutableVideoComposition = AVMutableVideoComposition()
mutableVideoComposition.instructions = [videoInstruction]
mutableVideoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
mutableVideoComposition.renderSize = renderSize
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .short
let date = dateFormatter.string(from: date)
let mergedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + "merged-\(date)" + ".mp4")
guard let exporter = AVAssetExportSession(asset: videoComposition,
presetName: AVAssetExportPresetHighestQuality) else { return }
exporter.outputURL = mergedURL
exporter.outputFileType = .mp4
exporter.videoComposition = mutableVideoComposition
exporter.shouldOptimizeForNetworkUse = true
completion(mergedURL, exporter)
}
func makeVideoCompositionInstruction(_ videoTrack: AVAssetTrack,
asset: AVAsset,
atTime: CMTime) -> AVMutableVideoCompositionLayerInstruction {
let renderSize = CGSize(width: 720, height: 1280)
let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
let assetTrack = asset.tracks(withMediaType: .video)[0]
let t = assetTrack.fixedPreferredTransform // new transform fix
let assetInfo = orientationFromTransform(t)
if assetInfo.isPortrait {
let scaleToFitRatio = renderSize.width / assetTrack.naturalSize.height
let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio)
var finalTransform = assetTrack.fixedPreferredTransform.concatenating(scaleFactor)
if assetInfo.orientation == .rightMirrored || assetInfo.orientation == .leftMirrored {
finalTransform = finalTransform.translatedBy(x: -t.ty, y: 0)
}
instruction.setTransform(t, at: atTime)
} else {
let renderRect = CGRect(x: 0, y: 0, width: renderSize.width, height: renderSize.height)
let videoRect = CGRect(origin: .zero, size: assetTrack.naturalSize).applying(assetTrack.fixedPreferredTransform)
let scale = renderRect.width / videoRect.width
let transform = CGAffineTransform(scaleX: renderRect.width / videoRect.width,
y: (videoRect.height * scale) / assetTrack.naturalSize.height)
let translate = CGAffineTransform(translationX: .zero,
y: ((renderSize.height - (videoRect.height * scale))) / 2)
instruction.setTransform(assetTrack.fixedPreferredTransform.concatenating(transform).concatenating(translate),
at: atTime)
}
// if atTime = 0, we can assume this is the first track being added
if atTime == .zero {
instruction.setOpacity(0.0,
at: asset.duration)
}
return instruction
}

AVExportSession | Add UIView with GIFs and drawings on a video

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.
class func makeContentAnimation(beginTime: Double, values: [Any], frameRate: Double) -> CAKeyframeAnimation {
let animation = CAKeyframeAnimation(keyPath: "contents")
animation.values = values
animation.beginTime = beginTime.isZero ? AVCoreAnimationBeginTimeAtZero : beginTime
animation.duration = frameRate * Double(values.count)
animation.isRemovedOnCompletion = false
animation.repeatCount = .infinity
return animation
}

AVExportSession exporting video super slow

I am trying to simply increase the speed of my exporting of my merged video.
Here is the code: //from my extensive research online and on SO, I have pretty much come down to the preset PassThrough makes it super fast, however as I wrote in a comment in the code, my merging code does not seem to work with that preset for export :/
static func videoCompositionInstruction(_ track: AVCompositionTrack, asset: AVAsset)
-> AVMutableVideoCompositionLayerInstruction {
let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
let assetTrack = asset.tracks(withMediaType: .video)[0]
let transform = assetTrack.preferredTransform
let assetInfo = orientationFromTransform(transform)
var scaleToFitRatio = 1080 / assetTrack.naturalSize.width
if assetInfo.isPortrait {
scaleToFitRatio = 1080 / assetTrack.naturalSize.height
let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio)
var finalTransform = assetTrack.preferredTransform.concatenating(scaleFactor)
//was needed in my case (if video not taking entire screen and leaving some parts black - don't know when actually needed so you'll have to try and see when it's needed)
if assetInfo.orientation == .rightMirrored || assetInfo.orientation == .leftMirrored {
finalTransform = finalTransform.translatedBy(x: -transform.ty, y: 0)
}
instruction.setTransform(finalTransform, at: CMTime.zero)
} else {
let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio)
var concat = assetTrack.preferredTransform.concatenating(scaleFactor)
.concatenating(CGAffineTransform(translationX: 0, y: UIScreen.main.bounds.width / 2))
if assetInfo.orientation == .down {
let fixUpsideDown = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
let windowBounds = UIScreen.main.bounds
let yFix = assetTrack.naturalSize.height + windowBounds.height
let centerFix = CGAffineTransform(translationX: assetTrack.naturalSize.width, y: yFix)
concat = fixUpsideDown.concatenating(centerFix).concatenating(scaleFactor)
}
instruction.setTransform(concat, at: CMTime.zero)
}
return instruction
}
static func orientationFromTransform(_ transform: CGAffineTransform)
-> (orientation: UIImage.Orientation, isPortrait: Bool) {
var assetOrientation = UIImage.Orientation.up
var isPortrait = false
if transform.a == 0 && transform.b == 1.0 && transform.c == -1.0 && transform.d == 0 {
assetOrientation = .right
isPortrait = true
} else if transform.a == 0 && transform.b == 1.0 && transform.c == 1.0 && transform.d == 0 {
assetOrientation = .rightMirrored
isPortrait = true
} else if transform.a == 0 && transform.b == -1.0 && transform.c == 1.0 && transform.d == 0 {
assetOrientation = .left
isPortrait = true
} else if transform.a == 0 && transform.b == -1.0 && transform.c == -1.0 && transform.d == 0 {
assetOrientation = .leftMirrored
isPortrait = true
} else if transform.a == 1.0 && transform.b == 0 && transform.c == 0 && transform.d == 1.0 {
assetOrientation = .up
} else if transform.a == -1.0 && transform.b == 0 && transform.c == 0 && transform.d == -1.0 {
assetOrientation = .down
}
return (assetOrientation, isPortrait)
}
func mergeVideosTestSQ(arrayVideos:[AVAsset], completion:#escaping (URL?, Error?) -> ()) {
let mixComposition = AVMutableComposition()
var instructions: [AVMutableVideoCompositionLayerInstruction] = []
var insertTime = CMTime(seconds: 0, preferredTimescale: 1)
/// for each URL add the video and audio tracks and their duration to the composition
for sourceAsset in arrayVideos {
let frameRange = CMTimeRange(start: CMTime(seconds: 0, preferredTimescale: 1), duration: sourceAsset.duration)
guard
let nthVideoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)),
let nthAudioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)), //0 used to be kCMPersistentTrackID_Invalid
let assetVideoTrack = sourceAsset.tracks(withMediaType: .video).first
else {
print("didnt work")
return
}
var assetAudioTrack: AVAssetTrack?
assetAudioTrack = sourceAsset.tracks(withMediaType: .audio).first
print(assetAudioTrack, ",-- assetAudioTrack???", assetAudioTrack?.asset, "<-- hes", sourceAsset)
do {
try nthVideoTrack.insertTimeRange(frameRange, of: assetVideoTrack, at: insertTime)
try nthAudioTrack.insertTimeRange(frameRange, of: assetAudioTrack!, at: insertTime)
//instructions:
let nthInstruction = MainCamVC.videoCompositionInstruction(nthVideoTrack, asset: sourceAsset)
nthInstruction.setOpacity(0.0, at: CMTimeAdd(insertTime, sourceAsset.duration)) //sourceasset.duration
instructions.append(nthInstruction)
insertTime = insertTime + sourceAsset.duration //sourceAsset.duration
} catch {
DispatchQueue.main.async {
print("didnt wor2k")
}
}
}
let mainInstruction = AVMutableVideoCompositionInstruction()
mainInstruction.timeRange = CMTimeRange(start: CMTime(seconds: 0, preferredTimescale: 1), duration: insertTime)
mainInstruction.layerInstructions = instructions
let mainComposition = AVMutableVideoComposition()
mainComposition.instructions = [mainInstruction]
mainComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
mainComposition.renderSize = CGSize(width: 1080, height: 1920)
let outputFileURL = URL(fileURLWithPath: NSTemporaryDirectory() + "merge.mp4")
//below to clear the video form docuent folder for new vid...
let fileManager = FileManager()
try? fileManager.removeItem(at: outputFileURL)
/// try to start an export session and set the path and file type
if let exportSession = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) { //DOES NOT WORK WITH AVAssetExportPresetPassthrough
exportSession.outputFileType = .mov
exportSession.outputURL = outputFileURL
exportSession.videoComposition = mainComposition
exportSession.shouldOptimizeForNetworkUse = true
/// try to export the file and handle the status cases
exportSession.exportAsynchronously {
if let url = exportSession.outputURL{
completion(url, nil)
}
if let error = exportSession.error {
completion(nil, error)
}
}
}
}
Note I have instructions in order to preserve correct orientations.
Thanks for any help! I just need it to be faster, it roughly takes videoDuration/2 seconds to export in time to export...
After implementing your code into my project, it seems what is making your export slow, would be the way you handle the renderSize, as well as the resolution of the video. On top of that perhaps using a lower preset of quality may make it higher.
Specicially I would note this part:
let mainInstruction = AVMutableVideoCompositionInstruction()
mainInstruction.timeRange = CMTimeRange(start: CMTime(seconds: 0, preferredTimescale: 1), duration: insertTime)
mainInstruction.layerInstructions = instructions
let mainComposition = AVMutableVideoComposition()
mainComposition.instructions = [mainInstruction]
mainComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
mainComposition.renderSize = CGSize(width: 1080, height: 1920)
Changing the renderSize to = the videoCompositions.size (may be different name for your project) does the trick.
Then in the exporting place, I suggest changing this part:
/// try to start an export session and set the path and file type
if let exportSession = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) { //DOES NOT WORK WITH AVAssetExportPresetPassthrough
exportSession.outputFileType = .mov
exportSession.outputURL = outputFileURL
exportSession.videoComposition = mainComposition
exportSession.shouldOptimizeForNetworkUse = true
/// try to export the file and handle the status cases
exportSession.exportAsynchronously {
if let url = exportSession.outputURL{
completion(url, nil)
}
if let error = exportSession.error {
completion(nil, error)
}
}
}
As I said before, change it to presetQuality one lower or so. It will vastly improve the speed! Some transformations were also duplicated as well, something to look out for!

AVAssetExportSession AVFoundationErrorDomain Code -11800 The operation could not be completed, NSOSStatusErrorDomain Code=-12780 "(null) in Swift iOS

I am developing a Video based Application in Swift. Where I am exporting a Video clip with Watermark logo and Fade In Out effect. Here is my code:
func watermark(video videoAsset:AVAsset, videoModal:VideoModel, watermarkText text : String!, imageName name : String!, saveToLibrary flag : Bool, watermarkPosition position : PDWatermarkPosition, withMode mode: SpeedoVideoMode, completion : ((_ status : AVAssetExportSessionStatus?, _ session: AVAssetExportSession?, _ outputURL : URL?) -> ())?) {
var arrayLayerInstructions:[AVMutableVideoCompositionLayerInstruction] = []
let servicemodel = ServiceModel()
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).sync {
let mixComposition = AVMutableComposition()
let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
if videoAsset.tracks(withMediaType: AVMediaTypeVideo).count == 0
{
completion!(nil, nil, nil)
return
}
let clipVideoTrack = videoAsset.tracks(withMediaType: AVMediaTypeVideo)[0]
self.addAudioTrack(composition: mixComposition, videoAsset: videoAsset as! AVURLAsset, withMode: mode, videoModal:videoModal)
do {
try compositionVideoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), of: clipVideoTrack, at: kCMTimeZero)
}
catch {
print(error.localizedDescription)
}
let videoSize = clipVideoTrack.naturalSize //CGSize(width: 375, height: 300)
//to add Watermark
let parentLayer = CALayer()
let videoLayer = CALayer()
parentLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
videoLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
//videoLayer.backgroundColor = UIColor.red.cgColor
parentLayer.addSublayer(videoLayer)
if name != nil {
let watermarkImage = UIImage(named: name)
let imageLayer = CALayer()
//imageLayer.backgroundColor = UIColor.purple.cgColor
imageLayer.contents = watermarkImage?.cgImage
var xPosition : CGFloat = 0.0
var yPosition : CGFloat = 0.0
let imageSize : CGFloat = 150
switch (position) {
case .TopLeft:
xPosition = 0
yPosition = 0
break
case .TopRight:
xPosition = videoSize.width - imageSize - 100
yPosition = 80
break
case .BottomLeft:
xPosition = 0
yPosition = videoSize.height - imageSize
break
case .BottomRight, .Default:
xPosition = videoSize.width - imageSize
yPosition = videoSize.height - imageSize
break
}
imageLayer.frame = CGRect(x: xPosition, y: yPosition, width: imageSize, height: imageSize)
imageLayer.opacity = 0.75
parentLayer.addSublayer(imageLayer)
if text != nil {
let titleLayer = CATextLayer()
titleLayer.backgroundColor = UIColor.clear.cgColor
titleLayer.string = text
titleLayer.font = "Helvetica" as CFTypeRef
titleLayer.fontSize = 20
titleLayer.alignmentMode = kCAAlignmentRight
titleLayer.frame = CGRect(x: 0, y: yPosition - imageSize, width: videoSize.width - imageSize/2 - 4, height: 57)
titleLayer.foregroundColor = UIColor.lightGray.cgColor
parentLayer.addSublayer(titleLayer)
}
}
let videoComp = AVMutableVideoComposition()
videoComp.renderSize = videoSize
videoComp.frameDuration = CMTimeMake(1, 30)
videoComp.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: parentLayer)
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, mixComposition.duration)
_ = mixComposition.tracks(withMediaType: AVMediaTypeVideo)[0] as AVAssetTrack
let layerInstruction = self.videoCompositionInstructionForTrack(track: compositionVideoTrack, asset: videoAsset)
//Add Fade In Out effects
let startTime = CMTime(seconds: Double(0), preferredTimescale: 1000)
let endTime = CMTime(seconds: Double(1), preferredTimescale: 1000)
let timeRange = CMTimeRange(start: startTime, end: endTime)
layerInstruction.setOpacityRamp(fromStartOpacity: 0.1, toEndOpacity: 1.0, timeRange: timeRange)
let startTime1 = CMTime(seconds: videoAsset.duration.seconds-1, preferredTimescale: 1000)
let endTime1 = CMTime(seconds: videoAsset.duration.seconds, preferredTimescale: 1000)
let timeRange1 = CMTimeRange(start: startTime1, end: endTime1)
layerInstruction.setOpacityRamp(fromStartOpacity: 1.0, toEndOpacity: 0.1, timeRange: timeRange1)
arrayLayerInstructions.append(layerInstruction)
instruction.layerInstructions = arrayLayerInstructions
videoComp.instructions = [instruction]
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let url = URL(fileURLWithPath: documentDirectory).appendingPathComponent("\(videoModal.fileID).mov")
let filePath = url.path
let fileManager = FileManager.default
do {
if fileManager.fileExists(atPath: filePath) {
print("FILE AVAILABLE")
try fileManager.removeItem(atPath:filePath)
} else {
print("FILE NOT AVAILABLE")
}
} catch _ {
}
let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)
exporter?.outputURL = url
exporter?.outputFileType = AVFileTypeQuickTimeMovie
let timeRangetoTrim = CMTimeRange(start: CMTime(seconds: Double(videoModal.leftRangeValue), preferredTimescale: 1000),
end: CMTime(seconds: Double(videoModal.rightRangeValue), preferredTimescale: 1000))
exporter?.timeRange = timeRangetoTrim
exporter?.shouldOptimizeForNetworkUse = false
exporter?.videoComposition = videoComp
exporter?.exportAsynchronously() {
DispatchQueue.main.async {
if exporter?.status == AVAssetExportSessionStatus.completed {
let outputURL = exporter?.outputURL
if flag {
if UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(outputURL!.path) {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL!)
}) { saved, error in
if saved {
completion!(AVAssetExportSessionStatus.completed, exporter, outputURL)
}
}
}
} else {
completion!(AVAssetExportSessionStatus.completed, exporter, outputURL)
}
} else {
// Error
completion!(exporter?.status, exporter, nil)// Getting error here
}
}
}
}
}
func addAudioTrack(composition: AVMutableComposition, videoAsset: AVURLAsset, withMode mode: SpeedoVideoMode, videoModal:VideoFileModel) {
let compositionAudioTrack:AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID())
let audioTracks = videoAsset.tracks(withMediaType: AVMediaTypeAudio)
for audioTrack in audioTracks {
try! compositionAudioTrack.insertTimeRange(audioTrack.timeRange, of: audioTrack, at: kCMTimeZero)
}
}
func videoCompositionInstructionForTrack(track: AVCompositionTrack, asset: AVAsset) -> AVMutableVideoCompositionLayerInstruction {
let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
let assetTrack = asset.tracks(withMediaType: AVMediaTypeVideo)[0]
let transform = assetTrack.preferredTransform
let assetInfo = orientationFromTransform(transform: transform)
var scaleToFitRatio = UIScreen.main.bounds.width / 375
if assetInfo.isPortrait {
scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.height
let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio)
instruction.setTransform(assetTrack.preferredTransform.concatenating(scaleFactor),
at: kCMTimeZero)
} else {
let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio)
var concat = assetTrack.preferredTransform.concatenating(scaleFactor).concatenating(CGAffineTransform(translationX: 0, y: 0))
if assetInfo.orientation == .down {
let fixUpsideDown = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
let windowBounds = UIScreen.main.bounds
let yFix = 375 + windowBounds.height
let centerFix = CGAffineTransform(translationX: assetTrack.naturalSize.width, y: CGFloat(yFix))
concat = fixUpsideDown.concatenating(centerFix).concatenating(scaleFactor)
}
instruction.setTransform(concat, at: kCMTimeZero)
}
return instruction
}
private func orientationFromTransform(transform: CGAffineTransform) -> (orientation: UIImageOrientation, isPortrait: Bool) {
var assetOrientation = UIImageOrientation.up
var isPortrait = false
if transform.a == 0 && transform.b == 1.0 && transform.c == -1.0 && transform.d == 0 {
assetOrientation = .right
isPortrait = true
} else if transform.a == 0 && transform.b == -1.0 && transform.c == 1.0 && transform.d == 0 {
assetOrientation = .left
isPortrait = true
} else if transform.a == 1.0 && transform.b == 0 && transform.c == 0 && transform.d == 1.0 {
assetOrientation = .up
} else if transform.a == -1.0 && transform.b == 0 && transform.c == 0 && transform.d == -1.0 {
assetOrientation = .down
}
return (assetOrientation, isPortrait)
}
My code is working fine for some of the videos and sometimes it's not working for some videos too. I am getting below error due to AVAssetExportSessionStatus failed :
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could
not be completed" UserInfo={NSLocalizedFailureReason=An unknown error
occurred (-12780), NSLocalizedDescription=The operation could not be
completed, NSUnderlyingError=0x28262c240 {Error
Domain=NSOSStatusErrorDomain Code=-12780 "(null)"}}
Can anyone help me on this? Thank you in advance.
This method func videoCompositionInstructionForTrack(track: AVCompositionTrack, asset: AVAsset) -> AVMutableVideoCompositionLayerInstruction { is wrong because you need to provide and AVAssetTrack which has the actual videos.
But instead of that, you are passing AVCompositionTrack which is still need to be composed, so replace your method with this func videoCompositionInstructionForTrack(track: AVAssetTrack, asset: AVAsset) -> AVMutableVideoCompositionLayerInstruction {.
Now when you are calling the actual method, you need to pass clipVideoTrack, i.e., let layerInstruction = self.videoCompositionInstructionForTrack(track: clipVideoTrack, asset: videoAsset).
let me know, if you are still facing the error!

Swift: Square video composition

I am following the below code for square video composition
func completeWithVideoAtURL(input: NSURL) {
let asset = AVAsset(url: input as URL)
let output = NSURL(fileURLWithPath: NSHomeDirectory() + "/Documents/Video.mp4")
let session = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetMediumQuality)!
session.videoComposition = self.squareVideoCompositionForAsset(asset: asset)
session.outputURL = output as URL
session.outputFileType = AVFileTypeMPEG4
session.shouldOptimizeForNetworkUse = true
session.exportAsynchronously(completionHandler: { () -> Void in
DispatchQueue.main.async(execute: { () -> Void in
// do something with the output
print("\(output)")
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: output as URL)
}) { saved, error in
if saved {
print("saved to gallery")
}
}
})
})
}
func squareVideoCompositionForAsset(asset: AVAsset) -> AVVideoComposition {
let track = asset.tracks(withMediaType: AVMediaTypeVideo)[0]
let length = max(track.naturalSize.width, track.naturalSize.height)
var transform = track.preferredTransform
let size = track.naturalSize
let scale: CGFloat = (transform.a == -1 && transform.b == 0 && transform.c == 0 && transform.d == -1) ? -1 : 1 // check for inversion
transform = transform.translatedBy(x: scale * -(size.width - length) / 2, y: scale * -(size.height - length) / 2)
let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
transformer.setTransform(transform, at: kCMTimeZero)
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRange(start: kCMTimeZero, duration: kCMTimePositiveInfinity)
instruction.layerInstructions = [transformer]
let composition = AVMutableVideoComposition()
composition.frameDuration = CMTime(value: 1, timescale: 30)
composition.renderSize = CGSize(width: length, height: length)
composition.instructions = [instruction]
return composition
}
From the squareVideoCompositionForAsset() function I take the max value for length between track.naturalSize.width & track.naturalSize.height cause I don't want to crop any partial part of the video. If I take min value, for portrait video it cropped the upper & lower portion of the video and for landscape video it cropped some left & right portion of the video.
For landscape video, output is okay
but for portrait video, output is like following image
the video gets left sided. Is it possible to center the video? Any assistance would be great and sorry for long explanation.
instead of this line
let scale: CGFloat = (transform.a == -1 && transform.b == 0 &&
transform.c == 0 && transform.d == -1) ? -1 : 1
I just used this
var scale = CGFloat()
if (transform.a == 0 && transform.b == 1 && transform.c == -1 && transform.d == 0) {
scale = -1
}
else if (transform.a == 0 && transform.b == -1 && transform.c == 1 && transform.d == 0) {
scale = -1
}
else if (transform.a == 1 && transform.b == 0 && transform.c == 0 && transform.d == 1) {
scale = 1
}
else if (transform.a == -1 && transform.b == 0 && transform.c == 0 && transform.d == -1) {
scale = 1
}
and it worked like a charm
Swift 4.2
func suqareCropVideo(videoURL: URL, withSide sideLength: CGFloat, completion: #escaping (_ resultURL: URL?, _ error: Error?) -> ()) {
let asset = AVAsset(url: videoURL)
if let assetVideoTrack = asset.tracks(withMediaType: .video).last {
let originalSize = assetVideoTrack.naturalSize
var scale: CGFloat
if originalSize.width < originalSize.height {
scale = sideLength / originalSize.width
} else {
scale = sideLength / originalSize.height
}
let scaledSize = CGSize(width: originalSize.width * scale, height: originalSize.height * scale)
let topLeft = CGPoint(x: sideLength * 0.5 - scaledSize.width * 0.5, y: sideLength * 0.5 - scaledSize.height * 0.5)
let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: assetVideoTrack)
var orientationTransform = assetVideoTrack.preferredTransform
if (orientationTransform.tx == originalSize.width || orientationTransform.tx == originalSize.height) {
orientationTransform.tx = sideLength
}
if (orientationTransform.ty == originalSize.width || orientationTransform.ty == originalSize.height) {
orientationTransform.ty = sideLength
}
let transform = CGAffineTransform(scaleX: scale, y: scale).concatenating(CGAffineTransform(translationX: topLeft.x, y: topLeft.y)).concatenating(orientationTransform)
layerInstruction.setTransform(transform, at: .zero)
let instruction = AVMutableVideoCompositionInstruction()
instruction.layerInstructions = [layerInstruction]
instruction.timeRange = assetVideoTrack.timeRange
let videoComposition = AVMutableVideoComposition()
videoComposition.renderSize = CGSize(width: sideLength, height: sideLength)
videoComposition.renderScale = 1.0
videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
videoComposition.instructions = [instruction]
if let export = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) {
export.videoComposition = videoComposition
export.outputURL = NSURL.fileURL(withPath: "\(NSTemporaryDirectory())\(NSUUID().uuidString).mp4")
export.outputFileType = AVFileType.mp4
export.shouldOptimizeForNetworkUse = true
export.exportAsynchronously {
DispatchQueue.main.async {
if export.status == .completed {
completion(export.outputURL, nil)
} else {
completion(nil, export.error)
}
}
}
} else {
completion(nil, nil)
}
}
}

Resources