我已经设法完全解决了我的问题,但我仍然想要一个更好的解释,而不是我猜的是如果我不正确它是不起作用的原因
我一直试图在视频上制作精灵表,但每次导出视频时,最终结果都是我开始的示例视频.
这是我的代码:
首先我的自定义CALayer来处理我自己的精灵表
class SpriteLayer: CALayer { var frameIndex: Int overrIDe init() { // Using 0 as a default state self.frameIndex = 0 super.init() } required init?(coder aDecoder: NSCoder) { self.frameIndex = 0 super.init(coder: aDecoder) } overrIDe func display() { let currentFrameIndex = self.frameIndex if currentFrameIndex == 0 { return } let frameSize = self.contentsRect.size self.contentsRect = CGRect(x: 0,y: CGfloat(currentFrameIndex - 1) * frameSize.height,wIDth: frameSize.wIDth,height: frameSize.height) } overrIDe func action(forKey event: String) -> CAAction? { if event == "contentsRect" { return nil } return super.action(forKey: event) } overrIDe class func needsdisplay(forKey key: String) -> Bool { return key == "frameIndex" }}
Gif是一个没有花哨的基本课程,工作得很好. gif.Strip是表示gif的垂直精灵表的UIImage.
现在出现了应该导出新视频的方法(它是用于导出的更大类的一部分.
func convertAndExport(to url :URL,completion: @escaPing () -> VoID ) { // Get Initial info and make sure our destination is available self.outputURL = url let stripCgImage = self.gif.strip!.cgImage! // This is used to time how long the export took let start = dispatchTime.Now() do { try fileManager.default.removeItem(at: outputURL) } catch { print("Remove Error: \(error.localizedDescription)") print(error) } // Find and load "sample.mp4" as a AVAsset let vIDeoPath = Bundle.main.path(forResource: "sample",ofType: "mp4")! let vIDeoUrl = URL(fileURLWithPath: vIDeoPath) let vIDeoAsset = AVAsset(url: vIDeoUrl) // Start a new mutable Composition with the same base vIDeo track let mixComposition = AVMutableComposition() let compositionVIDeoTrack = mixComposition.addMutableTrack(withMediaType: .vIDeo,preferredTrackID: kCMPersistentTrackID_InvalID)! let clipVIDeoTrack = vIDeoAsset.tracks(withMediaType: .vIDeo).first! do { try compositionVIDeoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero,vIDeoAsset.duration),of: clipVIDeoTrack,at: kCMTimeZero) } catch { print("Insert Error: \(error.localizedDescription)") print(error) return } compositionVIDeoTrack.preferredtransform = clipVIDeoTrack.preferredtransform // Quick access to the vIDeo size let vIDeoSize = clipVIDeoTrack.naturalSize // Setup CALayer and it's animation let aLayer = SpriteLayer() aLayer.contents = stripCgImage aLayer.frame = CGRect(x: 0,y: 0,wIDth: vIDeoSize.wIDth,height: vIDeoSize.height) aLayer.opacity = 1.0 aLayer.masksToBounds = true aLayer.bounds = CGRect(x: 0,height: vIDeoSize.height) aLayer.contentsRect = CGRect(x: 0,wIDth: 1,height: 1.0 / 3.0) let spriteAnimation = CABasicAnimation(keyPath: "frameIndex") spriteAnimation.fromValue = 1 spriteAnimation.tovalue = 4 spriteAnimation.duration = 2.25 spriteAnimation.repeatCount = .infinity spriteAnimation.autoreverses = false spriteAnimation.beginTime = AVCoreAnimationBeginTimeatZero aLayer.add(spriteAnimation,forKey: nil) // Setup Layers for AVVIDeoCompositionCoreAnimationTool let parentLayer = CALayer() let vIDeolayer = CALayer() parentLayer.frame = CGRect(x: 0,height: vIDeoSize.height) vIDeolayer.frame = CGRect(x: 0,height: vIDeoSize.height) parentLayer.addSublayer(vIDeolayer) parentLayer.addSublayer(aLayer) // Create the mutable vIDeo composition let vIDeoComp = AVMutableVIDeoComposition() vIDeoComp.renderSize = vIDeoSize vIDeoComp.frameDuration = CMTimeMake(1,30) vIDeoComp.animationTool = AVVIDeoCompositionCoreAnimationTool(postProcessingAsVIDeolayer: vIDeolayer,in: parentLayer) // Set the vIDeo composition to apply to the composition's vIDeo track let instruction = AVMutableVIDeoCompositionInstruction() instruction.timeRange = CMTimeRangeMake(kCMTimeZero,mixComposition.duration) let vIDeoTrack = mixComposition.tracks(withMediaType: .vIDeo).first! let layerInstruction = AVMutableVIDeoCompositionLayerInstruction(assetTrack: vIDeoTrack) instruction.layerInstructions = [layerInstruction] vIDeoComp.instructions = [instruction] // Initialize export session let assetExport = AVAssetExportSession(asset: mixComposition,presetname: AVAssetExportPresetPassthrough)! assetExport.vIDeoComposition = vIDeoComp assetExport.outputfileType = AVfileType.mp4 assetExport.outputURL = self.outputURL assetExport.shouldOptimizeforNetworkUse = true // Export assetExport.exportAsynchronously { let status = assetExport.status switch status { case .Failed: print("Export Failed") print("Export Error: \(assetExport.error!.localizedDescription)") print(assetExport.error!) case .unkNown: print("Export UnkNown") case .exporting: print("Export Exporting") case .waiting: print("Export Waiting") case .cancelled: print("Export Cancelled") case .completed: let end = dispatchTime.Now() let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds let timeInterval = Double(nanoTime) / 1_000_000_000 // Function is Now over,we can print how long it took print("Time to generate vIDeo: \(timeInterval) seconds") completion() } }}
编辑:
我将我的代码基于以下链接
> SpriteLayer and how to use it
> CABasicAnimation on a video
> Using AVVideoCompositionCoreAnimationTool and AVAssetExportSession to save the new video
更新1:
我已经尝试删除我的代码中的CABasicAnimation部分并使用我的CALayer,但无济于事.我甚至无法让图像显示出来.
为了测试一下,我尝试使用Xcode Playground中的contentsRect上的CAKeyframeAnimation动画这个精灵表,它运行正常,所以我认为问题不在于CABasicAnimation,甚至可能与CALayer本身无关.我真的可以在这方面使用一些帮助,因为我不明白为什么我甚至无法在导出的示例视频上显示图像.
更新2:
为了回应matt的评论我已经尝试忘记了一点精灵表并将其改成CATextLayer但仍然没有在我的视频上看到任何东西(它有深色图像,所以白色文字应该是完全可见的)
let aLayer = CATextLayer()aLayer.string = "This is a test"aLayer.FontSize = vIDeoSize.height / 6aLayer.alignmentMode = kCAAlignmentCenteraLayer.foregroundcolor = UIcolor.white.cgcoloraLayer.bounds = CGRect(x: 0,height: vIDeoSize.height / 6)
更新3:
根据Matt的要求,我尝试将parentLayer.addSublayer(aLayer)更改为vIDeolayer.addSublayer(aLayer),但仍然没有改变,但我想尽可能多,因为AVVIDeoCompositionCoreAnimationTool的documentation如下
convenIEnce init(postProcessingAsVIDeolayer vIDeolayer: CALayer,in animationLayer: CALayer)
意思是我的parentLayer是它的animationLayer,可能意味着任何动画应该在这一层完成.
更新4:
我开始疯狂了,我现在已经放弃了显示文字或动画图像的想法,我想以任何可能的方式影响我的视频,所以我将aLayer更改为:
let aLayer = CALayer()aLayer.frame = CGRect(x: 0,height: vIDeoSize.height)aLayer.backgroundcolor = UIcolor.white.cgcolor
好吧,这绝对没有,我仍然在我的outputUrl上获取我的示例视频(如果你想“玩”,我开始在 *** 场上用以下代码测试它)
import PlaygroundSupportimport UIKitimport Foundationimport AVFoundationfunc convertAndExport(to url :URL,completion: @escaPing () -> VoID ) { let start = dispatchTime.Now() do { try fileManager.default.removeItem(at: url) } catch { print("Remove Error: \(error.localizedDescription)") print(error) } let vIDeoPath = Bundle.main.path(forResource: "sample",ofType: "mp4")! let vIDeoUrl = URL(fileURLWithPath: vIDeoPath) let vIDeoAsset = AVURLAsset(url: vIDeoUrl) let mixComposition = AVMutableComposition() let compositionVIDeoTrack = mixComposition.addMutableTrack(withMediaType: .vIDeo,preferredTrackID: kCMPersistentTrackID_InvalID)! let clipVIDeoTrack = vIDeoAsset.tracks(withMediaType: .vIDeo).first! do { try compositionVIDeoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero,at: kCMTimeZero) } catch { print("Insert Error: \(error.localizedDescription)") print(error) return } compositionVIDeoTrack.preferredtransform = clipVIDeoTrack.preferredtransform let vIDeoSize = clipVIDeoTrack.naturalSize print("VIDeo Size Detected: \(vIDeoSize.wIDth) x \(vIDeoSize.height)") let aLayer = CALayer() aLayer.frame = CGRect(x: 0,height: vIDeoSize.height) aLayer.backgroundcolor = UIcolor.white.cgcolor let parentLayer = CALayer() let vIDeolayer = CALayer() parentLayer.frame = CGRect(x: 0,height: vIDeoSize.height) vIDeolayer.frame = CGRect(x: 0,height: vIDeoSize.height) parentLayer.addSublayer(vIDeolayer) parentLayer.addSublayer(aLayer) aLayer.setNeedsdisplay() 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) let vIDeoTrack = mixComposition.tracks(withMediaType: .vIDeo).first! let layerInstruction = AVMutableVIDeoCompositionLayerInstruction(assetTrack: vIDeoTrack) instruction.layerInstructions = [layerInstruction] vIDeoComp.instructions = [instruction] let assetExport = AVAssetExportSession(asset: mixComposition,presetname: AVAssetExportPresetPassthrough)! assetExport.vIDeoComposition = vIDeoComp assetExport.outputfileType = AVfileType.mp4 assetExport.outputURL = url assetExport.shouldOptimizeforNetworkUse = true assetExport.exportAsynchronously { let status = assetExport.status switch status { case .Failed: print("Export Failed") print("Export Error: \(assetExport.error!.localizedDescription)") print(assetExport.error!) case .unkNown: print("Export UnkNown") case .exporting: print("Export Exporting") case .waiting: print("Export Waiting") case .cancelled: print("Export Cancelled") case .completed: let end = dispatchTime.Now() let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds let timeInterval = Double(nanoTime) / 1_000_000_000 print("Time to generate vIDeo: \(timeInterval) seconds") completion() } }}let outputUrl = fileManager.default.temporaryDirectory.appendingPathComponent("test.mp4")convertAndExport(to: outputUrl) { print(outputUrl)}
请有人帮我理解我做错了什么……
更新5:
我正在运行iPad Air 2以外的所有游乐场测试(所以没有模拟器),因为我使用相机拍照,然后将它们拼接成精灵表,然后我计划通过电子邮件发送动画.我开始做Playground测试,因为来自iPad的每个测试都要求我经历整个应用程序周期(倒计时,照片,表单,电子邮件发送/接收)
首先,即使他删除了他的评论,感谢Matt链接到一个工作示例,帮助我将我的代码错误拼凑在一起.
>首先
let assetExport = AVAssetExportSession(asset: mixComposition,presetname: AVAssetExportPresetPassthrough)!
我需要使用AVAssetExportPresetHighestQuality而不是AVAssetExportPresetPassthrough.我的猜测是,直通预设意味着你不进行任何重新编码,因此将其设置为最高(因为我导出的视频超过400×400而不是中等)使得我可以实际重新编码我的视频.我猜这是阻止导出的视频包含我尝试的任何CALayer(甚至用白色覆盖视频)的原因.
>其次(不确定这是否真的影响但我会稍后再试)
parentLayer.addSublayer(aLayer)
我将其替换为:
vIDeolayer.addSublayer(aLayer)
不确定这是否真的很重要,但我的理解是,这实际上是AVVIDeoCompositionCoreAnimationTool的动画层,而parentLayer只是一个容器,并不意味着包含更多,但我可能错了.
>我做了第三次改变
let spriteAnimation = CABasicAnimation(keyPath: "frameIndex")spriteAnimation.fromValue = 1spriteAnimation.tovalue = 4spriteAnimation.duration = 2.25spriteAnimation.repeatCount = .infinityspriteAnimation.autoreverses = falsespriteAnimation.beginTime = AVCoreAnimationBeginTimeatZeroaLayer.add(spriteAnimation,forKey: nil)
我改成了这个:
let animation = CAKeyframeAnimation(keyPath: #keyPath(CALayer.contentsRect))animation.duration = 2.25animation.calculationMode = kCAAnimationdiscreteanimation.repeatCount = .infinityanimation.values = [ CGRect(x: 0,height: 1/3.0),CGRect(x: 0,y: 1/3.0,y: 2/3.0,height: 1/3.0) ] as [CGRect]animation.beginTime = AVCoreAnimationBeginTimeatZeroanimation.fillMode = kCAFillModeBackwardsanimation.isRemovedOnCompletion = falseaLayer.add(animation,forKey: nil)
这个改变主要是删除我的精灵表的自定义动画(因为它总是一样的,我首先想要一个工作的例子,然后我将它概括,并可能将它添加到我的私人UI Pod).但最重要的是animation.isRemovedOnCompletion = false我注意到删除它会使得动画根本无法在导出的视频上播放.因此,对于CABasicAnimation导出后未对视频进行动画处理的任何人,请尝试查看您的动画上是否正确设置了isRemovedOnCompletion.
我认为这几乎都是我所做的改变.
虽然我在技术上回答了我的问题,但我的理解仍然是了解AVVIDeoCompositionCoreAnimationTool和AVAssetExport是如何工作的,以及为什么我必须做的更改,如果有人有兴趣解释,我最终会让它工作.
再次感谢Matt,你通过向我展示你是如何做到的来帮助我的.
总结以上是内存溢出为你收集整理的swift – 无法使用AVVideoCompositionCoreAnimationTool在视频中显示动画CALayer全部内容,希望文章能够帮你解决swift – 无法使用AVVideoCompositionCoreAnimationTool在视频中显示动画CALayer所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)