web-dev-qa-db-ja.com

iOS 8 Photosフレームワーク。写真のメタデータにアクセスする

私はアプリでALAssetsLibraryPhotos framework に置き換えることを検討しています。

写真、コレクション、および資産ソースは問題なく取得できます(書き戻すことも可能)が、写真のメタデータ({Exif}、{TIFF}、{GPS}、等...)。

ALAssetsLibraryには方法があります。 UIImagePickerControllerには方法があります。 Photosにも方法が必要です。

PHAssetにはGPS辞書用のlocationプロパティがありますが、顔、向き、露出、ISOなどを含むすべてのメタデータにアクセスしようとしています。 。

現在、Appleはベータ2です。おそらく、さらにAPIが登場するでしょうか?

[〜#〜] update [〜#〜]

Photos APIのみを使用してこれを行う公式の方法はありません。

ただし、画像データをダウンロードした後、メタデータを読み取ることができます。 PHImageManagerまたはPHContentEditingInputのいずれかを使用してこれを行うには、いくつかの方法があります。

PHContentEditingInputメソッドは、必要なコードが少なく、ImageIOをインポートする必要がありません。 PHAssetカテゴリ にまとめました。

25
VaporwareWolf

コンテンツ編集入力をリクエストすると、完全な画像をCIImageとして取得でき、CIImageにはpropertiesというプロパティがあります。これは画像メタデータを含む辞書です。

サンプルSwift Code:

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}などの辞書を取得します。

34
Jordan H

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;
}

これには画像を取得するオーバーヘッドがあるため、アセットまたはコレクションを列挙するほど高速ではありませんが、少なくとも何かはあります。

7
VaporwareWolf

私が見つけてうまく働いたより良い解決策は次のとおりです:

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

PhotoKitは、メタデータへのアクセスをPHAssetのプロパティ(location、creationDate、favourite、hidden、modificatonDate、pixelWidth、pixelHeight ...)に制限します。理由(私は疑います)は、iCloud PhotoLibraryの導入により、画像がデバイス上にない可能性があるためです。したがって、メタデータ全体は利用できません。完全なEXIF/IPTCメタデータを取得する唯一の方法は、最初に元のイメージ(利用できない場合)をiCloudからダウンロードしてから、ImageIOを使用してメタデータを抽出することです。

5
holtmann

私は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) = metadataFromImageData(data: data as NSData)
            //image = UIImage(data: data)
        }
    }
    // here to return image and meta
}
4
lbsweek

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!)")
}
0
kev8484