Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save rgcottrell/325ac087585ff6eeb8cb50e2729fd1ab to your computer and use it in GitHub Desktop.
Save rgcottrell/325ac087585ff6eeb8cb50e2729fd1ab to your computer and use it in GitHub Desktop.
Transcode a movie to a different framerate.
// 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