How do I export UIImage array as a movie in Swift

2019-03-31 06:12发布

问题:

I need to export an array of UIImage and build a movie putting some text in front of the image and if it's possible music also. Can you give me an hand with code? I only have found something with Objective-c and old version of Swift.

回答1:

This is the first answers I posted to the question: create movie from [UIImage], Swift

Following is a copy of the answer:

I convert the objective-c code that posted by ’@Cameron E‘ to Swift 3, and It's working. the answer's link:@Cameron E's CEMovieMaker

import Foundation
import AVFoundation
import UIKit

public typealias CXEMovieMakerCompletion = (URL) -> Void
typealias CXEMovieMakerUIImageExtractor = (AnyObject) -> UIImage?


public class CXEImagesToVideo: NSObject{
var assetWriter:AVAssetWriter!
var writeInput:AVAssetWriterInput!
var bufferAdapter:AVAssetWriterInputPixelBufferAdaptor!
var videoSettings:[String : Any]!
var frameTime:CMTime!
var fileURL:URL!

var completionBlock: CXEMovieMakerCompletion?
var movieMakerUIImageExtractor:CXEMovieMakerUIImageExtractor?


public class func videoSettings(codec:String, width:Int, height:Int) -> [String: Any]{
    if(Int(width) % 16 != 0){
        print("warning: video settings width must be divisible by 16")
    }

    let videoSettings:[String: Any] = [AVVideoCodecKey: AVVideoCodecH264,
                         AVVideoWidthKey: width,
                         AVVideoHeightKey: height]

    return videoSettings
}

public init(videoSettings: [String: Any]) {
    super.init()

    let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
    let tempPath = paths[0] + "/exprotvideo.mp4"
    if(FileManager.default.fileExists(atPath: tempPath)){
        guard (try? FileManager.default.removeItem(atPath: tempPath)) != nil else {
            print("remove path failed")
            return
        }
    }

    self.fileURL = URL(fileURLWithPath: tempPath)
    self.assetWriter = try! AVAssetWriter(url: self.fileURL, fileType: AVFileTypeQuickTimeMovie)

    self.videoSettings = videoSettings
    self.writeInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings)
    assert(self.assetWriter.canAdd(self.writeInput), "add failed")

    self.assetWriter.add(self.writeInput)
    let bufferAttributes:[String: Any] = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_32ARGB)]
    self.bufferAdapter = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: self.writeInput, sourcePixelBufferAttributes: bufferAttributes)
    self.frameTime = CMTimeMake(1, 1)
}

public func createMovieFrom(urls: [URL], withCompletion: @escaping CXEMovieMakerCompletion){
    self.createMovieFromSource(images: urls as [AnyObject], extractor:{(inputObject:AnyObject) ->UIImage? in
        return UIImage(data: try! Data(contentsOf: inputObject as! URL))}, withCompletion: withCompletion)
}

public func createMovieFrom(images: [UIImage], withCompletion: @escaping CXEMovieMakerCompletion){
    self.createMovieFromSource(images: images, extractor: {(inputObject:AnyObject) -> UIImage? in
    return inputObject as? UIImage}, withCompletion: withCompletion)
}

func createMovieFromSource(images: [AnyObject], extractor: @escaping CXEMovieMakerUIImageExtractor, withCompletion: @escaping CXEMovieMakerCompletion){
    self.completionBlock = withCompletion

    self.assetWriter.startWriting()
    self.assetWriter.startSession(atSourceTime: kCMTimeZero)

    let mediaInputQueue = DispatchQueue(label: "mediaInputQueue")
    var i = 0
    let frameNumber = images.count

    self.writeInput.requestMediaDataWhenReady(on: mediaInputQueue){
        while(true){
            if(i >= frameNumber){
                break
            }

            if (self.writeInput.isReadyForMoreMediaData){
                var sampleBuffer:CVPixelBuffer?
                autoreleasepool{
                    let img = extractor(images[i])
                    if img == nil{
                        i += 1
                        print("Warning: counld not extract one of the frames")
                        //continue
                    }
                    sampleBuffer = self.newPixelBufferFrom(cgImage: img!.cgImage!)
                }
                if (sampleBuffer != nil){
                    if(i == 0){
                        self.bufferAdapter.append(sampleBuffer!, withPresentationTime: kCMTimeZero)
                    }else{
                        let value = i - 1
                        let lastTime = CMTimeMake(Int64(value), self.frameTime.timescale)
                        let presentTime = CMTimeAdd(lastTime, self.frameTime)
                        self.bufferAdapter.append(sampleBuffer!, withPresentationTime: presentTime)
                    }
                    i = i + 1
                }
            }
        }
        self.writeInput.markAsFinished()
        self.assetWriter.finishWriting {
            DispatchQueue.main.sync {
                self.completionBlock!(self.fileURL)
            }
        }
    }
}

func newPixelBufferFrom(cgImage:CGImage) -> CVPixelBuffer?{
    let options:[String: Any] = [kCVPixelBufferCGImageCompatibilityKey as String: true, kCVPixelBufferCGBitmapContextCompatibilityKey as String: true]
    var pxbuffer:CVPixelBuffer?
    let frameWidth = self.videoSettings[AVVideoWidthKey] as! Int
    let frameHeight = self.videoSettings[AVVideoHeightKey] as! Int

    let status = CVPixelBufferCreate(kCFAllocatorDefault, frameWidth, frameHeight, kCVPixelFormatType_32ARGB, options as CFDictionary?, &pxbuffer)
    assert(status == kCVReturnSuccess && pxbuffer != nil, "newPixelBuffer failed")

    CVPixelBufferLockBaseAddress(pxbuffer!, CVPixelBufferLockFlags(rawValue: 0))
    let pxdata = CVPixelBufferGetBaseAddress(pxbuffer!)
    let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
    let context = CGContext(data: pxdata, width: frameWidth, height: frameHeight, bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pxbuffer!), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue)
    assert(context != nil, "context is nil")

    context!.concatenate(CGAffineTransform.identity)
    context!.draw(cgImage, in: CGRect(x: 0, y: 0, width: cgImage.width, height: cgImage.height))
    CVPixelBufferUnlockBaseAddress(pxbuffer!, CVPixelBufferLockFlags(rawValue: 0))
    return pxbuffer
}
}

Usage:

        var uiImages = [UIImage]()

/** add image to uiImages */

let settings = CXEImagesToVideo.videoSettings(codec: AVVideoCodecH264, width: (uiImages[0].cgImage?.width)!, height: (uiImages[0].cgImage?.height)!)
let movieMaker = CXEImagesToVideo(videoSettings: settings)
movieMaker.createMovieFrom(images: uiImages){ (fileURL:URL) in
    let video = AVAsset(url: fileURL)
    let playerItem = AVPlayerItem(asset: video)
    let player = CXEPlayer()
    player.setPlayerItem(playerItem: playerItem)

    self.playerView.player = player
}

export or play the video with fileURL. Gist:ImagesToAssetURL async and sync