GuillermoMP
GuillermoMP

Reputation: 1764

How to access NSData/NSURL of slow motion videos using PhotoKit

Working with new Photo framework, I can access the NSData of PHAssets using requestImageDataForAsset. I can also access the file URL using the PHImageFileURLKey of the returned info NSDictionary.

[[PHImageManager defaultManager] requestImageDataForAsset:asset options:nil resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {

                //imageData contains the correct data for images and videos
                NSLog(@"info - %@", info);
                NSURL* fileURL = [info objectForKey:@"PHImageFileURLKey"];
}];

This works fine for images and normal videos.

However, when the asset is a PHAssetMediaSubtypeVideoHighFrameRate (slow motion video), the returned data corresponds to a JPG file containing the first frame of the video (both the NSData, the dataUTI and the info dictionary point to the same jpg file). As example, this is the URL and the dataUTI returned for a slow motion video:

PHImageFileURLKey = "file:///var/mobile/Media/PhotoData/Metadata/DCIM/100APPLE/IMG_0642.JPG"; PHImageFileUTIKey = "public.jpeg";

Why is this happening? How can i access the NSData/NSURL of the slow motion video instead of this JPG preview?

Upvotes: 8

Views: 8337

Answers (5)

yoAlex5
yoAlex5

Reputation: 34421

Swift Play Slow-Motion video from PHAsset

private func handleAssetVideo(asset: PHAsset) {
    PHCachingImageManager().requestAVAsset(forVideo: asset, options: nil) { avAsset, audioMix, info in
        guard let avAsset = avAsset else {
            return
        }
        if let avUrlAsset = avAsset as? AVURLAsset {
            //usual video
            let videoUrl = avUrlAsset.url
            
            DispatchQueue.main.async {
                //use videoUrl
            }
        } else if let avComposition = avAsset as? AVComposition {
            //slow-mo video
            guard avComposition.tracks.count > 1 else {
                return
            }

            guard let exportSession = AVAssetExportSession(asset: avComposition, presetName: AVAssetExportPresetHighestQuality) else {
                return
            }
            
            let exportPath = NSTemporaryDirectory().appendingFormat("temp.mp4")
            exportSession.outputURL = NSURL.fileURL(withPath: exportPath)
            exportSession.outputFileType = AVFileType.mp4
            
            exportSession.exportAsynchronously {
                guard let slowMotionUrl = exportSession.outputURL else {
                    return
                }

                DispatchQueue.main.sync {
                    //use slowMotionUrl
                }
            }
        } else {
            print("asset is not video")
        }
    }
}   

Optimizing for Slow Motion Video in iOS

Upvotes: 3

sahiljain
sahiljain

Reputation: 2374

Following code snippet for Swift 3/4

PHImageManager.default().requestAVAsset(forVideo: asset, 
                                         options: nil, 
                                         resultHandler: { (asset, _, _) in

            // AVAsset has two sub classes: AVComposition and AVAssetURL
            // AVComposition for slow mo vid
            // AVAssetURL for normal videos

            // For slow motion video checking for AVCompostion
            // Creating an exporter to write the video into local file path and using the same to play/upload

            if asset!.isKind(of: AVComposition.self){

                let avCompositionAsset = asset as! AVComposition

                if avCompositionAsset.tracks.count > 1{

                    let exporter = AVAssetExportSession(asset: avCompositionAsset, presetName: AVAssetExportPresetHighestQuality)
                    exporter!.outputURL = self.fetchOutputURL()
                    exporter!.outputFileType = AVFileTypeMPEG4
                    exporter!.shouldOptimizeForNetworkUse = true

                    exporter!.exportAsynchronously {
                        DispatchQueue.main.sync {
                          // Use this url for uploading or playing a video
                           let url = exporter!.outputURL
                        }
                    }
                }
            }else{

                // Normal video, are stored as AVAssetURL

                let url = (asset as! AVURLAsset).url
            }
        })

// Fetch local path

 func fetchOutputURL() -> URL{
     let documentDirectory = getDocumentsDirectory() as NSString
     let path = documentDirectory.appendingPathComponent("test.mp4")
     return URL(fileURLWithPath:path)
 }

Upvotes: 7

Utsav Dusad
Utsav Dusad

Reputation: 2199

It is important to note that Slow motion videos are of type AVComposition not AVURLAsset. An AVComposition object combines media data from multiple sources together.

Exporting a slow motion video

To achieve this, I basically went through a three-step process:

  1. Create an output URL for the video
  2. Configure an export session
  3. Export the video and grab the URL!

PHVideoRequestOptions *options = [PHVideoRequestOptions new];
        options.networkAccessAllowed = YES;
        [[PHImageManager defaultManager] requestAVAssetForVideo:asset options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info) {
            if(([asset isKindOfClass:[AVComposition class]] && ((AVComposition *)asset).tracks.count == 2)){
                //slow motion videos. See Here: https://overflow.buffer.com/2016/02/29/slow-motion-video-ios/

                //Output URL of the slow motion file.
                NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
                NSString *documentsDirectory = paths.firstObject;
                NSString *myPathDocs =  [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"mergeSlowMoVideo-%d.mov",arc4random() % 1000]];
                NSURL *url = [NSURL fileURLWithPath:myPathDocs];

                //Begin slow mo video export
                AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetHighestQuality];
                exporter.outputURL = url;
                exporter.outputFileType = AVFileTypeQuickTimeMovie;
                exporter.shouldOptimizeForNetworkUse = YES;

                [exporter exportAsynchronouslyWithCompletionHandler:^{
                    dispatch_async(dispatch_get_main_queue(), ^{
                        if (exporter.status == AVAssetExportSessionStatusCompleted) {
                            NSURL *URL = exporter.outputURL;
                            self.filePath=URL.absoluteString;



                            // NSData *videoData = [NSData dataWithContentsOfURL:URL];
                            //
                            //// Upload
                            //[self uploadSelectedVideo:video data:videoData];
                        }
                    });
                }];


            } 
        }];

Please see this wonderful blog for slow motion videos in iOS.

Upvotes: 13

guhan0
guhan0

Reputation: 676

//video slo-mo

PHVideoRequestOptions *options=[[PHVideoRequestOptions alloc]init];
options.version=PHVideoRequestOptionsVersionOriginal;

Request AVAsset from PHImageManager

[[PHImageManager defaultManager] requestAVAssetForVideo:videoAsset options:options resultHandler:^(AVAsset *asset, AVAudioMix *audioMix, NSDictionary *info)
 {         
     if ([asset isKindOfClass:[AVURLAsset class]])
     {
         // use URL to get file content

         NSURL *URL = [(AVURLAsset *)asset URL];
         NSData *videoData=[NSData dataWithContentsOfURL:URL];
         NSNumber *fileSizeValue = nil;
         [URL getResourceValue:&fileSizeValue forKey:NSURLFileSizeKey error:nil];
      }
}

Upvotes: 1

GuillermoMP
GuillermoMP

Reputation: 1764

After going nuts and testing every single option I found the problem.

The responsable of returning JPG images for slow motion videos is the default PHImageRequestOptionsVersionCurrent value for the PHImageRequestOptions.version property.

Simply assign the version to PHImageRequestOptionsVersionUnadjusted or PHImageRequestOptionsVersionOriginal will return the original slow motion video.

PHImageRequestOptions * imageRequestOptions = [[PHImageRequestOptions alloc] init];

imageRequestOptions.version = PHImageRequestOptionsVersionUnadjusted;
// or 
imageRequestOptions.version = PHImageRequestOptionsVersionOriginal;

I consider this as an unexpected behaviour, since i am not expecting that the "current" version of a slow motion video is a still image (maybe a video with the slow motion effect applied, but not a photo).

Hope this is usefull to someone.

Upvotes: 19

Related Questions