Created
May 3, 2016 03:48
-
-
Save rgcottrell/325ac087585ff6eeb8cb50e2729fd1ab to your computer and use it in GitHub Desktop.
Transcode a movie to a different framerate.
This file contains hidden or 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
// This sketches a script that will sample images from an AVAsset at a | |
// new framerate. This might be used to transcode a movie or create an | |
// animated GIF. | |
import AppKit | |
import AVFoundation | |
import CoreMedia | |
let FPS = 12 // The target framerate for the new movie. | |
let frameDuration = CMTimeMake(1, Int32(FPS)) | |
let srcURL = NSURL(fileURLWithPath: "./cat.mov", isDirectory: false) | |
let options: [String : AnyObject] = [AVURLAssetPreferPreciseDurationAndTimingKey: NSNumber(bool: true)] | |
let asset = AVURLAsset(URL: srcURL, options: options) | |
// Compile a list of times to sample the movie. By picking times that | |
// correspond to the new target framerate, we can ensure that the | |
// transcode movie or GIF will play smoothly. | |
let duration = asset.duration | |
var time = kCMTimeZero | |
var requestedTimes: [NSValue] = [] | |
while CMTimeCompare(time, duration) <= 0 { | |
requestedTimes.append(NSValue(CMTime: time)) | |
time = CMTimeAdd(time, frameDuration) | |
} | |
// Configure the image sampler. By default this generates images at | |
// their native resolution in the source, but smaller sizes can also | |
// be requested. | |
let imageGenerator = AVAssetImageGenerator(asset: asset) | |
imageGenerator.appliesPreferredTrackTransform = true | |
imageGenerator.requestedTimeToleranceBefore = kCMTimeZero | |
imageGenerator.requestedTimeToleranceAfter = kCMTimeZero | |
// Asynchronously generate and process the sampled images. The given | |
// callback will be called for each image in order. A semaphore is | |
// used to block further progress of the script until the conversion | |
// has finished. | |
// | |
// This should be an efficient operation, perhaps even more efficient | |
// than reading the movie directly from an AVAssetReader as intermediate | |
// frames do not need to be decoded. | |
var loop = 0 | |
let semaphore = dispatch_semaphore_create(0) | |
imageGenerator.generateCGImagesAsynchronouslyForTimes(requestedTimes) { | |
(imageTime, image, _, result, error) -> Void in | |
// Make sure to count every callback request, whether or not it | |
// completed successfully, so that the semaphore can be singaled | |
// when all requested times have been processed. | |
defer { | |
loop = loop + 1 | |
if loop == requestedTimes.count { | |
dispatch_semaphore_signal(semaphore) | |
} | |
} | |
guard let image = image else { | |
return | |
} | |
// DO SOMETHING WITH IMAGE | |
// * Convert to CVPixelBuffer and append to an AVAssetWriter | |
// * Add to CGImageDestination to create animated GIF | |
} | |
// Wait for processing to finish. | |
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment