6

I'm trying to retrieve/download the video/frames of Live Photo. As for the API documents there is a possible scenario which the Live Photos will be store at iCloud. In order to retrieve them as well you need to declare

let options = PHAssetResourceRequestOptions()
        options.networkAccessAllowed = true  

I'm trying to create a progress bar while the Live Photo is being download. According to the API, you need to declare this properties:

public var progressHandler: PHAssetResourceProgressHandler?

progress    
A floating-point value indicating the progress of the download. 
A value of 0.0 indicates that the download has just started,
and a value of 1.0 indicates the download is complete. 

I haven't found the correct way to retrieve those yet. Any suggestion?

Full Code :

 let assestResource = PHAssetResource.assetResourcesForAsset(asset)
 let options = PHAssetResourceRequestOptions()
 options.networkAccessAllowed = true
for assetRes in assestResource {
            print(assetRes.type.rawValue)
            if (assetRes.type == .PairedVideo) {
                print("imageTaken")
                manager.writeDataForAssetResource(assetRes, toFile: documentsURL,    options: options, completionHandler: { (error) -> Void in
                    if error == nil
                    {

                    }
                    else
                    {
                        print(error)
                    }
                })
Roi Mulia
  • 5,626
  • 11
  • 54
  • 105

2 Answers2

5

Yes, unfortunately, there is an Apple bug with iCloud downloads + PHAssetResourceManager. I get the following error regardless of the asset type:

Error: Missing resource download context

Instead, use PHImageManager. You need to have a unique request for each type of PHAsset:

- (void)downloadAsset:(PHAsset *)asset toURL:(NSURL *)url completion:(void (^)(void))completion
{
    if (asset.mediaType == PHAssetMediaTypeImage && (asset.mediaSubtypes & PHAssetMediaSubtypePhotoLive))
    {
        PHLivePhotoRequestOptions *options = [PHLivePhotoRequestOptions new];
        options.networkAccessAllowed = YES;
        [[PHImageManager defaultManager] requestLivePhotoForAsset:asset targetSize:CGSizeZero contentMode:PHImageContentModeAspectFill options:options resultHandler:^(PHLivePhoto * _Nullable livePhoto, NSDictionary * _Nullable info) {
            if ([info objectForKey:PHImageErrorKey] == nil)
            {
                NSData *livePhotoData = [NSKeyedArchiver archivedDataWithRootObject:livePhoto];
                if ([[NSFileManager defaultManager] createFileAtPath:url.path contents:livePhotoData attributes:nil])
                {
                    NSLog(@"downloaded live photo:%@", url.path);
                    completion();
                }
            }
        }];
    }
    else if (asset.mediaType == PHAssetMediaTypeImage)
    {
        PHImageRequestOptions *options = [PHImageRequestOptions new];
        options.networkAccessAllowed = YES;
        [[PHImageManager defaultManager] requestImageDataForAsset:asset options:options resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {
            if ([info objectForKey:PHImageErrorKey] == nil
                && [[NSFileManager defaultManager] createFileAtPath:url.path contents:imageData attributes:nil])
            {
                NSLog(@"downloaded photo:%@", url.path);
                completion();
            }
        }];
    }
    else if (asset.mediaType == PHAssetMediaTypeVideo)
    {
        PHVideoRequestOptions *options = [PHVideoRequestOptions new];
        options.networkAccessAllowed = YES;
        [[PHImageManager defaultManager] requestExportSessionForVideo:asset options:options exportPreset:AVAssetExportPresetHighestQuality resultHandler:^(AVAssetExportSession * _Nullable exportSession, NSDictionary * _Nullable info) {
            if ([info objectForKey:PHImageErrorKey] == nil)
            {
                exportSession.outputURL = url;

                NSArray<PHAssetResource *> *resources = [PHAssetResource assetResourcesForAsset:asset];
                for (PHAssetResource *resource in resources)
                {
                    exportSession.outputFileType = resource.uniformTypeIdentifier;
                    if (exportSession.outputFileType != nil)
                        break;
                }

                [exportSession exportAsynchronouslyWithCompletionHandler:^{
                    if (exportSession.status == AVAssetExportSessionStatusCompleted)
                    {
                        NSLog(@"downloaded video:%@", url.path);
                        completion();
                    }
                }];
            }
        }];
    }
}
owjhart
  • 66
  • 3
  • Amazing, Thanks! But does the @Photo section takes only the preview frame, right? – Roi Mulia Feb 14 '16 at 04:47
  • What do you mean by preview frame? – owjhart Feb 14 '16 at 05:08
  • Depends on your `options` argument. According to Apple documentation, for PHAssetMediaTypeImage: `If the version option is set to PHImageRequestOptionsVersionCurrent, Photos provides rendered image data, including the results of any edits that have been made to the asset content. Otherwise, Photos provides the originally captured image data for the asset.` – owjhart Feb 14 '16 at 05:34
  • I ran into a complication with persisting the PHLivePhoto asset since it does not hold the video data, it references the video by url. So I will use the PHAssetResourceManager for live photos regardless of whether iCloud is working or not. I wished PHLivePhoto worked like UIImage where it can easily be converted to/from NSData. – owjhart Feb 14 '16 at 09:31
  • Yap. They have very nice API tho over the PHAssetResourceManager. Very clean as well. I can get used to it :) – Roi Mulia Feb 14 '16 at 12:23
1

@owjhart: Great solution!!!

For those, who would like to grab life photos as movie files, I've adopted the code a bit :-)

- (void)downloadAsset:(PHAsset *)pAsset
           completion:(void (^)(BOOL pSucceeded, NSURL* pURL))pCompletion {
    NSParameterAssert(pCompletion);


    if ((PHAssetMediaTypeImage == pAsset.mediaType) &&
        (pAsset.mediaSubtypes & PHAssetMediaSubtypePhotoLive)) {

        NSArray*            assetResources = [PHAssetResource assetResourcesForAsset:pAsset];
        PHAssetResource*    assetResource = nil;
        for (PHAssetResource* asress in assetResources) {
            if (UTTypeConformsTo((__bridge CFStringRef)asress.uniformTypeIdentifier, kUTTypeMovie)) {
                assetResource = asress;
                break;
            }
        }

        if (assetResource) {
            __block NSMutableData*      assetData = NSMutableData.new;
            [PHAssetResourceManager.defaultManager requestDataForAssetResource:assetResource
                                                                       options:nil
                                                           dataReceivedHandler:^(NSData * _Nonnull pData) {
                                                               [assetData appendData:pData];
                                                           }
                                                             completionHandler:^(NSError * _Nullable pError) {
                                                                 //kUTTypeLivePhoto
                                                                 CFStringRef extension = UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)assetResource.uniformTypeIdentifier, kUTTagClassFilenameExtension);
                                                                 NSString*  filename = [NSString stringWithFormat:@"%@.%@", NSProcessInfo.processInfo.globallyUniqueString, extension];
                                                                 NSURL*     tempURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()
                                                                                               isDirectory:YES] URLByAppendingPathComponent:filename];
                                                                 if ((!pError) &&
                                                                     (assetData.length) &&
                                                                     ([NSFileManager.defaultManager createFileAtPath:tempURL.path
                                                                                                            contents:assetData
                                                                                                          attributes:nil])) {
                                                                     pCompletion(YES, tempURL);
                                                                 }
                                                                 else {
                                                                     pCompletion(NO, nil);
                                                                 }
                                                             }];
        }
        else {
            pCompletion(NO, nil);
        }
    }
    else if (PHAssetMediaTypeImage == pAsset.mediaType) {

        PHImageRequestOptions*  options = PHImageRequestOptions.new;
        options.networkAccessAllowed = YES;
        [PHImageManager.defaultManager requestImageDataForAsset:pAsset
                                                        options:options
                                                  resultHandler:^(NSData * _Nullable imageData, NSString * _Nullable dataUTI, UIImageOrientation orientation, NSDictionary * _Nullable info) {

            CFStringRef extension = UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)dataUTI, kUTTagClassFilenameExtension);
            NSString*   filename = [NSString stringWithFormat:@"%@.%@", NSProcessInfo.processInfo.globallyUniqueString, (__bridge NSString*)extension];
            NSURL*      tempURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()
                                          isDirectory:YES] URLByAppendingPathComponent:filename];
            if ((nil == [info objectForKey:PHImageErrorKey]) &&
                ([NSFileManager.defaultManager createFileAtPath:tempURL.path 
                                                       contents:imageData
                                                     attributes:nil])) {
                NSLog(@"downloaded photo:%@", tempURL.path);
                pCompletion(YES, tempURL);
            }
            else {
                pCompletion(NO, nil);
            }
        }];
    }
    else if (PHAssetMediaTypeVideo == pAsset.mediaType) {

        PHVideoRequestOptions*  options = PHVideoRequestOptions.new;
        options.networkAccessAllowed = YES;
        [PHImageManager.defaultManager requestExportSessionForVideo:pAsset
                                                            options:options
                                                       exportPreset:AVAssetExportPresetHighestQuality
                                                      resultHandler:^(AVAssetExportSession * _Nullable exportSession, NSDictionary * _Nullable info) {
            if (nil == [info objectForKey:PHImageErrorKey]) {
                NSArray<PHAssetResource*>*  resources = [PHAssetResource assetResourcesForAsset:pAsset];
                for (PHAssetResource* resource in resources) {
                    exportSession.outputFileType = resource.uniformTypeIdentifier;
                    if (nil != exportSession.outputFileType) {
                        break;
                    }
                }
                CFStringRef extension = UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)exportSession.outputFileType, kUTTagClassFilenameExtension);
                NSString*   filename = [NSString stringWithFormat:@"%@.%@", NSProcessInfo.processInfo.globallyUniqueString, (__bridge NSString*)extension];
                NSURL*      tempURL = [[NSURL fileURLWithPath:NSTemporaryDirectory()
                                              isDirectory:YES] URLByAppendingPathComponent:filename];
                exportSession.outputURL = tempURL;


                [exportSession exportAsynchronouslyWithCompletionHandler:^{
                    if (AVAssetExportSessionStatusCompleted == exportSession.status) {
                        NSLog(@"downloaded video:%@", tempURL.path);
                        pCompletion(YES, tempURL);
                    }
                    else {
                        pCompletion(NO, nil);
                    }
                }];
            }
            else {
                pCompletion(NO, nil);
            }
        }];
    }
    else {
        pCompletion(NO, nil);
    }
}
LaborEtArs
  • 1,938
  • 23
  • 27