I am trying to add/modify the image metadata when I add an image asset to an album in an iOS 8.4 Photos collection. The following code successfully adds the GPS dictionary but does not add or change the EXIF data (I have examined the image header and used EXIF tools to confirm). I have tried two different methods (see altSetMyImageInfo) with the same non-result. What am I missing?
@property (nonatomic) CLLocation *location;
. . .
[self.stillImageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:^( CMSampleBufferRef imageDataSampleBuffer, NSError *error ) {
if ( imageDataSampleBuffer ) {
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
CFDictionaryRef tempDict = CMCopyDictionaryOfAttachments(NULL, imageDataSampleBuffer, kCMAttachmentMode_ShouldPropagate);
CFMutableDictionaryRef theDict = CFDictionaryCreateMutableCopy(NULL, 0, tempDict);
[self setMyImageInfo:theDict];
[self setMyLocation:theDict];
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
CFStringRef UTI = CGImageSourceGetType(source);
NSMutableData *newImageData = [NSMutableData data];
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)newImageData,UTI,1,NULL);
BOOL success = CGImageDestinationFinalize(destination);
CFRelease(destination);
CFRelease(source);
}
}
. . .
if ( [PHAssetResourceCreationOptions class] ) {
[[PHAssetCreationRequest creationRequestForAsset] addResourceWithType:PHAssetResourceTypeFullSizePhoto data:newImageData options:nil];
}
else {
NSError *error = nil;
[newImageData writeToURL:temporaryFileURL options:NSDataWritingAtomic error:&error];
if ( error ) {
NSLog( @"Error: %@", error );
}
else {
PHAssetChangeRequest *newImageRequest = [PHAssetChangeRequest creationRequestForAssetFromImageAtFileURL:temporaryFileURL];
PHAssetCollectionChangeRequest *albumChangeRequest = [PHAssetCollectionChangeRequest changeRequestForAssetCollection:theAlbum];
[albumChangeRequest addAssets:@[newImageRequest.placeholderForCreatedAsset]];
}
. . .
- (void) setMyImageInfo:(CFMutableDictionaryRef)theDict
{
NSMutableDictionary *exif = [NSMutableDictionary dictionary];
[exif setObject:@"2.2.2" forKey:(NSString *)kCGImagePropertyExifVersion];
[exif setObject:@“MyIdentity” forKey:(NSString *)kCGImagePropertyExifImageUniqueID];
[exif setObject:@"I hope this works!" forKey:(NSString *)kCGImagePropertyExifUserComment];
CFDictionarySetValue(theDict, kCGImagePropertyExifDictionary, (__bridge void *)exif);
}
- (void) setMyLocation:(CFMutableDictionaryRef)theDict
{
NSMutableDictionary *gps = [NSMutableDictionary dictionary];
[gps setObject:@"2.2.0.0" forKey:(NSString *)kCGImagePropertyGPSVersion];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"HH:mm:ss.SSSSSS"];
[formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
[gps setObject:[formatter stringFromDate:self.location.timestamp] forKey:(NSString *)kCGImagePropertyGPSTimeStamp];
[formatter setDateFormat:@"yyyy:MM:dd"];
[gps setObject:[formatter stringFromDate:self.location.timestamp] forKey:(NSString *)kCGImagePropertyGPSDateStamp];
CGFloat latitude = self.location.coordinate.latitude;
if (latitude < 0) {
latitude = -latitude;
[gps setObject:@"S" forKey:(NSString *)kCGImagePropertyGPSLatitudeRef];
} else {
[gps setObject:@"N" forKey:(NSString *)kCGImagePropertyGPSLatitudeRef];
}
[gps setObject:[NSNumber numberWithFloat:latitude] forKey:(NSString *)kCGImagePropertyGPSLatitude];
CGFloat longitude = self.location.coordinate.longitude;
if (longitude < 0) {
longitude = -longitude;
[gps setObject:@"W" forKey:(NSString *)kCGImagePropertyGPSLongitudeRef];
} else {
[gps setObject:@"E" forKey:(NSString *)kCGImagePropertyGPSLongitudeRef];
}
[gps setObject:[NSNumber numberWithFloat:longitude] forKey:(NSString *)kCGImagePropertyGPSLongitude];
CGFloat altitude = self.location.altitude;
if (!isnan(altitude)){
if (altitude < 0) {
altitude = -altitude;
[gps setObject:@"1" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
} else {
[gps setObject:@"0" forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
}
[gps setObject:[NSNumber numberWithFloat:altitude] forKey:(NSString *)kCGImagePropertyGPSAltitude];
}
if (self.location.speed >= 0){
[gps setObject:@"K" forKey:(NSString *)kCGImagePropertyGPSSpeedRef];
[gps setObject:[NSNumber numberWithFloat:self.location.speed*3.6] forKey:(NSString *)kCGImagePropertyGPSSpeed];
}
if (self.location.course >= 0){
[gps setObject:@"T" forKey:(NSString *)kCGImagePropertyGPSTrackRef];
[gps setObject:[NSNumber numberWithFloat:self.location.course] forKey:(NSString *)kCGImagePropertyGPSTrack];
}
CFDictionarySetValue(theDict, kCGImagePropertyGPSDictionary, (__bridge void *)gps);
}
- (void) altSetMyImageInfo:(CFMutableDictionaryRef)theDict
{
CFStringRef imageUniqueID = CFStringCreateWithCString(NULL, [@“MyImageID” cStringUsingEncoding:NSASCIIStringEncoding], kCFStringEncodingASCII);
CFDictionarySetValue(theDict, kCGImagePropertyExifImageUniqueID, imageUniqueID);
CFRelease (imageUniqueID);
CFStringRef userComment = CFStringCreateWithCString(NULL, [@"I hope this works!" cStringUsingEncoding:NSASCIIStringEncoding], kCFStringEncodingASCII);
CFDictionarySetValue(theDict, kCGImagePropertyExifUserComment, userComment);
CFRelease (userComment);
}
The answer to this question involves a large number of things. A minor example, "CGImageSourceRef source" was missing. The most important thing was that I could not put the problem into the context of the project due to NDA restrictions from "you all know who" (you better know because I cannot say). Instead of parsing the question, I have posted the answer in code. If you insert this code into a certain example program of "you know who" (I cannot say the example name, but maCVA might offer you a clue to search their developer library). I have made it so that most of the code will sit at the end of a certain large source file, and the other lines have reference line numbers (start inserting at the top). The only other change is that the header file gets CLLocationManagerDelegate added to the @interface. You might want to add location-services to the plist, too.
I have included debug code to see the metadata in a log file. The only question remaining is whether to use CFDictionarySetValue or "setObject: forKey:" to assign the dictionary values. I used the former for the EXIF/TIFF data and the latter for the GPS data but included the "setObject: forKey:" code in the EXIF/TIFF method as comments. Both work. The "setObject: forKey:" is more terse. Any opinions?