static func create(videoTrack: AVAssetTrack, composition: AVMutableComposition, videoComposition: AVMutableVideoComposition) {
let fillRect = CGRect(x: 0, y: 0, width: videoTrack.naturalSize.width, height: videoTrack.naturalSize.height)
let parentlayer = CALayer()
parentlayer.frame = fillRect
parentlayer.zPosition = 100
let videolayer = CALayer()
videolayer.frame = fillRect
parentlayer.addSublayer(videolayer)
videolayer.isHidden = false
parentlayer.isHidden = false
for textSection in [""] {
let imglayer = CALayer()
imglayer.contents = UIImage(systemName: "circle")!.cgImage// textSection.getImage().cgImage
imglayer.frame = fillRect
imglayer.opacity = 1
parentlayer.addSublayer(imglayer)
}
videoComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videolayer, in: parentlayer)
// instruction for watermark
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRangeMake(start: .zero, duration: composition.duration)
let videotrack = composition.tracks(withMediaType: .video)[0] as AVAssetTrack
let layerinstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videotrack)
layerinstruction.setTransform(videoTrack.preferredTransform.concatenating(CGAffineTransform(scaleX: 1, y: 1)), at: .zero)
instruction.layerInstructions = [layerinstruction]
videoComposition.instructions.append(instruction)
static func exportMovie(assets: [AVAsset], fileType: AVFileType, completion: (() -> Void)?) {
let destinationURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("square.mov")
let mixComposition = AVMutableComposition()
// video composition に、先程の instruction を設定する。また、レンダリングの動画サイズを正方形に設定する
let croppedVideoComposition = AVMutableVideoComposition()
croppedVideoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
for avAsset in assets {
let videoTrack = avAsset.tracks(withMediaType: AVMediaType.video)[0]
let audioTrack = avAsset.tracks(withMediaType: AVMediaType.audio)[0]
let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)!
let compositionAudioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)!
try! compositionVideoTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: avAsset.duration), of: videoTrack, at: .zero)
try! compositionAudioTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: avAsset.duration), of: audioTrack, at: .zero)
compositionVideoTrack.preferredTransform = videoTrack.preferredTransform
let size = CGSize(width: videoTrack.naturalSize.height, height: videoTrack.naturalSize.width)
let squareEdgeLength = size.height
let squareEdgeWidth = size.width
let instruction = cropVideo(size: size, videoTrack: videoTrack, compositionVideoTrack: compositionVideoTrack, avAsset: avAsset)
create(videoTrack: videoTrack, composition: mixComposition, videoComposition: croppedVideoComposition)
croppedVideoComposition.instructions.append(instruction)
croppedVideoComposition.renderSize = CGSize(width: squareEdgeWidth, height: squareEdgeLength)
}
// エクスポートの設定。先程の video compsition をエクスポートに使うよう設定する。
let assetExport = AVAssetExportSession.init(asset: mixComposition, presetName: AVAssetExportPresetMediumQuality)
assetExport?.outputFileType = fileType
assetExport?.outputURL = destinationURL
assetExport?.videoComposition = croppedVideoComposition
if FileManager.default.fileExists(atPath: (assetExport?.outputURL?.path)!) {
try! FileManager.default.removeItem(atPath: (assetExport?.outputURL?.path)!)
}
assetExport?.exportAsynchronously(completionHandler: {
print("assetExport",assetExport?.error)
VideoComposeHelper.didCompone(url: destinationURL)
})
}
static func cropVideo(size: CGSize, videoTrack: AVAssetTrack, compositionVideoTrack: AVAssetTrack, avAsset: AVAsset) -> AVMutableVideoCompositionInstruction {
let croppingRect = CGRect(x: (videoTrack.naturalSize.width - size.height) / 2,
y: (videoTrack.naturalSize.height - size.width) / 2,
width: size.height, height: size.width)
let transform = videoTrack.preferredTransform.translatedBy(x: -croppingRect.minX, y: -croppingRect.minY)
// layer instruction を正方形に
let layerInstruction = AVMutableVideoCompositionLayerInstruction.init(assetTrack: compositionVideoTrack)
layerInstruction.setCropRectangle(croppingRect, at: .zero)
layerInstruction.setTransform(transform, at: .zero)
// instruction に、先程の layer instruction を設定する
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRangeMake(start: .zero, duration: avAsset.duration)
instruction.layerInstructions = [layerInstruction]
return instruction
}
static func didCompone(url: URL) {
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)
}) { completed, error in
if let error = error {
print("Failed to save video in photos", error)
return
}
DispatchQueue.main.async {
//self.view.isUserInteractionEnabled = true
if completed {
print("Video has been saved to your photos.")
} else {
print("Video saving has NOT been completed")
}
}
}
}
}
Above code fails with error below:
assetExport Optional(Error Domain=AVFoundationErrorDomain Code=-11841 "操作が停止しました" UserInfo={NSLocalizedFailureReason=ビデオを作成できませんでした。, NSLocalizedDescription=操作が停止しました, NSUnderlyingError😮 x2823c8000 {Error Domain=NSOSStatusErrorDomain Code=-17390 "(null)"}})
Failed to save video in photos Error Domain😛 HPhotosErrorDomain Code=-1 "(null)"
What could be wrong??