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