5

I am trying to get the first thumbnail from a video.

I tried the following:

ONE

AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:_moviePath] options:nil];
AVAssetImageGenerator *gen = [[AVAssetImageGenerator alloc] initWithAsset:asset];
gen.appliesPreferredTrackTransform = YES;
CMTime time = CMTimeMakeWithSeconds(0.0, 600);
NSError *error = nil;
CMTime actualTime;

CGImageRef image = [gen copyCGImageAtTime:time actualTime:&actualTime error:&error];
UIImage *thumb = [[UIImage alloc] initWithCGImage:image];
CGImageRelease(image);
NSLog(@"the error is %@: image is %@: url is %@: ",error,_mainThumbnail,[NSURL fileURLWithPath:_moviePath] );

However, my log is:

the error is (null): image is (null): 
url is file:///private/var/mobile/Applications/D1293BDC-EA7E-4AC7-AD3C-1BA3548F37D6/tmp/trim.6F0C4631-E5E8-43CD-BF32-9B8F09D4ACF1.MOV: 

TWO

AVURLAsset *asset=[[AVURLAsset alloc] initWithURL:[NSURL fileURLWithPath:_moviePath] options:nil];
    AVAssetImageGenerator *generator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
    generator.appliesPreferredTrackTransform=TRUE;

    CMTime thumbTime = CMTimeMakeWithSeconds(0,30);

    AVAssetImageGeneratorCompletionHandler handler = ^(CMTime requestedTime, CGImageRef im, CMTime actualTime, AVAssetImageGeneratorResult result, NSError *error){
        if (result != AVAssetImageGeneratorSucceeded) {
            NSLog(@"couldn't generate thumbnail, error:%@", error);
        }
        _mainThumbnail.image = [UIImage imageWithCGImage:im];
        NSLog(@"thumbnails %@ ",_mainThumbnail.image);
    };

    CGSize maxSize = CGSizeMake(320, 180);
    generator.maximumSize = maxSize;
    [generator generateCGImagesAsynchronouslyForTimes:[NSArray arrayWithObject:[NSValue valueWithCMTime:thumbTime]] completionHandler:handler];

The log thumnails null.

Why is the image null? I looked in other forums, and they suggested to use fileURLWithPath -which I am already using.

I am using ios7

Dejell
  • 13,947
  • 40
  • 146
  • 229

5 Answers5

18

You can try this method. It takes the first frame of the video at the given URL and returns it as UIImage.

- (UIImage *)thumbnailFromVideoAtURL:(NSURL *)url
{
    AVAsset *asset = [AVAsset assetWithURL:url];

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

    //  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;
}

The method does not check for errors as my code using it is OK with just nil returned if anything fails.


Edit: Clarification of CMTime values

CMTime has

  • timescale - think about this as a FPS (frames per second) of the video (25 FPS -> 1s of the video has 25 video frames)
  • value - identification of concrete frame. In the code above, this says which frame you want to take thumbnail of.

For calculation of frame at exact time, you have to make this calculation:

value = timescale * seconds_to_skip

which is the equivalent to

"Frame of the video to take" = "FPS" * "Number of seconds to skip"

If you want to take for example 1st frame of 2nd second of the video, you want in fact to skip 1st second of the video. So you would use:

CMTime thumbnailTime = [asset duration];
thumbnailTime.value = thumbnailTime.timescale * 1;

Another look: you want to skip first 1s of the video and than take a thumbnail. I.e. on 25 FPS, you want to skip 25 frames. So CMTime.value = 25 (skip 25 frames).

Lukas Kukacka
  • 7,604
  • 2
  • 25
  • 50
  • what if I want the second one? shall I try thumbnailTime.value = 1? – Dejell Oct 09 '13 at 13:54
  • Do you need thumbnail of 2nd second or 2nd frame of the video? Why would you need 2nd frame? The difference between the 1st and 2nd one is about 0.04s @ 25FPS :-) However: CMTime defines video time at which the thumbnail should be taken, not the exact number of frame. And my code uses simple premise that 1st frame is at time 0 ;-) So in order to get specific frame, you would probably have to calculate FPS of the video etc, but that is a different discussion – Lukas Kukacka Oct 09 '13 at 14:09
  • I mean the 2nd second . how canI get it? – Dejell Oct 09 '13 at 14:16
  • Please take a look at this SO question: http://stackoverflow.com/questions/4001755/trying-to-understand-cmtime-and-cmtimemake It covers this topic in more detail than I can give you in comments. Basically, `CMTime` docs says "value/timescale = seconds" ... so if you set `thumbnailTime.value = thumbnailTime.timescale * 1` (where 1 is number second of video you'd like to take thumbnail of), you should have approximately time of 1st frame of 2nd second of the video (time 00h00m01s) – Lukas Kukacka Oct 09 '13 at 14:29
  • do you mean thumbnailTime.value = 1 in order to get the 2nd? – Dejell Oct 09 '13 at 17:04
  • Have you took a look at the answer I sent you a link to in a previous comment? You have to set thumbnailTime.value = thumbnailTime.timescale in order to get the 1st frame of 2nd second of the video. Exactly as I posted in a previous commend. Please read the discussion to the question I sent you link to. Its explains everything you need to know about this – Lukas Kukacka Oct 09 '13 at 18:46
  • I have updated my answer. If you really don't understand this simple multiplication relation of the values, I'm sorry, but I don't see any other way how to explain you as this discussion seems like a wasting of my time. If you really don't understand, just copy&paste the code from answer's edit – Lukas Kukacka Oct 10 '13 at 12:11
  • Thanks I understand it now. How can I know what is the FPS of the video? I believe that some are more than 25 e.g. 29 as far as I know the video industry – Dejell Oct 10 '13 at 16:32
  • `CMTime thumbnailTime = [asset duration]` returns duration of the video as the `CMTime`. It means you get FPS and total frames count (duration) of the video. Then you just reset `value` to your time, keeping the same `timescale` (FPS) – Lukas Kukacka Oct 11 '13 at 06:47
  • I checked it again, and the image appears landscape left rotated. what is the reason? – Dejell Oct 14 '13 at 19:13
  • The reason is probably the orientation in which the video was recorded. Video contains metadata specifying orientation in which it was recorded so the players can handle the video accordingly. That you see the video in Camera Roll always in a proper orientation is just a trick of the controller / player – Lukas Kukacka Oct 15 '13 at 06:59
  • 1
    I found the solution here http://stackoverflow.com/questions/5347800/avassetimagegenerator-provides-images-rotated – Dejell Oct 15 '13 at 08:27
  • Is it possible to cache this Thumbnail ? – Chlebta Sep 07 '15 at 15:35
2

Just in case someone made the same mistake as I did:

Check out if the URL is generated using appropriate method. You shouldn't get counfused for local and web URLs of the file.

In case of local video file, use below code:

NSURL *movieURL = [NSURL fileURLWithPath:_moviePath];
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:movieURL options:nil];

In case of video file from internet, use below code:

NSURL *movieURL = [NSURL URLWithString:_moviePath];
AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:movieURL options:nil];
Shamsiddin Saidov
  • 2,281
  • 4
  • 23
  • 35
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.

EmmettBrown88
  • 148
  • 1
  • 2
  • 13
  • 1
    Thank you! Was requesting frame at 3.0 seconds and always getting the frame at 0 seconds. These settings solved this issue. requestedTimeToleranceAfter = .zero requestedTimeToleranceBefore = .zero – Ozgur Sahin Aug 25 '22 at 13:27
0

I successfully use this snippet, to get a screenshot from video:

- (UIImage *)currentItemScreenShot
{
    AVURLAsset *asset = (AVURLAsset *)playerItem_.asset;

    AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];

    imageGenerator.appliesPreferredTrackTransform = YES;

    CGImageRef thumb = [imageGenerator 
    copyCGImageAtTime:CMTimeMakeWithSeconds(0, 1.0)
    actualTime:NULL
    error:NULL];

    UIImage *image = [UIImage imageWithCGImage:thumb];

    CGImageRelease(thumb);

    [imageGenerator release];

    return image;
}

But in my case - I am doing it two seconds after video was successfully imported in application. ( It had problems with picture orientation, sometimes returned no image or half image if I did it once video was imported).

Guntis Treulands
  • 4,764
  • 2
  • 50
  • 72
  • what do you mean by successfully imported? – Dejell Oct 09 '13 at 13:55
  • Well - In my case I film a video or choose from library, then this video is being imported in application (and saved in application cache directory) - if it is successfully saved - then it is successfully imported. There could something go wrong - video corrupted, or full memory or any other problem. – Guntis Treulands Oct 09 '13 at 14:38
0

Swift version of the accepted answer @Lukas Kukacka answer:

func thumbnailFromVideoAtURL(url: URL) -> UIImage?{

        let asset = AVAsset(url: url)

        //  Get thumbnail at the very start of the video
        var thumbnailTime: CMTime = asset.duration
        thumbnailTime.value = 0

        //  Get image from the video at the given time
        let imageGenerator = AVAssetImageGenerator(asset: asset)
        imageGenerator.appliesPreferredTrackTransform = true // this rotates the image to the correct position the camera took it in

        do{
            let imageRef = try imageGenerator.copyCGImage(at: thumbnailTime, actualTime: nil)
            return UIImage(cgImage: imageRef)

        }catch let err as NSError{
            print(err.localizedDescription)
        }

        return nil
}

Bonus

Under let imageGenerator = AVAssetImageGenerator(asset: asset) I added:

imageGenerator.appliesPreferredTrackTransform = true

I had a problem where I would record in portrait, get the thumbnail image and it would get returned in landscape. This answer solved that problem.

Lance Samaria
  • 17,576
  • 18
  • 108
  • 256