Фреймворк для фотографий iOS 8. Доступ к метаданным фотографий

#ios #iphone #uiimageview #alassetslibrary #ios8

#iOS #iPhone #uiimageview #alassetslibrary #ios8

Вопрос:

Я рассматриваю возможность замены ALAssetsLibrary Photos рамки в моем приложении.

Я могу просто отлично извлекать фотографии, коллекции и источники ресурсов (даже записывать их обратно), но нигде не вижу доступа к метаданным фотографий (словари, такие как {Exif}, {TIFF}, {GPS} и т. Д.).

ALAssetsLibrary есть способ. UIImagePickerController есть способ. Photos тоже должен быть способ.

Я вижу, что PHAsset у location этого есть свойство, которое подойдет для словаря GPS, но я хочу получить доступ ко всем метаданным, которые включают лица, ориентацию, экспозицию, ISO и многое другое.

В настоящее время Apple находится на стадии бета-версии 2. Возможно, появятся новые API?

Обновить

Официального способа сделать это, используя только API-интерфейсы Photos, не существует.

Однако вы можете прочитать метаданные после загрузки данных изображения. Есть несколько способов сделать это, используя либо PHImageManager или PHContentEditingInput .

Этот PHContentEditingInput метод требует меньше кода и не требует импорта ImageIO . Я поместил его в категорию PHAsset.

Комментарии:

1. Выяснили ли вы, есть ли способ сделать это без загрузки данных изображения?

2. Я проверил вашу категорию, но requestMetadataWithCompletionBlock не возвращает метаданные для видео. Есть ли какой-либо другой способ получить метаданные видео без загрузки видео

Ответ №1:

Если вы запрашиваете ввод для редактирования содержимого, вы можете получить полное изображение в виде 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} и т. Д.

Комментарии:

1. Очень круто. Я могу читать свойства, как вы показали. Попробую записать их тоже.

2. Если вы сможете их выписать, пожалуйста, дайте мне знать!

3. По-видимому, это самый быстрый способ выполнить работу. Это также намного меньше кода, чем в моем примере (загрузка данных изображения), и эта версия не зависит от ImageIO. Принятый ответ

4. Спасибо тебе, @Joey! Я создал gist на основе вашего кода, чтобы поделиться им со своей командой. Я также хотел бы поделиться им здесь: swift-фотографии-метаданные

5. @Benjohn Это решение требует загрузки полного изображения, если оно еще не доступно локально, т.Е. Хранится в iCloud

Ответ №2:

Я подумал, что поделюсь некоторым кодом для чтения метаданных с использованием фреймворка 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;
}
 

Это связано с накладными расходами на захват изображения, поэтому это происходит не так быстро, как перечисление ваших ресурсов или коллекций, но, по крайней мере, это что-то.

Комментарии:

1. В этом примере кода произошла утечка памяти; если imageProperties значение не равно нулю, метод вернется до его вызова CFRelease(imageSource) .

Ответ №3:

Я предпочитаю не решение 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
}
 

Комментарии:

1. Интересно. Спасибо за совместное использование. Проверю это.

Ответ №4:

PhotoKit ограничивает доступ к метаданным свойствами PHAsset (местоположение, дата создания, избранное, скрытое, дата изменения, ширина пикселя, высота пикселя …). Причина (я подозреваю) заключается в том, что из-за внедрения iCloud PhotoLibrary изображений может не быть на устройстве. Поэтому все метаданные недоступны. Единственный способ получить полные метаданные EXIF / IPTC — это сначала загрузить исходное изображение (если оно недоступно) из iCloud, а затем использовать ImageIO для извлечения его метаданных.

Комментарии:

1. Да, это все, что можно сделать прямо сейчас. Я отправил сообщение об ошибке (фактически запрос функции) в Apple. Они ответили мне: «Спасибо, мы всегда ищем новые идеи». Я был бы удивлен, увидев это в 8.0. Жаль, однако. Это может обеспечить некоторую мощную фильтрацию. Они уже извлекают некоторые из них (например, местоположение). Они могли бы извлечь весь словарь и представить его как .metadata, как в ALAsset.defaultRepresentation , даже если он был доступен только для чтения.

2. Мне кажется, что PhotoKit все еще находится на самой ранней стадии. К сожалению, все еще существует много лазеек в функциональности. Новая концепция выборки, например, является мощной, но свойства, которые мы можем извлекать, очень ограничены.

Ответ №5:

Лучшее решение, которое я нашел и которое хорошо сработало для меня, это:

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

Комментарии:

1. Вы нашли способ записать изменения метаданных обратно в исходный источник?

2. Учитывая производительность и использование памяти, ImageIO по-прежнему предпочтительнее.

3. Можно ли перевести этот код на Swift? Я не могу.

Ответ №6:

Вы можете изменить PHAsset (например, добавить метаданные о местоположении), используя Photos Framework и метод UIImagePickerControllerDelegate. Нет накладных расходов из сторонних библиотек, не создаются дубликаты фотографий. Работает для iOS 8.0

В методе делегирования didFinishPickingMediaWithInfo вызовите UIImageWriteToSavedPhotosAlbum, чтобы сначала сохранить изображение. Это также создаст PHAsset, данные 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)
    }    
}
 

Функция выбора завершения будет запущена после завершения сохранения или сбоя с ошибкой. В обратном вызове извлеките только что созданный 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!)")
}
 

Комментарии:

1. Да, вы можете. Я поделился категорией в своем оригинальном сообщении, в которой есть удобный способ сделать это (похожий на ваш). Его блок вызывается с обновленным набором PHAsset. Никаких дубликатов. [местоположение обновления активов:дата создания местоположения: nil AssetBlock:(^AssetBlock)];