IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> iOS -- 图片数组 生成视频 一键成片 -> 正文阅读

[移动开发]iOS -- 图片数组 生成视频 一键成片

** 可以直接拷贝到工具类中使用**

import UIKit
import AVFoundation

enum AnimationType {
    case fromRightToLeft
    case fromLowerToUpper
    case zoomOut
    case zoomIn
    case rotate
    case fadeInFadeOut
}

class ImageAnimationTool {
        
    static let shared = ImageAnimationTool()
    
    private let outputPath: URL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("imagesComposition.mp4")
    private let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("baseVideo.mp4")
    private var eachImageDuration: CGFloat = 3//每张图片默认三秒
    private var animationDuration: CGFloat = 0.5
    private var alphaAnimationDuration: CGFloat = 0.3
    private var videoWidth: Int = 720
    private var videoHeight: Int = 1280
    private var FPS: Int32 = 30
    private var videoResource: String?
    private var videoDuration: CGFloat = 0
    private var zoomRate: CGFloat = 1.2
    private var animationType: AnimationType = .fromRightToLeft
    private var mouldArray: [AnimationType] = [.fadeInFadeOut, .fadeInFadeOut, .fadeInFadeOut, .fadeInFadeOut, .fadeInFadeOut, .fadeInFadeOut,.fadeInFadeOut,.fadeInFadeOut,.fadeInFadeOut]
    private lazy var videoSetting = [AVVideoCodecKey: AVVideoCodecType.h264, AVVideoWidthKey: NSNumber(value: videoWidth), AVVideoHeightKey: NSNumber(value: videoHeight)] as [String : Any]

    func transacformImages(images: [UIImage?], audioUrl: URL? = nil, animationType: AnimationType = .fromRightToLeft, animationDuration: CGFloat = 0.5, alphaAnimationDuration: CGFloat = 0.3, zoomRate: CGFloat = 1.2, FPS: Int32 = 30, videoFrameWidth: Int = 720, videoFrameHeight: Int = 1280, eachImageDuration: CGFloat = 3, success: @escaping (URL) -> Void, failure: @escaping (Error?) -> Void) {
        
        self.animationDuration = animationDuration
        self.FPS = FPS
        self.videoWidth = videoFrameWidth
        self.videoHeight = videoFrameHeight
        self.eachImageDuration = eachImageDuration
        self.animationType = animationType
        self.alphaAnimationDuration = alphaAnimationDuration
        self.zoomRate = zoomRate
        
        removeFileIfExist(url: url)
        removeFileIfExist(url: outputPath)

        do {
            let videoWriter = try AVAssetWriter(outputURL: url, fileType: AVFileType.mov)
            
            let writerInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSetting)
            
            let zeroTime = CMTimeMake(value: Int64(0),timescale: self.FPS)
            
            videoWriter.canAdd(writerInput)
            videoWriter.add(writerInput)
            
            let bufferAttributes:[String: Any] = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32ARGB)]
            let bufferAdapter = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: writerInput, sourcePixelBufferAttributes: bufferAttributes)
            
            videoWriter.startWriting()
            videoWriter.startSession(atSourceTime: zeroTime)
                        
            let _ = images.enumerated().map { (index, image) in
                while !writerInput.isReadyForMoreMediaData {}
                guard let buffer = newPixelBufferFrom(image: UIImage()) else { return }
                let lastTime = CMTimeMake(value: Int64(index - 1) * Int64(FPS), timescale: self.FPS)
                let presentTime = CMTimeAdd(lastTime, CMTimeMake(value: Int64(FPS), timescale: FPS))
                bufferAdapter.append(buffer, withPresentationTime: index == 0 ? CMTime.zero : presentTime)
            }
            
            writerInput.markAsFinished()
            videoWriter.finishWriting { [weak self] in
                switch videoWriter.status {
                case .completed:
                    self?.videoResource = self?.url.absoluteString
                    self?.imagesVideoAnimation(with: images, audioUrl: audioUrl, success: success, failure: failure)
                default:
                    failure(videoWriter.error)
                }
            }
        }catch {
            failure(error)
        }
        
    }
    
    private func imagesVideoAnimation(with images:[UIImage?], audioUrl: URL? = nil, success: @escaping (URL)->(), failure: @escaping (Error?)->()) {
        guard let videoPath = videoResource, let url = URL(string: videoPath) else {
            failure(NSError.init() as Error)
            return
        }
        
        self.videoDuration = eachImageDuration * CGFloat(images.count)
        let videoAsset = AVURLAsset(url: url)
        let composition = AVMutableComposition()
        let track = videoAsset.tracks(withMediaType: AVMediaType.video)

        guard let assetTrack = track.first else {
            failure(NSError.init() as Error)
            return
        }
        
        let videoTrack:AVAssetTrack = assetTrack as AVAssetTrack
        let endTime = CMTime(value: CMTimeValue(videoAsset.duration.timescale * Int32(videoDuration)), timescale: videoAsset.duration.timescale)
        let timerange = CMTimeRangeMake(start: CMTime.zero, duration: endTime)

        guard let compositionVideoTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: CMPersistentTrackID())  else {
            failure(NSError.init() as Error)
            return
        }

        do {
            try compositionVideoTrack.insertTimeRange(timerange, of: videoTrack, at: CMTime.zero)
            compositionVideoTrack.preferredTransform = videoTrack.preferredTransform
        } catch {
            failure(error)
        }

        let size = videoTrack.naturalSize

        let videolayer = CALayer()
        videolayer.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)

        let parentlayer = CALayer()
        parentlayer.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        parentlayer.addSublayer(videolayer)
        
        let imageArray = (animationType == .fromRightToLeft || animationType == .fromLowerToUpper) ? images.enumerated().map { (ele) -> EnumeratedSequence<[UIImage?]>.Element in
            return ele
        } : images.enumerated().reversed()
        
        for (index, image) in imageArray {

            let nextImage = image

            let horizontalRatio = CGFloat(self.videoWidth) / (nextImage?.size.width ?? 0)
            let verticalRatio = CGFloat(self.videoHeight) / (nextImage?.size.height ?? 0)
            let aspectRatio = min(horizontalRatio, verticalRatio)
            let newSize: CGSize = CGSize(width: (nextImage?.size.width ?? 0) * aspectRatio, height: (nextImage?.size.height ?? 0) * aspectRatio)
            let x = newSize.width < CGFloat(self.videoWidth) ? (CGFloat(self.videoWidth) - newSize.width) / 2 : 0
            let y = newSize.height < CGFloat(self.videoHeight) ? (CGFloat(self.videoHeight) - newSize.height) / 2 : 0

            let blackLayer = CALayer()
            blackLayer.frame = CGRect(x: 0, y: 0, width: videoTrack.naturalSize.width, height: videoTrack.naturalSize.height)
            blackLayer.backgroundColor = UIColor.black.cgColor
            
            let imageLayer = CALayer()
            imageLayer.backgroundColor = UIColor.black.cgColor
            imageLayer.frame = CGRect(x: x, y: y, width: newSize.width, height: newSize.height)
            imageLayer.contents = image?.cgImage
            blackLayer.addSublayer(imageLayer)
            
            positionAnimation(layer: blackLayer, index: index, isLastOne: index == images.count - 1)

            parentlayer.addSublayer(blackLayer)
        }

        let layercomposition = AVMutableVideoComposition()
        layercomposition.frameDuration = CMTimeMake(value: 1, timescale: self.FPS)
        layercomposition.renderSize = size
        layercomposition.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videolayer, in: parentlayer)
        let instruction = AVMutableVideoCompositionInstruction()
        instruction.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: composition.duration)
        let videotrack = composition.tracks(withMediaType: AVMediaType.video)[0] as AVAssetTrack
        let layerinstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videotrack)
        instruction.layerInstructions = [layerinstruction]
        layercomposition.instructions = [instruction]

        guard let assetExport = AVAssetExportSession(asset: composition, presetName:AVAssetExportPresetHighestQuality) else {return}
        assetExport.videoComposition = layercomposition
        assetExport.outputFileType = AVFileType.mp4
        assetExport.outputURL = outputPath
        assetExport.audioMix = audioMix(audioUrl: audioUrl, composition: composition, timerange: timerange)
        assetExport.exportAsynchronously(completionHandler: { [weak self] in
            guard let weakSelf = self else {
                failure(NSError.init() as Error)
                return
            }
            switch assetExport.status{
            case AVAssetExportSession.Status.completed:
                success(weakSelf.outputPath)
            default:
                failure(assetExport.error)
            }
        })
    }
    
    private func audioMix(audioUrl: URL?, composition: AVMutableComposition, timerange: CMTimeRange) -> AVAudioMix? {
        guard let url = audioUrl else { return nil }
        let audioAsset = AVURLAsset(url: url)
        let audioDuration = Int(audioAsset.duration.value) / Int(audioAsset.duration.timescale)
        let loopCount = Int(videoDuration) / audioDuration
        let residue = Int(videoDuration) - (audioDuration * loopCount)
        var musicDuration = CMTime.zero
        let audioTrack = audioAsset.tracks(withMediaType: AVMediaType.audio)
        guard let audioAssetTrack = audioTrack.first else { return nil }
        let compositionAudioTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: CMPersistentTrackID())
        
        let audioMix: AVMutableAudioMix = AVMutableAudioMix()
        var audioMixParam: [AVMutableAudioMixInputParameters] = []
        let audioParam: AVMutableAudioMixInputParameters = AVMutableAudioMixInputParameters(track: audioAssetTrack)
        audioParam.trackID = compositionAudioTrack?.trackID ?? CMPersistentTrackID()
        audioParam.setVolume(1, at: .zero)
        audioMixParam.append(audioParam)
        audioMix.inputParameters = audioMixParam
        
        do{
            for _ in 0..<loopCount {
                try compositionAudioTrack?.insertTimeRange(CMTimeRange(start: .zero, duration: audioAsset.duration), of: audioAssetTrack, at: musicDuration)
                musicDuration = CMTimeAdd(musicDuration, audioAsset.duration)
            }
            if residue > 0 {
                let dura = CMTime(value: CMTimeValue(residue), timescale: 1)
                try compositionAudioTrack?.insertTimeRange(CMTimeRange(start: .zero, duration: dura), of: audioAssetTrack, at: musicDuration)
            }
        }catch {
            return nil
        }
        
        return audioMix
    }
    
    private func positionAnimation(layer: CALayer, index: Int, isLastOne: Bool) {
//        self.animationType = mouldArray[index]
        self.animationType = .fadeInFadeOut
        switch self.animationType {
        case .fromRightToLeft:
            if index == 0 { return }
            layer.frame = CGRect(x: Int(Double(videoWidth) * 1.5), y: 0, width: videoWidth, height: videoHeight)
            let animation = CABasicAnimation(keyPath: "position.x")
            animation.fromValue = Double(videoWidth) * 1.5
            animation.toValue = videoWidth / 2
            animation.duration = CFTimeInterval(animationDuration)
            animation.beginTime = CFTimeInterval(eachImageDuration * CGFloat(index) - animationDuration / 2)
            animation.isRemovedOnCompletion = false
            animation.fillMode = .forwards
            layer.add(animation, forKey: "position.x")
                
        case .fromLowerToUpper:
            if index == 0 { return }
            layer.frame = CGRect(x: 0, y: -(videoHeight * Int(1.5)), width: videoWidth, height: videoHeight)
            let animation = CABasicAnimation(keyPath: "position.y")
            animation.fromValue = -(Double(videoHeight) * 1.5)
            animation.toValue = videoHeight / 2
            animation.duration = CFTimeInterval(animationDuration)
            animation.beginTime = CFTimeInterval(eachImageDuration * CGFloat(index) - animationDuration / 2)
            animation.isRemovedOnCompletion = false
            animation.fillMode = .forwards
            layer.add(animation, forKey: "position.y")

        case .zoomIn: //往中间等比例缩放
            let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
            scaleAnimation.fromValue = zoomRate
            scaleAnimation.toValue = 1
            scaleAnimation.beginTime = index == 0 ? -0.001 : CFTimeInterval(animationDuration + eachImageDuration * CGFloat(index) - alphaAnimationDuration)
            scaleAnimation.duration = CFTimeInterval(eachImageDuration)
            scaleAnimation.isRemovedOnCompletion = false
            layer.add(scaleAnimation, forKey: "transform.scale")
                
            let fadeOutAnimation = CABasicAnimation(keyPath: "opacity")
            fadeOutAnimation.fromValue = 1
            fadeOutAnimation.toValue = 0
            fadeOutAnimation.duration = CFTimeInterval(alphaAnimationDuration)
            fadeOutAnimation.beginTime = CFTimeInterval(animationDuration + eachImageDuration * CGFloat(index) + (eachImageDuration - alphaAnimationDuration))
            fadeOutAnimation.isRemovedOnCompletion = false
            fadeOutAnimation.fillMode = .forwards
            layer.add(fadeOutAnimation, forKey: "opacity")
                
        case .zoomOut://向外 等比例缩放
            let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
            scaleAnimation.fromValue = 1
            scaleAnimation.toValue = zoomRate
            scaleAnimation.beginTime = CFTimeInterval(animationDuration + eachImageDuration * CGFloat(index))
            scaleAnimation.duration = CFTimeInterval(eachImageDuration)
            scaleAnimation.isRemovedOnCompletion = false
            layer.add(scaleAnimation, forKey: "transform.scale")
                
            let fadeOutAnimation = CABasicAnimation(keyPath: "opacity")
            fadeOutAnimation.fromValue = 1
            fadeOutAnimation.toValue = 0
            fadeOutAnimation.duration = CFTimeInterval(alphaAnimationDuration)
            fadeOutAnimation.beginTime = CFTimeInterval(animationDuration + eachImageDuration * CGFloat(index) + (eachImageDuration - alphaAnimationDuration))
            fadeOutAnimation.isRemovedOnCompletion = false
            fadeOutAnimation.fillMode = .forwards
            layer.add(fadeOutAnimation, forKey: "opacity")
            
        case .rotate://逆时针旋转直到消失
            let animationDurationMargin = isLastOne ? 0 : animationDuration / 2
            let scaleAnimation = CABasicAnimation(keyPath: "transform.scale")
            scaleAnimation.fromValue = 1
            scaleAnimation.toValue = 0
            scaleAnimation.beginTime = CFTimeInterval(eachImageDuration * CGFloat(index + 1) - animationDurationMargin)
            scaleAnimation.duration = CFTimeInterval(animationDuration)
            scaleAnimation.isRemovedOnCompletion = false
            scaleAnimation.fillMode = .forwards
            layer.add(scaleAnimation, forKey: "transform.scale")
            
            let rotation = CABasicAnimation(keyPath: "transform.rotation.z")
            rotation.fromValue = 0
            rotation.toValue = 2 * Double.pi
            rotation.duration = CFTimeInterval(animationDuration)
            rotation.beginTime = CFTimeInterval(eachImageDuration * CGFloat(index + 1) - animationDurationMargin)
            rotation.isRemovedOnCompletion = false
            rotation.fillMode = .forwards
            layer.add(rotation, forKey: "transform.rotation.z")
            
        case .fadeInFadeOut://正常播放
            let animationDurationMargin = isLastOne ? 0 : animationDuration / 2
            let opacity = CABasicAnimation(keyPath: "opacity")
            opacity.fromValue = 1
            opacity.toValue = 0
            opacity.beginTime = CFTimeInterval(eachImageDuration * CGFloat(index + 1) - animationDurationMargin)
            opacity.duration = CFTimeInterval(animationDuration)
            opacity.isRemovedOnCompletion = false
            opacity.fillMode = .forwards
            layer.add(opacity, forKey: "opacity")
        }
    }
}

extension ImageAnimationTool {
    
    private func removeFileIfExist(url: URL) {
        if FileManager.default.fileExists(atPath: url.path) {
            do {
                try FileManager.default.removeItem(atPath: url.path)
            }catch {
                print(error)
            }
        }
    }
    
    private func newPixelBufferFrom(image: UIImage) -> CVPixelBuffer? {
        
        let options:[String: Any] = [kCVPixelBufferCGImageCompatibilityKey as String: true, kCVPixelBufferCGBitmapContextCompatibilityKey as String: true]
        
        var pxbuffer:CVPixelBuffer? = nil
        
        guard let frameWidth = self.videoSetting[AVVideoWidthKey] as? Int, let frameHeight: Int = self.videoSetting[AVVideoHeightKey] as? Int else { return nil }
        
        let _ = CVPixelBufferCreate(kCFAllocatorDefault, frameWidth, frameHeight, kCVPixelFormatType_32ARGB, options as CFDictionary?, &pxbuffer)
                
        guard let newPxbuffer = pxbuffer else { return nil }

        CVPixelBufferLockBaseAddress(newPxbuffer, CVPixelBufferLockFlags(rawValue: 0))

        let pxdata = CVPixelBufferGetBaseAddress(newPxbuffer)
        let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
        guard let context = CGContext(data: pxdata, width: frameWidth, height: frameHeight, bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(newPxbuffer), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue) else { return nil }

        UIGraphicsPushContext(context)
        context.translateBy(x: 0, y: CGFloat(frameHeight))
        context.scaleBy(x: 1, y: -1)
        image.draw(in: CGRect(x: 0, y: 0, width: frameWidth, height: frameHeight))
        UIGraphicsPopContext()
        
        CVPixelBufferUnlockBaseAddress(newPxbuffer, CVPixelBufferLockFlags(rawValue: 0))
        
        return pxbuffer
    }
}

使用方法

 //imagesArr : 图片数组
ImageAnimationTool.shared.transacformImages(images:imagesArr,animationType:.fadeInFadeOut) { url in
                print(url)
                DispatchQueue.main.async {
                    print("url == \(url)")
                    self.playVideo("\(url)")
                }
            } failure: { error in
                    print(error as Any)
            }

视频播放(原生api)

  func playVideo(_ url:String){
        let tempurl = URL(fileURLWithPath: "\(url)")
        let item = AVPlayerItem(url: tempurl)
        let player = AVPlayer(playerItem: item)
        let layer = AVPlayerLayer(player: player)
        //使playerLayer光栅化(即位图化),关闭了图层的blending。
        layer.shouldRasterize = true
        //显式指定光栅化的范围,这样能保证视频的显示质量,不然容易出现视频质量
        layer.rasterizationScale = UIScreen.main.scale
        layer.frame = CGRect(x: 0, y: 0, width: kScreenWidth, height: kScreenHeight)
        self.view.layer.addSublayer(layer)
        player.play()
    }
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-02-26 11:42:07  更:2022-02-26 11:45:27 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 15:43:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码