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 .
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.
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.
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);
}];
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.
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
}
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!)")
}