How do I export UIImage array as a movie?

后端 未结 10 1860
臣服心动
臣服心动 2020-11-22 02:11

I have a serious problem: I have an NSArray with several UIImage objects. What I now want to do, is create movie from those UIImages.

10条回答
  •  礼貌的吻别
    2020-11-22 02:24

    Just translated @Scott Raposa answer to swift3 (with some very little changes):

    import AVFoundation
    import UIKit
    import Photos
    
    struct RenderSettings {
    
        var size : CGSize = .zero
        var fps: Int32 = 6   // frames per second
        var avCodecKey = AVVideoCodecH264
        var videoFilename = "render"
        var videoFilenameExt = "mp4"
    
    
        var outputURL: URL {
            // Use the CachesDirectory so the rendered video file sticks around as long as we need it to.
            // Using the CachesDirectory ensures the file won't be included in a backup of the app.
            let fileManager = FileManager.default
            if let tmpDirURL = try? fileManager.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true) {
                return tmpDirURL.appendingPathComponent(videoFilename).appendingPathExtension(videoFilenameExt)
            }
            fatalError("URLForDirectory() failed")
        }
    }
    
    
    class ImageAnimator {
    
        // Apple suggests a timescale of 600 because it's a multiple of standard video rates 24, 25, 30, 60 fps etc.
        static let kTimescale: Int32 = 600
    
        let settings: RenderSettings
        let videoWriter: VideoWriter
        var images: [UIImage]!
    
        var frameNum = 0
    
        class func saveToLibrary(videoURL: URL) {
            PHPhotoLibrary.requestAuthorization { status in
                guard status == .authorized else { return }
    
                PHPhotoLibrary.shared().performChanges({
                    PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
                }) { success, error in
                    if !success {
                        print("Could not save video to photo library:", error)
                    }
                }
            }
        }
    
        class func removeFileAtURL(fileURL: URL) {
            do {
                try FileManager.default.removeItem(atPath: fileURL.path)
            }
            catch _ as NSError {
                // Assume file doesn't exist.
            }
        }
    
        init(renderSettings: RenderSettings) {
            settings = renderSettings
            videoWriter = VideoWriter(renderSettings: settings)
    //        images = loadImages()
        }
    
        func render(completion: (()->Void)?) {
    
            // The VideoWriter will fail if a file exists at the URL, so clear it out first.
            ImageAnimator.removeFileAtURL(fileURL: settings.outputURL)
    
            videoWriter.start()
            videoWriter.render(appendPixelBuffers: appendPixelBuffers) {
                ImageAnimator.saveToLibrary(videoURL: self.settings.outputURL)
                completion?()
            }
    
        }
    
    //    // Replace this logic with your own.
    //    func loadImages() -> [UIImage] {
    //        var images = [UIImage]()
    //        for index in 1...10 {
    //            let filename = "\(index).jpg"
    //            images.append(UIImage(named: filename)!)
    //        }
    //        return images
    //    }
    
        // This is the callback function for VideoWriter.render()
        func appendPixelBuffers(writer: VideoWriter) -> Bool {
    
            let frameDuration = CMTimeMake(Int64(ImageAnimator.kTimescale / settings.fps), ImageAnimator.kTimescale)
    
            while !images.isEmpty {
    
                if writer.isReadyForData == false {
                    // Inform writer we have more buffers to write.
                    return false
                }
    
                let image = images.removeFirst()
                let presentationTime = CMTimeMultiply(frameDuration, Int32(frameNum))
                let success = videoWriter.addImage(image: image, withPresentationTime: presentationTime)
                if success == false {
                    fatalError("addImage() failed")
                }
    
                frameNum += 1
            }
    
            // Inform writer all buffers have been written.
            return true
        }
    
    }
    
    
    class VideoWriter {
    
        let renderSettings: RenderSettings
    
        var videoWriter: AVAssetWriter!
        var videoWriterInput: AVAssetWriterInput!
        var pixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor!
    
        var isReadyForData: Bool {
            return videoWriterInput?.isReadyForMoreMediaData ?? false
        }
    
        class func pixelBufferFromImage(image: UIImage, pixelBufferPool: CVPixelBufferPool, size: CGSize) -> CVPixelBuffer {
    
            var pixelBufferOut: CVPixelBuffer?
    
            let status = CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pixelBufferPool, &pixelBufferOut)
            if status != kCVReturnSuccess {
                fatalError("CVPixelBufferPoolCreatePixelBuffer() failed")
            }
    
            let pixelBuffer = pixelBufferOut!
    
            CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
    
            let data = CVPixelBufferGetBaseAddress(pixelBuffer)
            let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
            let context = CGContext(data: data, width: Int(size.width), height: Int(size.height),
                                    bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)
    
            context!.clear(CGRect(x:0,y: 0,width: size.width,height: size.height))
    
            let horizontalRatio = size.width / image.size.width
            let verticalRatio = size.height / image.size.height
            //aspectRatio = max(horizontalRatio, verticalRatio) // ScaleAspectFill
            let aspectRatio = min(horizontalRatio, verticalRatio) // ScaleAspectFit
    
            let newSize = CGSize(width: image.size.width * aspectRatio, height: image.size.height * aspectRatio)
    
            let x = newSize.width < size.width ? (size.width - newSize.width) / 2 : 0
            let y = newSize.height < size.height ? (size.height - newSize.height) / 2 : 0
    
            context?.draw(image.cgImage!, in: CGRect(x:x,y: y, width: newSize.width, height: newSize.height))
            CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
    
            return pixelBuffer
        }
    
        init(renderSettings: RenderSettings) {
            self.renderSettings = renderSettings
        }
    
        func start() {
    
            let avOutputSettings: [String: Any] = [
                AVVideoCodecKey: renderSettings.avCodecKey,
                AVVideoWidthKey: NSNumber(value: Float(renderSettings.size.width)),
                AVVideoHeightKey: NSNumber(value: Float(renderSettings.size.height))
            ]
    
            func createPixelBufferAdaptor() {
                let sourcePixelBufferAttributesDictionary = [
                    kCVPixelBufferPixelFormatTypeKey as String: NSNumber(value: kCVPixelFormatType_32ARGB),
                    kCVPixelBufferWidthKey as String: NSNumber(value: Float(renderSettings.size.width)),
                    kCVPixelBufferHeightKey as String: NSNumber(value: Float(renderSettings.size.height))
                ]
                pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriterInput,
                                                                          sourcePixelBufferAttributes: sourcePixelBufferAttributesDictionary)
            }
    
            func createAssetWriter(outputURL: URL) -> AVAssetWriter {
                guard let assetWriter = try? AVAssetWriter(outputURL: outputURL, fileType: AVFileTypeMPEG4) else {
                    fatalError("AVAssetWriter() failed")
                }
    
                guard assetWriter.canApply(outputSettings: avOutputSettings, forMediaType: AVMediaTypeVideo) else {
                    fatalError("canApplyOutputSettings() failed")
                }
    
                return assetWriter
            }
    
            videoWriter = createAssetWriter(outputURL: renderSettings.outputURL)
            videoWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: avOutputSettings)
    
            if videoWriter.canAdd(videoWriterInput) {
                videoWriter.add(videoWriterInput)
            }
            else {
                fatalError("canAddInput() returned false")
            }
    
            // The pixel buffer adaptor must be created before we start writing.
            createPixelBufferAdaptor()
    
            if videoWriter.startWriting() == false {
                fatalError("startWriting() failed")
            }
    
            videoWriter.startSession(atSourceTime: kCMTimeZero)
    
            precondition(pixelBufferAdaptor.pixelBufferPool != nil, "nil pixelBufferPool")
        }
    
        func render(appendPixelBuffers: ((VideoWriter)->Bool)?, completion: (()->Void)?) {
    
            precondition(videoWriter != nil, "Call start() to initialze the writer")
    
            let queue = DispatchQueue(label: "mediaInputQueue")
            videoWriterInput.requestMediaDataWhenReady(on: queue) {
                let isFinished = appendPixelBuffers?(self) ?? false
                if isFinished {
                    self.videoWriterInput.markAsFinished()
                    self.videoWriter.finishWriting() {
                        DispatchQueue.main.async {
                            completion?()
                        }
                    }
                }
                else {
                    // Fall through. The closure will be called again when the writer is ready.
                }
            }
        }
    
        func addImage(image: UIImage, withPresentationTime presentationTime: CMTime) -> Bool {
    
            precondition(pixelBufferAdaptor != nil, "Call start() to initialze the writer")
    
            let pixelBuffer = VideoWriter.pixelBufferFromImage(image: image, pixelBufferPool: pixelBufferAdaptor.pixelBufferPool!, size: renderSettings.size)
            return pixelBufferAdaptor.append(pixelBuffer, withPresentationTime: presentationTime)
        }
    
    }
    

提交回复
热议问题