web-dev-qa-db-fra.com

cadre iOS 8 Photos. Accéder aux métadonnées de photo

Je cherche à remplacer ALAssetsLibrary par Photos framework dans mon application.

Je peux très bien récupérer des photos, des collections et des ressources (mais même les réécrire), mais je ne vois nulle part où accéder aux métadonnées des photos (les dictionnaires tels que {Exif}, {TIFF}, {GPS}, etc...).

ALAssetsLibrary a un chemin. UIImagePickerController a un chemin. Photos doit avoir un chemin aussi. 

Je vois que PHAsset a une propriété location qui convient au dictionnaire GPS, mais je cherche à accéder à toutes les métadonnées qui incluent les visages, l'orientation, l'exposition, l'ISO et bien plus encore. 

Actuellement, Apple est à la bêta 2. Peut-être y a-t-il plus d'API à venir?

METTRE À JOUR

Il n’existe aucun moyen officiel de le faire en utilisant uniquement les API Photos.

Cependant, vous pouvez lire les métadonnées après avoir téléchargé les données d'image. Il existe plusieurs méthodes pour ce faire en utilisant PHImageManager ou PHContentEditingInput

La méthode PHContentEditingInput nécessite moins de code et ne nécessite pas l'importation de ImageIO. Je l'ai emballé dans une catégorie PHAsset

25
VaporwareWolf

Si vous demandez une entrée de modification de contenu, vous pouvez obtenir l’image complète sous la forme CIImage et CIImage a une propriété intitulée properties, qui est un dictionnaire contenant les métadonnées de l’image.

Exemple de code 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)
}

Exemple de code 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);
}];

Vous obtiendrez les dictionnaires {Exif}, {TIFF}, {GPS}, etc. souhaités.

34
Jordan H

Je pensais partager du code pour lire les métadonnées à l'aide du cadre ImageIO conjointement au cadre Photos. Vous devez demander les données d'image à l'aide d'un PHCachingImageManager.

@property (strong) PHCachingImageManager *imageManager;

Demander l'image et utiliser ses données pour créer un dictionnaire de métadonnées

-(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...
        }];

    }];
}

Convertir NSData en métadonnées

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

Cela entraîne la surcharge de l'image, il est donc loin d'être aussi rapide que l'énumération de vos actifs ou de vos collections, mais c'est au moins quelque chose. 

7
VaporwareWolf

La meilleure solution que j'ai trouvée et qui a bien fonctionné pour moi est la suivante:

[[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 limite l'accès aux métadonnées aux propriétés de PHAsset (emplacement, date de création, favori, caché, date de modification, largeur en pixels, hauteur en pixels, etc.). La raison (je suppose) est qu’en raison de l’introduction de iCloud PhotoLibrary, les images risquent de ne pas figurer sur le périphérique. Par conséquent, toutes les métadonnées ne sont pas disponibles .. Le seul moyen d’obtenir des métadonnées EXIF ​​/ IPTC complètes est d’abord de télécharger l’image originale (si non disponible) depuis iCloud, puis d’utiliser ImageIO pour extraire ses métadonnées.

5
holtmann

Je préfère ne pas la solution CIImage, mais la solution 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)
}

ci-dessous est un code pour obtenir des données de 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

Vous pouvez modifier le PHAsset (par exemple, en ajoutant des métadonnées d'emplacement) à l'aide de Photos Framework et de la méthode UIImagePickerControllerDelegate. Pas de surcharge des bibliothèques tierces, pas de duplicata de photos créées. Fonctionne pour iOS 8.0+

Dans la méthode déléguée didFinishPickingMediaWithInfo, appelez UIImageWriteToSavedPhotosAlbum pour enregistrer d'abord l'image. Cela créera également le PHAsset dont nous modifierons les données EXIF ​​GPS:

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

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

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

La fonction de sélection d'achèvement s'exécutera une fois la sauvegarde terminée ou échouera avec une erreur. Dans le rappel, récupérez le PHAsset nouvellement créé. Ensuite, créez un PHAssetChangeRequest pour modifier les métadonnées d'emplacement.

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