iOS - How to get thumbnail from video without play?
Asked Answered
P

10

16

I'm trying to get thumbnail from video and show it in my tableview. Here is my code:

- (UIImage *)imageFromVideoURL:(NSURL *)contentURL {
    AVAsset *asset = [AVAsset assetWithURL:contentURL];

    //  Get thumbnail at the very start of the video
    CMTime thumbnailTime = [asset duration];
    thumbnailTime.value = 25;

    //  Get image from the video at the given time
    AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];

    CGImageRef imageRef = [imageGenerator copyCGImageAtTime:thumbnailTime actualTime:NULL error:NULL];
    UIImage *thumbnail = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);

    return thumbnail;
}

But image allways return black. What's wrong?

Pettitoes answered 21/9, 2015 at 9:8 Comment(2)
Time.value is in miliseconds maybe frame at time is black Did you change the frame to some other valueConspecific
@Pettitoes Will it work for HLS ?Interregnum
W
17

Using Swift 5, as an extension function on AVAsset:

import AVKit

extension AVAsset {

    func generateThumbnail(completion: @escaping (UIImage?) -> Void) {
        DispatchQueue.global().async {
            let imageGenerator = AVAssetImageGenerator(asset: self)
            let time = CMTime(seconds: 0.0, preferredTimescale: 600)
            let times = [NSValue(time: time)]
            imageGenerator.generateCGImagesAsynchronously(forTimes: times, completionHandler: { _, image, _, _, _ in
                if let image = image {
                    completion(UIImage(cgImage: image))
                } else {
                    completion(nil)
                }
            })
        }
    }
}

Usage:

            AVAsset(url: url).generateThumbnail { [weak self] (image) in
                DispatchQueue.main.async {
                    guard let image = image else { return }
                    self?.imageView.image = image
                }
            }
Woodworth answered 5/4, 2019 at 8:16 Comment(3)
how can I set image size?Incommunicative
@UtkuDalmaz why? image size would be the size of the video.Subulate
Your code doesn't respect asset transform so please add imageGenerator.appliesPreferredTrackTransform = true to get un rotated images.Sunnysunproof
A
11

Use this : Swift 3 -

func createThumbnailOfVideoFromFileURL(videoURL: String) -> UIImage? {
    let asset = AVAsset(url: URL(string: videoURL)!)
    let assetImgGenerate = AVAssetImageGenerator(asset: asset)
    assetImgGenerate.appliesPreferredTrackTransform = true
    let time = CMTimeMakeWithSeconds(Float64(1), 100)
    do {
        let img = try assetImgGenerate.copyCGImage(at: time, actualTime: nil)
        let thumbnail = UIImage(cgImage: img)
        return thumbnail
    } catch {
        return UIImage(named: "ico_placeholder")
    }
}

Important Note :

You will need to use this in an if else as it is resource extensive. You will have to store the image in an array or model and check that if once thumbnail has been created it refers to the cache/array so that cellForRowAtIndexPath does not cause a lag in scrolling your UITableView

Afreet answered 24/3, 2017 at 10:1 Comment(0)
F
4

Just use this code.. Pass your video URL and get an image.

+(UIImage *)getPlaceholderImageFromVideo:(NSString *)videoURL {
    NSURL *url = [NSURL URLWithString:videoURL];
    AVAsset *asset = [AVAsset assetWithURL:url];
    AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
    CMTime time = [asset duration];
    time.value = 0;
    CGImageRef imageRef = [imageGenerator copyCGImageAtTime:time actualTime:NULL error:NULL];
    UIImage *thumbnail = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    return thumbnail;
}

Hope, this is what you're looking for. Any concern get back to me. :)

Freebooter answered 18/7, 2017 at 10:47 Comment(1)
this function taking little time, due to this table scroll is not smooth.Lordsandladies
O
3
//(Local URL)
NSURL *videoURL = [NSURL fileURLWithPath:filepath];// filepath is your video file path 



AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:videoURL options:nil];
AVAssetImageGenerator *generateImg = [[AVAssetImageGenerator alloc] initWithAsset:asset];
NSError *error = NULL;
CMTime time = CMTimeMake(1, 1);
CGImageRef refImg = [generateImg copyCGImageAtTime:time actualTime:NULL error:&error];
NSLog(@"error==%@, Refimage==%@", error, refImg);

UIImage *frameImage= [[UIImage alloc] initWithCGImage:refImg];
return frameImage;
Outrigger answered 30/12, 2015 at 5:0 Comment(1)
This approach will leak refImg, and needs a CGImageRelease before returning your result (developer.apple.com/library/archive/documentation/…)Natie
B
2

Please check the link for "Generating Thumbnails from Videos"

https://littlebitesofcocoa.com/115-generating-thumbnails-from-videos

It's quite common for an app to need to display one or more thumbnails (small still-image previews) of what's in a video. However, depending on where the video is coming from, we might not have easy access to pre-made thumbnail(s) for it. Let's look at how we can use AVAssetImageGenerator to grab our own. We start with a simple NSURL for the video, this can be local or remote. We'll create an AVAsset with it and that to a new AVAssetImageGenerator object. We'll configure the generator to apply preferred transforms so our thumbnails are in the correct orientation.

import AVFoundation

if let asset = AVAsset(URL: videoURL) {
   let durationSeconds = CMTimeGetSeconds(asset.duration)
   let generator = AVAssetImageGenerator(asset: asset)

   generator.appliesPreferredTrackTransform = true

   let time = CMTimeMakeWithSeconds(durationSeconds/3.0, 600)
   var thumbnailImage: CGImageRef 
   generator.generateCGImagesAsynchronouslyForTimes([NSValue(CMTime: time)]) { 
       (requestedTime: CMTime, thumbnail: CGImage?, actualTime: CMTime, result: AVAssetImageGeneratorResult, error: NSError?) in
      self.videoThumbnailImageView.image = UIImage(CGImage: thumbnail)
    }
}
Boisvert answered 13/4, 2017 at 9:48 Comment(1)
Please consider putting some of that linked content into your answer.Forepaw
A
2
func getThumbnailFrom(path: URL) -> UIImage? {

        do {
            let asset = AVURLAsset(url: path , options: nil)
            let imgGenerator = AVAssetImageGenerator(asset: asset)
            imgGenerator.appliesPreferredTrackTransform = true
            let timestamp = asset.duration
            print("Timestemp:   \(timestamp)")
            let cgImage = try imgGenerator.copyCGImage(at: timestamp, actualTime: nil)
            let thumbnail = UIImage(cgImage: cgImage)
            return thumbnail
            } catch let error {
            print("*** Error generating thumbnail: \(error.localizedDescription)")
            return nil
          }
        }

This Code is working.

Adrianadriana answered 17/6, 2019 at 6:57 Comment(0)
F
1

Swift 4 code for @Disha's answer:

let imageGenerator = AVAssetImageGenerator(asset: avAsset)
                let time = CMTime(seconds: seconds, preferredTimescale: 600)
                let times = [NSValue(time: time)]
                imageGenerator.generateCGImagesAsynchronously(forTimes: times, completionHandler: {
                    requestedTime, image, actualTime, result, error in
                    guard let cgImage = image else
                    {
                        print("No image!")
                        return
                    }
                    let uiImage = UIImage(cgImage: cgImage)
                    UIImageWriteToSavedPhotosAlbum(uiImage, nil, nil, nil);
                })
Footstep answered 22/1, 2019 at 20:7 Comment(0)
E
1

SWIFT 5.2 VERSION:

You can find my original post here, where I took inspiration from rozochkin's answer.

func getCurrentFrame() -> UIImage? {
    guard let player = self.player, let avPlayerAsset = player.currentItem?.asset else {return nil}
    let assetImageGenerator = AVAssetImageGenerator(asset: avPlayerAsset)
    assetImageGenerator.requestedTimeToleranceAfter = .zero
    assetImageGenerator.requestedTimeToleranceBefore = .zero
    assetImageGenerator.appliesPreferredTrackTransform = true
    let imageRef = try! assetImageGenerator.copyCGImage(at: player.currentTime(), actualTime: nil)
    let image = UIImage(cgImage: imageRef)
    return image
}

IMPORTANT NOTES:

requestedTimeToleranceAfter and requestedTimeToleranceBefore should be set to .zero, because, according to source code, "The actual time of the generated images [...] may differ from the requested time for efficiency".

appliesPreferredTrackTransform must be set to TRUE (default is FALSE), otherwise you get a bad-rotated frame. With this property set to TRUE you get what you really see in the player.

Easternmost answered 11/5, 2020 at 17:41 Comment(0)
M
0

You can try for Swift 5.0 and above

//cal in your code with your videoUrl
captureThumbnailFromVideo(url: videoURL, atTime: 5.0) { thumbnailImage in
                if let thumbnail = thumbnailImage {
                    self.myImageView.image = thumbnail
                } else {
    
                }
        } 
    
    func captureThumbnailFromVideo(url: URL, atTime time: Double, completion: @escaping (UIImage?) -> Void) {
            let asset = AVAsset(url: url)
            let imageGenerator = AVAssetImageGenerator(asset: asset)
            
            let time = CMTime(seconds: time, preferredTimescale: 600)
            
            do {
                let cgImage = try imageGenerator.copyCGImage(at: time, actualTime: nil)
                
                let thumbnailImage = UIImage(cgImage: cgImage)
                
                completion(thumbnailImage)
            } catch {
                print("Error: - \(error.localizedDescription)")
                completion(nil)
            }
        }
Moffit answered 31/10, 2023 at 12:38 Comment(0)
A
0

Starting from iOS 16.0+ macOS 13.0+ the prefered way is to use new concurrency method in order to not block code execution with synchronous copyCGImage(at:, actualTime:) call which take a time.

extension AVPlayer {
    func generateThumbnail(asset: AVAsset, time: CMTime) async throws -> UIImage {
        let imageGenerator = AVAssetImageGenerator(asset: asset)
        let thumbnail = try await imageGenerator.image(at: time).image
        return UIImage(cgImage: thumbnail)
    }
}

The example of usage to generate image for current time of the player item will be like so:

if let asset = mediaPlayer.currentItem?.asset {
      let image = try await mediaPlayer.generateThumbnail(asset: asset, time: mediaPlayer.currentTime())
      // do something with image here
}

Please note function func image(at time: CMTime) will return a tuple (image: CGImage, actualTime: CMTime). The reason why there is actuatlTime is because depending on input CMTime it may return image from a nearest I-frame of the video, which is faster than obtaining image from P-frame at specific time with very high preferredTimescale.

Aggravate answered 8/11, 2023 at 11:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.