Save the exif metadata using the new PHPhotoLibrary
Asked Answered
C

2

5

I am currently using AVCaptureStillImageOutput to get a full resolution picture. I am also able to get the exif metadata using the following code:

[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:videoConnection completionHandler: ^(CMSampleBufferRef imageSampleBuffer, NSError *error)
     { 

         CFDictionaryRef metaDict = CMCopyDictionaryOfAttachments(NULL, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);
         CFMutableDictionaryRef mutableDict = CFDictionaryCreateMutableCopy(NULL, 0, metaDict);

         NSLog(@"test attachments %@", mutableDict);

         // set the dictionary back to the buffer
         CMSetAttachments(imageSampleBuffer, mutableDict, kCMAttachmentMode_ShouldPropagate);

         NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];

         UIImage *image = [[UIImage alloc] initWithData:imageData];    
         [self.delegate frameReadyToSave:image withExifAttachments: mutableDict];
     }];

The metadata being located in the mutableDict variable. Now, I want to save this picture in two different places, with the metadata. I want to save it on the disk in the application folders and in the Photo Library.

Now, I tried to save the image, in another method, using the following (the image variable you see is a custom object):

NSData* imageData = UIImageJPEGRepresentation(image.image, 1.0f);

[imageData writeToFile:image.filePath atomically:YES];

UIImageWriteToSavedPhotosAlbum(image.image, nil, nil, nil);

Now, the image is properly saved but does not contain any Exif metadata.

From what I have read, I need to use the PHPhotoLibrary to do so but the documentation isn't too loquacious on that. Here's what I found:

[[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
    PHAssetChangeRequest *createAssetRequest = [PHAssetChangeRequest creationRequestForAssetFromImage:image.image];      

} completionHandler:nil];

But how do I save the metadata with it?

Chuch answered 2/3, 2016 at 21:32 Comment(0)
W
2

I would suggest you use ImageIO to accomplish that:

-(void)frameReadyToSave:(UIImage*)image withExifAttachments:(NSMutableDictionary*)mutableDict
{
    NSData* imageData = UIImageJPEGRepresentation(image, 1.0f);
    CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) imageData, NULL);
    __block NSURL* tmpURL = [NSURL fileURLWithPath:@"example.jpg"]; //modify to your needs
    CGImageDestinationRef destination = CGImageDestinationCreateWithURL((__bridge CFURLRef) tmpURL, kUTTypeJPEG, 1, NULL);
    CGImageDestinationAddImageFromSource(destination, source, 0, (__bridge CFDictionaryRef) mutableDict);
    CGImageDestinationFinalize(destination);
    CFRelease(source);
    CFRelease(destination);
    [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{
        [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:tmpURL];
    }   completionHandler:^(BOOL success, NSError *error) {
        //cleanup the tmp file after import, if needed
    }];
}
Wheeler answered 13/3, 2016 at 23:27 Comment(6)
This saves the metadata in the EXIF part of the JPEG but it doesn't show up in Photos (on the Mac).Anticosti
[PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:tmpURL] should on import read out the EXIF metadata data of the photo and populate the iCloud Photo Library database with the necessary data (recording date, location data). Does the photo appear in the iOS Photos App with the correct timestamp?Wheeler
Indeed, it works for date and location. It does not work however for title, description, and keywords. At least not with the EXIF fields I tried.Anticosti
Title, description and keywords are not part of the iOS Photo Library database (and not editable on iOS - neither in the Photos App or as properties of PHAsset). It's correct that these properties are editable using the Photos App on the Mac. Title, description and keywords seem not to be synced over iCloud (yet).Wheeler
Is there no way to save photo directly to camera roll without creating a local file first?Cholecystitis
Did anyone find a solution for this? and if so, any way possible to save the image with its new metadata as uiimage before saving it in photo library? ThanksIndividuate
Q
1

Use this to merge metadata into image data and save it to Photo Library:

func saveImageData(data: Data, metadata: NSDictionary? = nil, album: PHAssetCollection, completion:((PHAsset?)->())? = nil) {
    var placeholder: PHObjectPlaceholder?

    PHPhotoLibrary.shared().performChanges({
        var changeRequest: PHAssetChangeRequest

        if let metadata = metadata {
            let newImageData = UIImage.mergeImageData(imageData: data, with: metadata)
            changeRequest = PHAssetCreationRequest.forAsset()
            (changeRequest as! PHAssetCreationRequest).addResource(with: .photo, data: newImageData as Data, options: nil)
        }
        else {
            changeRequest = PHAssetChangeRequest.creationRequestForAsset(from: UIImage(data: data)!)
        }

        guard let albumChangeRequest = PHAssetCollectionChangeRequest(for: album),
            let photoPlaceholder = changeRequest.placeholderForCreatedAsset else { return }

        placeholder = photoPlaceholder
        let fastEnumeration = NSArray(array: [photoPlaceholder] as [PHObjectPlaceholder])
        albumChangeRequest.addAssets(fastEnumeration)
    }, completionHandler: { success, error in
        guard let placeholder = placeholder else {
            completion?(nil)
            return
        }

        if success {
            let assets:PHFetchResult<PHAsset> =  PHAsset.fetchAssets(withLocalIdentifiers: [placeholder.localIdentifier], options: nil)
            let asset:PHAsset? = assets.firstObject
            completion?(asset)
        }
        else {
            completion?(nil)
        }
    })
}

func mergeImageData(imageData: Data, with metadata: NSDictionary) -> Data {
    let source: CGImageSource = CGImageSourceCreateWithData(imageData as NSData, nil)!
    let UTI: CFString = CGImageSourceGetType(source)!
    let newImageData =  NSMutableData()
    let cgImage = UIImage(data: imageData)!.cgImage
    let imageDestination: CGImageDestination = CGImageDestinationCreateWithData((newImageData as CFMutableData), UTI, 1, nil)!
    CGImageDestinationAddImage(imageDestination, cgImage!, metadata as CFDictionary)
    CGImageDestinationFinalize(imageDestination)

    return newImageData as Data
}
Quintus answered 21/6, 2018 at 5:0 Comment(1)
(changeRequest as! PHAssetCreationRequest).addResource( << this helps!Drysalt

© 2022 - 2024 — McMap. All rights reserved.