【问题标题】:iOS 8 Photos framework. Access photo metadataiOS 8 照片框架。访问照片元数据
【发布时间】:2014-08-19 04:06:19
【问题描述】:

我正在考虑在我的应用中将 ALAssetsLibrary 替换为 Photos framework

我可以很好地检索照片、收藏和资产来源(甚至将它们写回),但看不到任何地方可以访问照片的元数据(例如 {Exif}、{TIFF}、{ GPS}等...)。

ALAssetsLibrary 有办法。 UIImagePickerController 有办法。 Photos 也一定有办法。

我看到PHAsset 有一个location 属性,可以用于 GPS 字典,但我希望访问所有元数据,包括面部、方向、曝光、ISO 等等。

目前苹果处于 beta 2。也许还有更多 API 即将推出?

更新

没有官方方法可以仅使用照片 API 来做到这一点。

但是,您可以在下载图像数据后读取元数据。有几种方法可以使用PHImageManagerPHContentEditingInput 来完成此操作。

PHContentEditingInput 方法所需的代码更少,并且不需要您导入ImageIO。我已将其封装在 PHAsset category 中。

【问题讨论】:

  • 你知道是否有办法在不下载图像数据的情况下做到这一点?
  • 我已经检查了您的类别,但 requestMetadataWithCompletionBlock 不返回视频的元数据。有没有其他方法可以在不下载视频的情况下获取视频的元数据

标签: ios iphone uiimageview alassetslibrary ios8


【解决方案1】:

我不喜欢 CIImage 解决方案,而是喜欢 ImageIO 解决方案:

func imageAndMetadataFromImageData(data: NSData)-> (UIImage?,[String: Any]?) {
    let options = [kCGImageSourceShouldCache as String: kCFBooleanFalse]
    if let imgSrc = CGImageSourceCreateWithData(data, options as CFDictionary) {
        let metadata = CGImageSourceCopyPropertiesAtIndex(imgSrc, 0, options as CFDictionary) as! [String: Any]
        //print(metadata)
        // let image = UIImage(cgImage: imgSrc as! CGImage)
        let image = UIImage(data: data as Data)
        return (image, metadata)
    }
    return (nil, nil)
}

以下是从 PHAseet 获取数据的代码

func getImageAndMeta(asset: PHAsset){
    let options = PHImageRequestOptions()
    options.isSynchronous = true
    options.resizeMode = .none
    options.isNetworkAccessAllowed = false
    options.version = .current
    var image: UIImage? = nil
    var meta:[String:Any]? = nil
    _ = PHCachingImageManager().requestImageData(for: asset, options: options) { (imageData, dataUTI, orientation, info) in
        if let data = imageData {
            (image, meta) = imageAndMetadataFromImageData(data: data as NSData)
            //image = UIImage(data: data)
        }
    }
    // here to return image and meta
}

【讨论】:

  • 有趣。谢谢分享。会检查一下。
【解决方案2】:

如果您请求内容编辑输入,则可以将完整图像作为CIImage 获取,并且CIImage 具有标题为properties 的属性,该属性是包含图像元数据的字典。

示例 Swift 代码:

let options = PHContentEditingInputRequestOptions()
options.networkAccessAllowed = true //download asset metadata from iCloud if needed

asset.requestContentEditingInputWithOptions(options) { (contentEditingInput: PHContentEditingInput?, _) -> Void in
    let fullImage = CIImage(contentsOfURL: contentEditingInput!.fullSizeImageURL)

    print(fullImage.properties)
}

示例 Objective-C 代码:

PHContentEditingInputRequestOptions *options = [[PHContentEditingInputRequestOptions alloc] init];
options.networkAccessAllowed = YES; //download asset metadata from iCloud if needed

[asset requestContentEditingInputWithOptions:options completionHandler:^(PHContentEditingInput *contentEditingInput, NSDictionary *info) {
    CIImage *fullImage = [CIImage imageWithContentsOfURL:contentEditingInput.fullSizeImageURL];

    NSLog(@"%@", fullImage.properties.description);
}];

您将获得所需的 {Exif}、{TIFF}、{GPS} 等字典。

【讨论】:

  • 非常酷。我能够读取您显示的属性。也会尝试把它们写出来。
  • 如果你能写出来,请告诉我!
  • 这似乎是完成工作的最快方法。它的代码也比我的示例(下载图像数据)少得多,而且这个版本不依赖于 ImageIO。接受的答案
  • 谢谢@Joey!我根据您的代码创建了要点以与我的团队分享。我也想在这里分享:swift-photos-metadata
  • @Benjohn 此解决方案确实需要下载完整图像,如果它在本地尚不可用,即存储在 iCloud 中
【解决方案3】:

您可以使用 Photos Framework 和 UIImagePickerControllerDelegate 方法修改 PHAsset(例如添加位置元数据)。没有第三方图书馆的开销,没有创建重复的照片。适用于 iOS 8.0+

在didFinishPickingMediaWithInfo委托方法中,调用UIImageWriteToSavedPhotosAlbum首先保存图片。这也将创建我们将修改其 EXIF GPS 数据的 PHAsset:

func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {

    if let myImage = info[UIImagePickerControllerOriginalImage] as? UIImage  {

        UIImageWriteToSavedPhotosAlbum(myImage, self, Selector("image:didFinishSavingWithError:contextInfo:"), nil)
    }    
}

完成选择器功能将在保存完成或失败并出现错误后运行。在回调中,获取新创建的 PHAsset。然后,创建一个 PHAssetChangeRequest 来修改位置元数据。

func image(image: UIImage, didFinishSavingWithError: NSErrorPointer, contextInfo:UnsafePointer<Void>)       {

    if (didFinishSavingWithError != nil) {
        print("Error saving photo: \(didFinishSavingWithError)")
    } else {
        print("Successfully saved photo, will make request to update asset metadata")

        // fetch the most recent image asset:
        let fetchOptions = PHFetchOptions()
        fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
        let fetchResult = PHAsset.fetchAssetsWithMediaType(PHAssetMediaType.Image, options: fetchOptions)

        // get the asset we want to modify from results:
        let lastImageAsset = fetchResult.lastObject as! PHAsset

        // create CLLocation from lat/long coords:
        // (could fetch from LocationManager if needed)
        let coordinate = CLLocationCoordinate2DMake(myLatitude, myLongitude)
        let nowDate = NSDate()
        // I add some defaults for time/altitude/accuracies:
        let myLocation = CLLocation(coordinate: coordinate, altitude: 0.0, horizontalAccuracy: 1.0, verticalAccuracy: 1.0, timestamp: nowDate)

        // make change request:
        PHPhotoLibrary.sharedPhotoLibrary().performChanges({

            // modify existing asset:
            let assetChangeRequest = PHAssetChangeRequest(forAsset: lastImageAsset)
            assetChangeRequest.location = myLocation

            }, completionHandler: {
                (success:Bool, error:NSError?) -> Void in

                if (success) {
                    print("Succesfully saved metadata to asset")
                    print("location metadata = \(myLocation)")
                } else {
                    print("Failed to save metadata to asset with error: \(error!)")
}

【讨论】:

  • 是的,你可以。我在我的原始帖子中分享了一个类别,它有一个方便的方法来做到这一点(类似于你的)。使用更新的 PHAsset 调用它的块。没有重复。 [asset updateLocation:location creationDate:nilassetBlock:(^assetBlock)];
【解决方案4】:

我找到并为我工作的更好的解决方案是:

[[PHImageManager defaultManager] requestImageDataForAsset:photoAsset
                                                  options:reqOptions
                                            resultHandler:
         ^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
             CIImage* ciImage = [CIImage imageWithData:imageData];
             DLog(@"Metadata : %@", ciImage.properties);
         }];

【讨论】:

  • 您找到将元数据更改写回原始源的方法了吗?
  • 考虑性能和内存使用ImageIO还是比较可取的。
  • 是否可以将此代码翻译成 Swift?我做不到。
【解决方案5】:

我想我会分享一些代码来使用 ImageIO 框架和 Photos 框架来读取元数据。您必须使用 PHCachingImageManager 请求图像数据。

@property (strong) PHCachingImageManager *imageManager;

请求图像并使用它的数据创建元数据字典

-(void)metadataReader{
    PHFetchResult *result = [PHAsset fetchAssetsInAssetCollection:self.myAssetCollection options:nil];
    [result enumerateObjectsAtIndexes:[NSIndexSet indexSetWithIndex:myIndex] options:NSEnumerationConcurrent usingBlock:^(PHAsset *asset, NSUInteger idx, BOOL *stop) {
        [self.imageManager requestImageDataForAsset:asset options:nil resultHandler:^(NSData *imageData, NSString *dataUTI, UIImageOrientation orientation, NSDictionary *info) {
            NSDictionary *metadata = [self metadataFromImageData:imageData];
                           NSLog(@"Metadata: %@", metadata.description);
            NSDictionary *gpsDictionary = metadata[(NSString*)kCGImagePropertyGPSDictionary];
            if(gpsDictionary){
                NSLog(@"GPS: %@", gpsDictionary.description);
            }
            NSDictionary *exifDictionary = metadata[(NSString*)kCGImagePropertyExifDictionary];
            if(exifDictionary){
                NSLog(@"EXIF: %@", exifDictionary.description);
            }

            UIImage *image = [UIImage imageWithData:imageData scale:[UIScreen mainScreen].scale];
            // assign image where ever you need...
        }];

    }];
}

将 NSData 转换为元数据

-(NSDictionary*)metadataFromImageData:(NSData*)imageData{
    CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)(imageData), NULL);
    if (imageSource) {
        NSDictionary *options = @{(NSString *)kCGImageSourceShouldCache : [NSNumber numberWithBool:NO]};
        CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);
        if (imageProperties) {
            NSDictionary *metadata = (__bridge NSDictionary *)imageProperties;
            CFRelease(imageProperties);
            CFRelease(imageSource);
            return metadata;
        }
        CFRelease(imageSource);
    }

    NSLog(@"Can't read metadata");
    return nil;
}

这有抓取图像的开销,因此它不如枚举您的资产或集合快,但至少是这样。

【讨论】:

  • 此代码示例存在内存泄漏;如果imageProperties 不为零,则该方法将在调用CFRelease(imageSource) 之前返回。
【解决方案6】:

PhotoKit 将对元数据的访问限制为 PHAsset 的属性(位置、创建日期、收藏夹、隐藏、修改日期、像素宽度、像素高度...)。原因(我怀疑)是由于 iCloud PhotoLibrary 的引入,图像可能不在设备上。因此,整个元数据不可用。 获取完整 EXIF/IPTC 元数据的唯一方法是首先从 iCloud 下载原始图像(如果不可用),然后使用 ImageIO 提取其元数据。

【讨论】:

  • 是的,这就是现在可以做的一切。我向 Apple 提交了错误报告(实际上是功能请求)。他们给了我“谢谢,我们一直在寻找新的想法”的答复。如果在 8.0 中看到它,我会感到惊讶。不过太糟糕了。这可以提供一些强大的过滤。他们已经在提取其中的一部分(例如位置)。他们可以提取整个字典并将其公开为 .metadata,就像在 ALAsset.defaultRepresentation 中一样,即使它是只读的。
  • 在我看来,PhotoKit 似乎仍处于早期阶段。不幸的是,功能上仍然存在许多漏洞。新的获取概念是例如功能强大,但我们可以获取的属性非常有限。
猜你喜欢
  • 2017-08-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-01
  • 2016-01-20
  • 1970-01-01
  • 2017-05-08
  • 1970-01-01
相关资源
最近更新 更多