読者です 読者をやめる 読者になる 読者になる

Swiftで動画編集

Swift iOS

swiftで動画(AVAsset)を逆再生や、早送りにして書き出すのをやってみた。

方法としては、

  1. AVAsset[CGImage]に変換する
  2. それを並び替えたりして、CAKeyframeAnimationを仕込んだCALayerに変換する
  3. addSublayerでプレビューする
  4. MOVとして書き出す

まずは、動画を画像配列に変換

extension AVAsset {

    func frames() -> [CGImage] {
        var images = [CGImage]()
        guard let track = tracksWithMediaType(AVMediaTypeVideo).first else { return [CGImage]() }

        var reader: AVAssetReader?
        do {
            reader = try AVAssetReader(asset: self)
        } catch let error as NSError {
            print(error)
        }

        let options = [
            "\(kCVPixelBufferPixelFormatTypeKey)": Int(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange)
        ]
        let output = AVAssetReaderTrackOutput(track: track, outputSettings: options)
        reader?.addOutput(output)
        reader?.startReading()

        while let sample = output.copyNextSampleBuffer() {
            guard let pixelBuffer = CMSampleBufferGetImageBuffer(sample) else { return [CGImage]() }
            let ciImage = CIImage(CVPixelBuffer: pixelBuffer)
            let context = CIContext(options: [kCIContextUseSoftwareRenderer: true])
            let cgImage = context.createCGImage(ciImage, fromRect: ciImage.extent)
            images.append(cgImage)
        }

        return images
    }
}

次にその画像配列を並び替えたりして、アニメーションレイヤーを作成

任意の画像とdurationのセットで渡します

struct KeyFrame {
    internal let image: CGImage
    internal let duration: Double
}
extension CALayer {

    class func animation(keyFrames: [KeyFrame]) -> CALayer {
        let total = keyFrames.map({ $0.duration }).reduce(0, combine: +)
        let count = keyFrames.count
        var times = [Double]()
        var currentTime = 0.0
        for i in 0..<count {
            times.append(currentTime/total)
            currentTime += keyFrames[i].duration
        }
        let layer = CALayer()
        let animation = CAKeyframeAnimation(keyPath: "contents")
        animation.keyTimes = times
        animation.values = keyFrames.map { $0.image }
        animation.duration = total
        animation.repeatCount = .infinity
        animation.beginTime = AVCoreAnimationBeginTimeAtZero
        animation.removedOnCompletion = false
        animation.calculationMode = kCAAnimationDiscrete
        layer.addAnimation(animation, forKey: "contents")
        return layer
    }
}

これであとは、

let animationLayer = layer.animation(keyFrames)
            layer.frame = view.frame
            previewView.layer.addSublayer(layer)

みたいな感じでプレビユーできた!

最後書き出しはAVAssetExportSessionで簡単!