Как подходить к добавлению наложений изображений / субтитров поверх видео (AVPlayer) Swift 4.0 - PullRequest
0 голосов
/ 22 января 2019

Я заинтересован в разработке приложения, которое может накладывать стикеры / тексты поверх видео в течение определенного периода времени.В идеале, пользовательский интерфейс должен быть достаточно простым, чтобы пользователь мог легко перетаскивать стикер и легко изменять длительность.

Теперь я только начал изучать AVFoundation / AVPlayer.Как лучше всего подойти к этому?Вот мой прототип кода с использованием AVMutableVideoComposition:

Мой первоначальный план - использовать AVMutableVideoComposition.Но некоторые из моих опасений заключаются в следующем: будет ли рендеринг достаточно быстрым, чтобы каждый раз, когда пользователь перетаскивал стикер, он мог легко вносить изменения (положение / длина)?

// Add sticker by create a CALayer instance and add it afterward
func addSticker(_ sticker: Int, at position: Int) {
    addStickerView?.removeFromSuperview()

    // Create an AVMutableComposition for editing
    let mutableComposition = getVideoComposition()

    // Create a CALayer instance and configurate it
    let stickerLayer = CALayer()
    stickerLayer.contents = UIImage(named: "sticker\(sticker)")?.cgImage
    stickerLayer.contentsGravity = CALayerContentsGravity.resizeAspect
    stickerLayer.opacity = 0

    // fade in animation
    let animation = CABasicAnimation(keyPath: "opacity")
    animation.delegate = self
    animation.fillMode = .removed

    animation.isRemovedOnCompletion = true
    animation.duration = 1
    animation.fromValue = CGFloat(0.0)
    animation.toValue = CGFloat(1.0)
    animation.beginTime = AVCoreAnimationBeginTimeAtZero
    stickerLayer.add(animation, forKey: "opacity")

    let videoSize = videoAsset.tracks(withMediaType: AVMediaType.video)[0].naturalSize
    let videoWidth = videoSize.width
    let videoHeight = videoSize.height
    let stickerWidth = videoWidth/6
    let stickerX = videoWidth * CGFloat(5 * (position % 3)) / 12
    let stickerY = videoHeight * CGFloat(position / 3) / 3
    stickerLayer.frame = CGRect(x: stickerX, y: stickerY, width: stickerWidth, height: stickerWidth)

    // Further export the video with the layer
    exportWithAddedLayer(mutableComposition, with: stickerLayer, doneEditing: false)
}

}

func exportWithAddedLayer(_ mutableComposition: AVMutableComposition, with addedLayer: CALayer, doneEditing: Bool) {

    self.view.addSubview(processingLabel)
    processingLabel.frame = self.toolsTableView.frame

    let videoTrack: AVAssetTrack = mutableComposition.tracks(withMediaType: AVMediaType.video)[0]
    let videoSize = videoTrack.naturalSize


    let videoLayer = CALayer()
    videoLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)

    let containerLayer = CALayer()
    containerLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
    containerLayer.addSublayer(videoLayer)
    containerLayer.addSublayer(addedLayer)

    let layerComposition = AVMutableVideoComposition()
    layerComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
    layerComposition.renderSize = videoSize
    layerComposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: containerLayer)

    let instruction = AVMutableVideoCompositionInstruction()
    instruction.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: mutableComposition.duration)
    let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
    instruction.layerInstructions = [layerInstruction]
    layerComposition.instructions = [instruction]

    let exportUrl = generateExportUrl()
    // Set up exporter
    guard let exporter = AVAssetExportSession(asset: mutableComposition, presetName: AVAssetExportPresetHighestQuality) else { return }
    exporter.videoComposition = layerComposition
    exporter.outputURL = exportUrl as URL
    exporter.outputFileType = AVFileType.mov
    exporter.exportAsynchronously() {
        DispatchQueue.main.async { 
            self.exportDidComplete(exportURL: exporter.outputURL!, doneEditing: doneEditing)
        }
    }
}
...