iosswiftavassetavassetimagegenerator

What to input to Generate CGImages Asynchronously


In my app, I want to generate multiple thumbnails of a video, preferably good quality thumbnails. My old approach was to perform a loop 15 times, and copy a CGImage at different time. As shown below

func generateThumbnails(_ fileURL:URL) {
    let asset = AVAsset(url: fileURL)
    let imageGenerator = AVAssetImageGenerator(asset: asset)
    imageGenerator.apertureMode = AVAssetImageGeneratorApertureMode.cleanAperture
    imageGenerator.appliesPreferredTrackTransform = true
    let duration = asset.duration
    let seconds = CMTimeGetSeconds(duration)
    let addition = seconds / 15
    var number = 1.0
    do {
        while number < seconds {
            let thumbnailCGImage = try imageGenerator.copyCGImage(at: CMTimeMake(Int64(number),1), actualTime: nil)
            let image = UIImage(cgImage: thumbnailCGImage)
            thumbnails.append(image)
            number += addition
        }
    } catch let err {
        return
    }
}

However, after doing some more research, I found it was more logical to just generate thumbnails asynchronously using

let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.generateCGImagesAsynchronously(forTimes: [NSValue], completionHandler: AVAssetImageGeneratorCompletionHandler)

However, I don't really know what the I am supposed to input into the [NSValue] and the completion handler.

I just need an explanation on how to generate thumbnails this way, and see if it's the better thing to do.


Solution

  • https://developer.apple.com/documentation/avfoundation/avassetimagegenerator/1388100-generatecgimagesasynchronously

    requestedTimes An array of NSValue objects, each containing a CMTime, specifying the asset times at which an image is requested.

    Usage:

    let duration = asset.duration
    let seconds = CMTimeGetSeconds(duration)
    let addition = seconds / 15
    var number = 1.0
    
    var times = [NSValue]()
    times.append(NSValue(time: CMTimeMake(Int64(number), 1)))
    while number < seconds {
        number += addition
        times.append(NSValue(time: CMTimeMake(Int64(number), 1)))
    }
    
    struct Formatter {
        static let formatter: DateFormatter = {
            let result = DateFormatter()
            result.dateStyle = .short
            return result
        }()
    }
    
    imageGenerator.generateCGImagesAsynchronously(forTimes: times) { (requestedTime, cgImage, actualImageTime, status, error) in
    
        let seconds = CMTimeGetSeconds(requestedTime)
        let date = Date(timeIntervalSinceNow: seconds)
        let time = Formatter.formatter.string(from: date)
    
        switch status {
        case .succeeded: do {
                if let image = cgImage {
                    print("Generated image for approximate time: \(time)")
    
                    let img = UIImage(cgImage: image)
                    //do something with `img`
                }
                else {
                    print("Failed to generate a valid image for time: \(time)")
                }
            }
    
        case .failed: do {
                if let error = error {
                    print("Failed to generate image with Error: \(error) for time: \(time)")
                }
                else {
                    print("Failed to generate image for time: \(time)")
                }
            }
    
        case .cancelled: do {
            print("Image generation cancelled for time: \(time)")
            }
        }
    }