Created
August 16, 2015 00:07
-
-
Save plutovman/bc6f2f43794d924def30 to your computer and use it in GitHub Desktop.
UIImages to Video Swift files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// TimeLapseBuilder.swift | |
// | |
// Created by Adam Jensen on 5/10/15. | |
// Copyright (c) 2015 Adam Jensen. All rights reserved. | |
// | |
import AVFoundation | |
import UIKit | |
let kErrorDomain = "TimeLapseBuilder" | |
let kFailedToStartAssetWriterError = 0 | |
let kFailedToAppendPixelBufferError = 1 | |
class TimeLapseBuilder: NSObject { | |
let photoURLs: [String] | |
var videoWriter: AVAssetWriter? | |
init(photoURLs: [String]) { | |
self.photoURLs = photoURLs | |
} | |
func build(progress: (NSProgress -> Void), success: (NSURL -> Void), failure: (NSError -> Void)) { | |
let inputSize = CGSize(width: 4000, height: 3000) | |
let outputSize = CGSize(width: 1280, height: 720) | |
var error: NSError? | |
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! NSString | |
let videoOutputURL = NSURL(fileURLWithPath: documentsPath.stringByAppendingPathComponent("AssembledVideo.mov"))! | |
NSFileManager.defaultManager().removeItemAtURL(videoOutputURL, error: nil) | |
videoWriter = AVAssetWriter(URL: videoOutputURL, fileType: AVFileTypeQuickTimeMovie, error: &error) | |
if let videoWriter = videoWriter { | |
let videoSettings: [NSObject : AnyObject] = [ | |
AVVideoCodecKey : AVVideoCodecH264, | |
AVVideoWidthKey : outputSize.width, | |
AVVideoHeightKey : outputSize.height, | |
// AVVideoCompressionPropertiesKey : [ | |
// AVVideoAverageBitRateKey : NSInteger(1000000), | |
// AVVideoMaxKeyFrameIntervalKey : NSInteger(16), | |
// AVVideoProfileLevelKey : AVVideoProfileLevelH264BaselineAutoLevel | |
// ] | |
] | |
let videoWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoSettings) | |
let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor( | |
assetWriterInput: videoWriterInput, | |
sourcePixelBufferAttributes: [ | |
kCVPixelBufferPixelFormatTypeKey : kCVPixelFormatType_32ARGB, | |
kCVPixelBufferWidthKey : inputSize.width, | |
kCVPixelBufferHeightKey : inputSize.height, | |
] | |
) | |
assert(videoWriter.canAddInput(videoWriterInput)) | |
videoWriter.addInput(videoWriterInput) | |
if videoWriter.startWriting() { | |
videoWriter.startSessionAtSourceTime(kCMTimeZero) | |
assert(pixelBufferAdaptor.pixelBufferPool != nil) | |
let media_queue = dispatch_queue_create("mediaInputQueue", nil) | |
videoWriterInput.requestMediaDataWhenReadyOnQueue(media_queue, usingBlock: { () -> Void in | |
let fps: Int32 = 30 | |
let frameDuration = CMTimeMake(1, fps) | |
let currentProgress = NSProgress(totalUnitCount: Int64(self.photoURLs.count)) | |
var frameCount: Int64 = 0 | |
var remainingPhotoURLs = [String](self.photoURLs) | |
while (videoWriterInput.readyForMoreMediaData && !remainingPhotoURLs.isEmpty) { | |
let nextPhotoURL = remainingPhotoURLs.removeAtIndex(0) | |
let lastFrameTime = CMTimeMake(frameCount, fps) | |
let presentationTime = frameCount == 0 ? lastFrameTime : CMTimeAdd(lastFrameTime, frameDuration) | |
if !self.appendPixelBufferForImageAtURL(nextPhotoURL, pixelBufferAdaptor: pixelBufferAdaptor, presentationTime: presentationTime) { | |
error = NSError( | |
domain: kErrorDomain, | |
code: kFailedToAppendPixelBufferError, | |
userInfo: [ | |
"description": "AVAssetWriterInputPixelBufferAdapter failed to append pixel buffer", | |
"rawError": videoWriter.error ?? "(none)" | |
] | |
) | |
break | |
} | |
frameCount++ | |
currentProgress.completedUnitCount = frameCount | |
progress(currentProgress) | |
} | |
videoWriterInput.markAsFinished() | |
videoWriter.finishWritingWithCompletionHandler { () -> Void in | |
if error == nil { | |
success(videoOutputURL) | |
} | |
} | |
}) | |
} else { | |
error = NSError( | |
domain: kErrorDomain, | |
code: kFailedToStartAssetWriterError, | |
userInfo: ["description": "AVAssetWriter failed to start writing"] | |
) | |
} | |
} | |
if let error = error { | |
failure(error) | |
} | |
} | |
func appendPixelBufferForImageAtURL(url: String, pixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor, presentationTime: CMTime) -> Bool { | |
var appendSucceeded = true | |
autoreleasepool { | |
if let url = NSURL(string: url), | |
let imageData = NSData(contentsOfURL: url), | |
let image = UIImage(data: imageData) { | |
var pixelBuffer: Unmanaged<CVPixelBuffer>? | |
let status: CVReturn = CVPixelBufferPoolCreatePixelBuffer( | |
kCFAllocatorDefault, | |
pixelBufferAdaptor.pixelBufferPool, | |
&pixelBuffer | |
) | |
if let pixelBuffer = pixelBuffer where status == 0 { | |
let managedPixelBuffer = pixelBuffer.takeRetainedValue() | |
fillPixelBufferFromImage(image, pixelBuffer: managedPixelBuffer) | |
appendSucceeded = pixelBufferAdaptor.appendPixelBuffer( | |
managedPixelBuffer, | |
withPresentationTime: presentationTime | |
) | |
} else { | |
NSLog("error: Failed to allocate pixel buffer from pool") | |
} | |
} | |
} | |
return appendSucceeded | |
} | |
func fillPixelBufferFromImage(image: UIImage, pixelBuffer: CVPixelBufferRef) { | |
let imageData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage)) | |
let lockStatus = CVPixelBufferLockBaseAddress(pixelBuffer, 0) | |
let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer) | |
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedFirst.rawValue) | |
let rgbColorSpace = CGColorSpaceCreateDeviceRGB() | |
let context = CGBitmapContextCreate( | |
pixelData, | |
Int(image.size.width), | |
Int(image.size.height), | |
8, | |
Int(4 * image.size.width), | |
rgbColorSpace, | |
bitmapInfo | |
) | |
CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage) | |
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ViewController.swift | |
// Mod of Adam Jensen's movie creator code | |
import UIKit | |
import AVFoundation | |
class ViewController: UIViewController { | |
@IBOutlet weak var previewView: UIView! | |
@IBOutlet weak var capturedImage: UIImageView! | |
@IBAction func PlayImageSequence(sender: UIButton) { | |
toggleAnimationSequence() | |
} | |
@IBAction func EncodeVideoFromImageSequence(sender: UIButton) { | |
println("...placeholder for encode") | |
encodeVideo() | |
} | |
@IBOutlet weak var ImageSequenceView: UIImageView! | |
var captureSession: AVCaptureSession? | |
var stillImageOutput: AVCaptureStillImageOutput? | |
var previewLayer: AVCaptureVideoPreviewLayer? | |
var imageSequenceList = [UIImage]() | |
var rawImageList = [String]() | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
// Do any additional setup after loading the view, typically from a nib. | |
let formatter = NSNumberFormatter() | |
formatter.minimumIntegerDigits = 4 | |
for i in 0 ... 535 { | |
let imageName = ("scenes." + formatter.stringFromNumber(i)! + ".jpg") | |
println (imageName) | |
imageSequenceList.append(UIImage(named: imageName)!) | |
rawImageList.append(imageName) | |
} | |
} | |
override func viewWillAppear(animated: Bool) { | |
super.viewWillAppear(animated) | |
captureSession = AVCaptureSession() | |
captureSession!.sessionPreset = AVCaptureSessionPresetPhoto | |
var backCamera = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) | |
var error: NSError? | |
var input = AVCaptureDeviceInput(device: backCamera, error: &error) | |
if error == nil && captureSession!.canAddInput(input) { | |
captureSession!.addInput(input) | |
stillImageOutput = AVCaptureStillImageOutput() | |
stillImageOutput!.outputSettings = [AVVideoCodecKey: AVVideoCodecJPEG] | |
if captureSession!.canAddOutput(stillImageOutput) { | |
captureSession!.addOutput(stillImageOutput) | |
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) | |
previewLayer!.videoGravity = AVLayerVideoGravityResizeAspect | |
previewLayer!.connection?.videoOrientation = AVCaptureVideoOrientation.Portrait | |
previewView.layer.addSublayer(previewLayer) | |
captureSession!.startRunning() | |
} | |
} | |
} | |
override func viewDidAppear(animated: Bool) { | |
super.viewDidAppear(animated) | |
previewLayer!.frame = previewView.bounds | |
} | |
@IBAction func didPressTakePhoto(sender: UIButton) { | |
if let videoConnection = stillImageOutput!.connectionWithMediaType(AVMediaTypeVideo) { | |
videoConnection.videoOrientation = AVCaptureVideoOrientation.Portrait | |
stillImageOutput?.captureStillImageAsynchronouslyFromConnection(videoConnection, completionHandler: {(sampleBuffer, error) in | |
if (sampleBuffer != nil) { | |
var imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer) | |
var dataProvider = CGDataProviderCreateWithCFData(imageData) | |
var cgImageRef = CGImageCreateWithJPEGDataProvider(dataProvider, nil, true, kCGRenderingIntentDefault) | |
var image = UIImage(CGImage: cgImageRef, scale: 1.0, orientation: UIImageOrientation.Right) | |
self.capturedImage.image = image | |
} | |
}) | |
} | |
} | |
@IBAction func didTakeScreenGrab(sender: AnyObject) { | |
let screenshot = self.view?.pb_takeSnapshot() | |
UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil) | |
} | |
func toggleAnimationSequence(){ | |
if (!ImageSequenceView.isAnimating()){ | |
ImageSequenceView.animationImages = imageSequenceList | |
ImageSequenceView.startAnimating() | |
} else { | |
ImageSequenceView.stopAnimating() | |
} | |
} | |
func encodeVideo(){ | |
//func build(progress: (NSProgress -> Void), success: (NSURL -> Void), failure: (NSError -> Void)) { | |
let createVideo = TimeLapseBuilder(photoURLs: rawImageList) | |
createVideo.build( | |
{ (progress: NSProgress) in | |
NSLog("Progress: \(progress.completedUnitCount) / \(progress.totalUnitCount)") | |
}, | |
success: { url in | |
NSLog("Output written to \(url)") | |
}, | |
failure: { error in | |
NSLog("failure: \(error)") | |
} | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment