iOS 如何从实况照片中获取视频

How to Get Video from a Live Photo in iOS

我正在努力弄清楚,但找不到任何有用的信息。 我只找到了这个:

PHAssetResourceManager.defaultManager().writeDataForAssetResource(assetRes, 
toFile: fileURL, options: nil, completionHandler: 
{
     // Video file has been written to path specified via fileURL
}

但我很惭愧地说我不知道​​怎么玩。 我创建了一个 UIImagePickerController 并从相机胶卷加载了一个图像。

问题有点乱

首先,如果你想选择现场照片并现场播放photo.I建议你使用Photos Framework instead of UIImagePickerController. This way you can fetch the asset and have more control. Then you can play the live photo as mov or the muted version with PHLivePhotoView,将startPlayback(with:)设置为hintfull.

您可以参考这里的代码:

  • a github repo LivePreview 向您展示如何 select 实时照片并播放它。

其次,如果你想把live photo转换成mov,你粘贴的代码就可以了,如果你想直接播放mov,你可能需要使用AVPlayer

另外,WWDC 提供 Example app using Photos framework

使用此代码从实时照片中获取视频:

- (void)videoUrlForLivePhotoAsset:(PHAsset*)asset withCompletionBlock:(void (^)(NSURL* url))completionBlock{
    if([asset isKindOfClass:[PHAsset class]]){
        NSString* identifier = [(PHAsset*)asset localIdentifier];
        NSString* filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.mov",[NSString stringWithFormat:@"%.0f",[[NSDate date] timeIntervalSince1970]]]];
        NSURL *fileUrl = [NSURL fileURLWithPath:filePath];

        PHLivePhotoRequestOptions* options = [PHLivePhotoRequestOptions new];
        options.deliveryMode = PHImageRequestOptionsDeliveryModeFastFormat;
        options.networkAccessAllowed = YES;
        [[PHImageManager defaultManager] requestLivePhotoForAsset:asset targetSize:[UIScreen mainScreen].bounds.size contentMode:PHImageContentModeDefault options:options resultHandler:^(PHLivePhoto * _Nullable livePhoto, NSDictionary * _Nullable info) {
            if(livePhoto){
                NSArray* assetResources = [PHAssetResource assetResourcesForLivePhoto:livePhoto];
                PHAssetResource* videoResource = nil;
                for(PHAssetResource* resource in assetResources){
                    if (resource.type == PHAssetResourceTypePairedVideo) {
                        videoResource = resource;
                        break;
                    }
                }
                if(videoResource){
                    [[PHAssetResourceManager defaultManager] writeDataForAssetResource:videoResource toFile:fileUrl options:nil completionHandler:^(NSError * _Nullable error) {
                        if(!error){
                            completionBlock(fileUrl);
                        }else{
                            completionBlock(nil);
                        }
                    }];
                }else{
                    completionBlock(nil);
                }
            }else{
                completionBlock(nil);
            }
        }];
    }else{
        completionBlock(nil);
    }
}

基本上您需要做的是首先需要从您的 PHAsset 中获取 PHLivePhoto 对象。之后,你还得遍历你的live photo里面的所有asset资源,看是不是PHAssetResourceTypePairedVideo类型的。

如果是,您就收到了视频。现在您需要像我在这里所做的那样将它保存到某个临时目录中,并将此文件用于您可能有的任何目的。

要播放此视频,您可以使用以下代码:

NSURL *videoURL = [NSURL fileURLWithPath:fileUrl];
AVPlayer *player = [AVPlayer playerWithURL:videoURL];
AVPlayerViewController *playerViewController = [AVPlayerViewController new];
playerViewController.player = player;
[self presentViewController:playerViewController animated:YES completion:nil];

如果您需要任何说明,请随时询问。

P.S.- 我对此方法进行了一些更改以删除应用程序代码的依赖性,因此上述代码未经测试,但我觉得它应该可以工作不出所料。

Swift 4 版本

import Photos
import MobileCoreServices

// <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@IBAction func showImagePicker(sender: UIButton) {
    let picker = UIImagePickerController()
    picker.delegate = self;
    picker.allowsEditing = false;
    picker.sourceType = .photoLibrary;
    picker.mediaTypes = [kUTTypeLivePhoto as String, kUTTypeImage as String];

    present(picker, animated: true, completion: nil);
}

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    guard
        let livePhoto = info[UIImagePickerControllerLivePhoto] as? PHLivePhoto,
        let photoDir = generateFolderForLivePhotoResources()
        else {
            return;
    }

    let assetResources = PHAssetResource.assetResources(for: livePhoto)
    for resource in assetResources {

        // SAVE FROM BUFFER
//            let buffer = NSMutableData()
//            PHAssetResourceManager.default().requestData(for: resource, options: nil, dataReceivedHandler: { (chunk) in
//                buffer.append(chunk)
//            }, completionHandler: {[weak self] error in
//                self?.saveAssetResource(resource: resource, inDirectory: photoDir, buffer: buffer, maybeError: error)
//            })

        // SAVE DIRECTLY
        saveAssetResource(resource: resource, inDirectory: photoDir, buffer: nil, maybeError: nil)
    }

    picker.dismiss(animated: true) {}
}

func saveAssetResource(
    resource: PHAssetResource,
    inDirectory: NSURL,
    buffer: NSMutableData?, maybeError: Error?
    ) -> Void {
    guard maybeError == nil else {
        print("Could not request data for resource: \(resource), error: \(String(describing: maybeError))")
        return
    }

    let maybeExt = UTTypeCopyPreferredTagWithClass(
        resource.uniformTypeIdentifier as CFString,
        kUTTagClassFilenameExtension
        )?.takeRetainedValue()

    guard let ext = maybeExt else {
        return
    }

    guard var fileUrl = inDirectory.appendingPathComponent(NSUUID().uuidString) else {
        print("file url error")
        return
    }

    fileUrl = fileUrl.appendingPathExtension(ext as String)

    if let buffer = buffer, buffer.write(to: fileUrl, atomically: true) {
        print("Saved resource form buffer \(resource) to filepath \(String(describing: fileUrl))")
    } else {
        PHAssetResourceManager.default().writeData(for: resource, toFile: fileUrl, options: nil) { (error) in
            print("Saved resource directly \(resource) to filepath \(String(describing: fileUrl))")
        }
    }
}

func generateFolderForLivePhotoResources() -> NSURL? {
    let photoDir = NSURL(
        // NB: Files in NSTemporaryDirectory() are automatically cleaned up by the OS
        fileURLWithPath: NSTemporaryDirectory(),
        isDirectory: true
        ).appendingPathComponent(NSUUID().uuidString)

    let fileManager = FileManager()
    // we need to specify type as ()? as otherwise the compiler generates a warning
    let success : ()? = try? fileManager.createDirectory(
        at: photoDir!,
        withIntermediateDirectories: true,
        attributes: nil
    )

    return success != nil ? photoDir! as NSURL : nil
}

这里有深度教程Live Photo API on iOS

Swift 5

func videoUrlForLivePhotoAsset(asset: PHAsset, completionHandler: @escaping (_ result: URL?) -> Void) {
            
    print("videoUrlForLivePhotoAsset: \(asset)")
    
    let options : PHLivePhotoRequestOptions = PHLivePhotoRequestOptions.init()
    
    options.deliveryMode = .fastFormat
    options.isNetworkAccessAllowed = true
    
    PHImageManager.default().requestLivePhoto(for: asset, targetSize: UIScreen.main.bounds.size, contentMode: .default, options: options) { (livePhoto, info) in
        
        if livePhoto != nil {
            
            let assetResources : [PHAssetResource] = PHAssetResource.assetResources(for: livePhoto!)
            
            var videoResource : PHAssetResource?
            
            for resource in assetResources {
                
                if resource.type == .pairedVideo {
                    
                    videoResource = resource

                    break
                    
                }
                
            }
            
            guard let photoDir = self.generateFolderForLivePhotoResources() else {

                return

            }

            
            print("videoResource: \(videoResource)")
            
            if videoResource != nil {
                
                self.saveAssetResource(resource: videoResource!, inDirectory: photoDir, buffer: nil, maybeError: nil) { (fileUrl) in
                    
                    completionHandler(fileUrl)

                }
                                                            
            }
            
        } else {
            
            completionHandler(nil)

        }
        
    }
    
}

func saveAssetResource(
    resource: PHAssetResource,
    inDirectory: NSURL,
    buffer: NSMutableData?, maybeError: Error?, completionHandler: @escaping (_ result: URL?) -> Void) {
    
    guard maybeError == nil else {
        print("Could not request data for resource: \(resource), error: \(String(describing: maybeError))")
        return
    }

    let maybeExt = UTTypeCopyPreferredTagWithClass(
        resource.uniformTypeIdentifier as CFString,
        kUTTagClassFilenameExtension
        )?.takeRetainedValue()

    guard let ext = maybeExt else {
        return
    }

    guard var fileUrl = inDirectory.appendingPathComponent(NSUUID().uuidString) else {
        print("file url error")
        return
    }

    fileUrl = fileUrl.appendingPathExtension(ext as String)

    if let buffer = buffer, buffer.write(to: fileUrl, atomically: true) {
        
        print("Saved resource form buffer \(resource) to filepath \(String(describing: fileUrl))")

        completionHandler(fileUrl)
        
    } else {

        PHAssetResourceManager.default().writeData(for: resource, toFile: fileUrl, options: nil) { (error) in

            print("Saved resource directly \(resource) to filepath \(String(describing: fileUrl))")

            if error == nil {
                
                completionHandler(fileUrl)

            } else {

                completionHandler(nil)

            }

        }

    }
    
}

func generateFolderForLivePhotoResources() -> NSURL? {
    
    let photoDir = NSURL(
        // NB: Files in NSTemporaryDirectory() are automatically cleaned up by the OS
        fileURLWithPath: NSTemporaryDirectory(),
        isDirectory: true
        ).appendingPathComponent(NSUUID().uuidString)

    let fileManager = FileManager()

    // we need to specify type as ()? as otherwise the compiler generates a warning

    let success : ()? = try? fileManager.createDirectory(
        at: photoDir!,
        withIntermediateDirectories: true,
        attributes: nil
    )

    return success != nil ? photoDir! as NSURL : nil
    
}

调用以下内容:

let asset = PHAsset.init()
                    
self.videoUrlForLivePhotoAsset(asset: asset!) { (url) in
                        
    print("url: \(url)")

}

注意:您需要清理 Temp 和 Documents 目录,并删除文件。

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {

 let phAsset = info[.phAsset] as? PHAsset
 imagePickerController.dismiss(animated: true, completion: nil)
 let style = phAsset?.playbackStyle
  if(style != .livePhoto) {
         print("This is not a live photo")
         return
  }
  let filePath = NSTemporaryDirectory() + String(format: "%.0f", NSDate().timeIntervalSince1970) + "_.mov"
  let fileURL = NSURL(fileURLWithPath: filePath)
  let options = PHLivePhotoRequestOptions()
  options.deliveryMode = .fastFormat
  options.isNetworkAccessAllowed = true

PHImageManager.default().requestLivePhoto(for: phAsset!, targetSize: CGSize(width: 1920, height: 1080), contentMode: PHImageContentMode.default, options: options) { livePhoto, info in
        if((livePhoto) != nil) {
           let assetResources = PHAssetResource.assetResources(for: livePhoto!)
           var videoResource : PHAssetResource?
           for resources in assetResources {
               if(resources.type == .pairedVideo) {
                    videoResource = resources
                    break
               }
            }
            guard let videoResource = videoResource else {
                fatalError("video resource is nil")
            }
            PHAssetResourceManager.default().writeData(for: videoResource, toFile: fileURL as URL, options: nil) { error in                   
                let avAsset : AVAsset = AVAsset(url: fileURL as URL)
                 DispatchQueue.main.async { [self] in

                        // Whatever you do using fileURL or avAsset.

                 }
             }
                
         }
    }

}