使用新的 PHPhotoLibrary 保存 exif 元数据

Posted

技术标签:

【中文标题】使用新的 PHPhotoLibrary 保存 exif 元数据【英文标题】:Save the exif metadata using the new PHPhotoLibrary 【发布时间】:2016-03-02 21:32:58 【问题描述】:

我目前正在使用AVCaptureStillImageOutput 获取全分辨率图片。我还可以使用以下代码获取 exif 元数据:

[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)
      

         CFDictionaryRef metaDict = CMCopyDictionaryOfAttachments(NULL, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);
         CFMutableDictionaryRef mutableDict = CFDictionaryCreateMutableCopy(NULL, 0, metaDict);

         NSLog(@"test attachments %@", mutableDict);

         // set the dictionary back to the buffer
         CMSetAttachments(imageSampleBuffer, mutableDict, kCMAttachmentMode_ShouldPropagate);

         NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];

         UIImage *image = [[UIImage alloc] initWithData:imageData];    
         [self.delegate frameReadyToSave:image withExifAttachments: mutableDict];
     ];

元数据位于mutableDict 变量中。现在,我想将这张图片与元数据一起保存在两个不同的地方。我想将它保存在磁盘上的应用程序文件夹和照片库中。

现在,我尝试使用以下方法以另一种方法保存图像(您看到的图像变量是自定义对象):

NSData* imageData = UIImageJPEGRepresentation(image.image, 1.0f);

[imageData writeToFile:image.filePath atomically:YES];

UIImageWriteToSavedPhotosAlbum(image.image, nil, nil, nil);

现在,图像已正确保存,但不包含任何 Exif 元数据。

根据我的阅读,我需要使用 phphotoLibrary 来执行此操作,但文档对此并不太健谈。这是我发现的:

[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^
    PHAssetChangeRequest *createAssetRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image.image];      

 completionHandler:nil];

但是如何保存元数据呢?

【问题讨论】:

【参考方案1】:

我建议您使用 ImageIO 来完成:

-(void)frameReadyToSave:(UIImage*)image withExifAttachments:(NSMutableDictionary*)mutableDict

    NSData* imageData = UIImageJPEGRepresentation(image, 1.0f);
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
    __block NSURL* tmpURL = [NSURL fileURLWithPath:@"example.jpg"]; //modify to your needs
    CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef) tmpURL, kUTTypeJPEG, 1, NULL);
    CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef) mutableDict);
    CGImageDestinationFinalize(destination);
    CFRelease(source);
    CFRelease(destination);
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^
        [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:tmpURL];
       completionHandler:^(BOOL success, NSError *error) 
        //cleanup the tmp file after import, if needed
    ];

【讨论】:

这会将元数据保存在 JPEG 的 EXIF 部分,但不会显示在照片中(在 Mac 上)。 [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:tmpURL] 应在导入时读出照片的 EXIF 元数据数据,并使用必要的数据(记录日期、位置数据)填充 iCloud 照片库数据库。照片是否以正确的时间戳出现在 ios 照片应用程序中? 确实,它适用于日期和位置。但是,它不适用于标题、描述和关键字。至少我尝试过的 EXIF 字段没有。 标题、描述和关键字不是 iOS 照片库数据库的一部分(在 iOS 上不可编辑 - 无论是在照片应用程序中还是作为 PHAsset 的属性)。使用 Mac 上的照片应用程序可以编辑这些属性是正确的。标题、描述和关键字似乎还没有通过 iCloud 同步。 有人找到解决方案了吗?如果是这样,在将图像保存到照片库之前,有什么方法可以将图像及其新元数据保存为 uiimage?谢谢【参考方案2】:

使用它来将元数据合并到图像数据中并将其保存到照片库:

func saveImageData(data: Data, metadata: NSDictionary? = nil, album: PHAssetCollection, completion:((PHAsset?)->())? = nil) 
    var placeholder: PHObjectPlaceholder?

    PHPhotoLibrary.shared().performChanges(
        var changeRequest: PHAssetChangeRequest

        if let metadata = metadata 
            let newImageData = UIImage.mergeImageData(imageData: data, with: metadata)
            changeRequest = PHAssetCreationRequest.forAsset()
            (changeRequest as! PHAssetCreationRequest).addResource(with: .photo, data: newImageData as Data, options: nil)
        
        else 
            changeRequest = PHAssetChangeRequest.creationRequestForAsset(from: UIImage(data: data)!)
        

        guard let albumChangeRequest = PHAssetCollectionChangeRequest(for: album),
            let photoPlaceholder = changeRequest.placeholderForCreatedAsset else  return 

        placeholder = photoPlaceholder
        let fastEnumeration = NSArray(array: [photoPlaceholder] as [PHObjectPlaceholder])
        albumChangeRequest.addAssets(fastEnumeration)
    , completionHandler:  success, error in
        guard let placeholder = placeholder else 
            completion?(nil)
            return
        

        if success 
            let assets:PHFetchResult<PHAsset> =  PHAsset.fetchAssets(withLocalIdentifiers: [placeholder.localIdentifier], options: nil)
            let asset:PHAsset? = assets.firstObject
            completion?(asset)
        
        else 
            completion?(nil)
        
    )


func mergeImageData(imageData: Data, with metadata: NSDictionary) -> Data 
    let source: CGImageSource = CGImageSourceCreateWithData(imageData as NSData, nil)!
    let UTI: CFString = CGImageSourceGetType(source)!
    let newImageData =  NSMutableData()
    let cgImage = UIImage(data: imageData)!.cgImage
    let imageDestination: CGImageDestination = CGImageDestinationCreateWithData((newImageData as CFMutableData), UTI, 1, nil)!
    CGImageDestinationAddImage(imageDestination, cgImage!, metadata as CFDictionary)
    CGImageDestinationFinalize(imageDestination)

    return newImageData as Data

【讨论】:

(changeRequest as!PHAssetCreationRequest).addResource(

以上是关于使用新的 PHPhotoLibrary 保存 exif 元数据的主要内容,如果未能解决你的问题,请参考以下文章

保存图像元数据 PHPhotoLibrary 与 ALAssetsLibrary

如何在 iOS9 中使用 PHPhotolibrary 将照片保存在带有元数据的相册中?

如何在 iOS/Swift 2 上使用 PHPhotoLibrary 保存具有自定义文件名的照片?

如何从 PHPhotoLibrary 获取图像的位置?

text PHPhotoLibrary + SaveImage - 使用Photos Framework swift 3保存图像

在 PHPhotoLibrary 中,如何知道视频文件是不是可以保存在 iPhone 上