Modified EXIF data doesn't save properly

匿名 (未验证) 提交于 2019-12-03 00:59:01

问题:

After countless attempts and sieving through every SO answer + google result, it baffles me that working with EXIF on iOS is so frustrating.

Below is working code with its results.

[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection     completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error)     {         NSData *imageNSData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];          CGImageSourceRef imgSource = CGImageSourceCreateWithData((__bridge_retained CFDataRef)imageNSData, NULL);          //get all the metadata in the image         NSDictionary *metadata = (__bridge NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imgSource, 0, NULL);          NSLog(@"original metadata Info: %@",metadata);          //make the metadata dictionary mutable so we can add properties to it         NSMutableDictionary *metadataAsMutable = [metadata mutableCopy];          NSMutableDictionary *EXIFDictionary = [[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy];         NSMutableDictionary *GPSDictionary = [[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyGPSDictionary]mutableCopy];         NSMutableDictionary *RAWDictionary = [[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyRawDictionary]mutableCopy];          if(!EXIFDictionary)             EXIFDictionary = [[NSMutableDictionary dictionary] init];          if(!GPSDictionary)             GPSDictionary = [[NSMutableDictionary dictionary] init];          if(!RAWDictionary)             RAWDictionary = [[NSMutableDictionary dictionary] init];           [GPSDictionary setObject:@"camera coord Latitude"                          forKey:(NSString*)kCGImagePropertyGPSLatitude];         [GPSDictionary setObject:@"camera coord Longitude"                          forKey:(NSString*)kCGImagePropertyGPSLongitude];         [GPSDictionary setObject:@"camera GPS Date Stamp"                          forKey:(NSString*)kCGImagePropertyGPSDateStamp];         [GPSDictionary setObject:@"camera direction (heading) in degrees"                          forKey:(NSString*)kCGImagePropertyGPSImgDirection];          [GPSDictionary setObject:@"subject coord Latitude"                          forKey:(NSString*)kCGImagePropertyGPSDestLatitude];         [GPSDictionary setObject:@"subject coord Longitude"                          forKey:(NSString*)kCGImagePropertyGPSDestLongitude];          [EXIFDictionary setObject:@"[S.D.] kCGImagePropertyExifUserComment"                            forKey:(NSString *)kCGImagePropertyExifUserComment];          [EXIFDictionary setValue:@"69 m" forKey:(NSString *)kCGImagePropertyExifSubjectDistance];           //Add the modified Data back into the image’s metadata         [metadataAsMutable setObject:EXIFDictionary forKey:(NSString *)kCGImagePropertyExifDictionary];         [metadataAsMutable setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];         [metadataAsMutable setObject:RAWDictionary forKey:(NSString *)kCGImagePropertyRawDictionary];           NSLog(@"metadataAsMutable Info: %@",metadataAsMutable);          CFStringRef UTI = CGImageSourceGetType(imgSource); //this is the type of image (e.g., public.jpeg)          //this will be the data CGImageDestinationRef will write into         NSMutableData *newImageData = [NSMutableData data];          CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)newImageData, UTI, 1, NULL);          if(!destination)             NSLog(@"***Could not create image destination ***");          //add the image contained in the image source to the destination, overidding the old metadata with our modified metadata         CGImageDestinationAddImageFromSource(destination, imgSource, 0, (__bridge CFDictionaryRef) metadataAsMutable);          //tell the destination to write the image data and metadata into our data object.         //It will return false if something goes wrong         BOOL success = NO;         success = CGImageDestinationFinalize(destination);          if(!success)             NSLog(@"***Could not create data from image destination ***");          CIImage *testImage = [CIImage imageWithData:newImageData];         NSDictionary *propDict = [testImage properties];         NSLog(@"Properties %@", propDict);      }]; 

Which outputs this:

2012-10-12 23:17:45.415 Waypointer[3120:907] original metadata Info: { ColorModel = RGB; DPIHeight = 72; DPIWidth = 72; Depth = 8; Orientation = 1; PixelHeight = 2448; PixelWidth = 3264; "{Exif}" =     {     ApertureValue = "2.526069";     BrightnessValue = "-4.410617";     ColorSpace = 1;     ComponentsConfiguration =         (         1,         2,         3,         0     );     ExifVersion =         (         2,         2,         1     );     ExposureMode = 0;     ExposureProgram = 2;     ExposureTime = "0.06666667";     FNumber = "2.4";     Flash = 16;     FlashPixVersion =         (         1,         0     );     FocalLenIn35mmFilm = 35;     FocalLength = "4.28";     ISOSpeedRatings =         (         800     );     MeteringMode = 5;     PixelXDimension = 3264;     PixelYDimension = 2448;     SceneCaptureType = 0;     SensingMethod = 2;     ShutterSpeedValue = "3.906905";     SubjectArea =         (         1631,         1223,         881,         881     );     WhiteBalance = 0; }; "{TIFF}" =     {     Orientation = 1;     ResolutionUnit = 2;     XResolution = 72;     YResolution = 72;     "_YCbCrPositioning" = 1; }; } 

And this:

2012-10-12 23:17:45.421 Waypointer[3120:907] metadataAsMutable Info: { ColorModel = RGB; DPIHeight = 72; DPIWidth = 72; Depth = 8; Orientation = 1; PixelHeight = 2448; PixelWidth = 3264; "{Exif}" =     {     ApertureValue = "2.526069";     BrightnessValue = "-4.410617";     ColorSpace = 1;     ComponentsConfiguration =         (         1,         2,         3,         0     );     ExifVersion =         (         2,         2,         1     );     ExposureMode = 0;     ExposureProgram = 2;     ExposureTime = "0.06666667";     FNumber = "2.4";     Flash = 16;     FlashPixVersion =         (         1,         0     );     FocalLenIn35mmFilm = 35;     FocalLength = "4.28";     ISOSpeedRatings =         (         800     );     MeteringMode = 5;     PixelXDimension = 3264;     PixelYDimension = 2448;     SceneCaptureType = 0;     SensingMethod = 2;     ShutterSpeedValue = "3.906905";     SubjectArea =         (         1631,         1223,         881,         881     );     SubjectDistance = "69 m";     UserComment = "[S.D.] kCGImagePropertyExifUserComment";     WhiteBalance = 0; }; "{GPS}" =     {     DateStamp = "camera GPS Date Stamp";     DestLatitude = "subject coord Latitude";     DestLongitude = "subject coord Longitude";     ImgDirection = "camera direction (heading) in degrees";     Latitude = "camera coord Latitude";     Longitude = "camera coord Longitude"; }; "{Raw}" =     { }; "{TIFF}" =     {     Orientation = 1;     ResolutionUnit = 2;     XResolution = 72;     YResolution = 72;     "_YCbCrPositioning" = 1; }; } 

And, after it's all done, this:

2012-10-12 23:17:47.131 Waypointer[3120:907] Properties { ColorModel = RGB; DPIHeight = 72; DPIWidth = 72; Depth = 8; Orientation = 1; PixelHeight = 2448; PixelWidth = 3264; "{Exif}" =     {     ApertureValue = "2.526069";     BrightnessValue = "-4.410617";     ColorSpace = 1;     ComponentsConfiguration =         (         0,         0,         0,         1     );     ExifVersion =         (         2,         2,         1     );     ExposureMode = 0;     ExposureProgram = 2;     ExposureTime = "0.06666667";     FNumber = "2.4";     Flash = 16;     FlashPixVersion =         (         1,         0     );     FocalLenIn35mmFilm = 35;     FocalLength = "4.28";     ISOSpeedRatings =         (         800     );     MeteringMode = 5;     PixelXDimension = 3264;     PixelYDimension = 2448;     SceneCaptureType = 0;     SensingMethod = 2;     ShutterSpeedValue = "3.906905";     SubjectArea =         (         1631,         1223,         881,         881     );     UserComment = "[S.D.] kCGImagePropertyExifUserComment";     WhiteBalance = 0; }; "{JFIF}" =     {     DensityUnit = 1;     JFIFVersion =         (         1,         1     );     XDensity = 72;     YDensity = 72; }; "{TIFF}" =     {     Orientation = 1;     ResolutionUnit = 2;     XResolution = 72;     YResolution = 72;     "_YCbCrPositioning" = 1; }; } 

As the example illustrates, you can see the image's original metaData, its modification, and then its final output.

The final output is what bothers me because no matter what I do, I cannot get my modified values to stick!

Is there some very specific format I'm missing? Why is iOS stripping my modifications? What do I need to do to add these extra values? They are listed in the .header and figured it should be easily accepted.

回答1:

Scott, a developer at Apple, returned with my incident report and fixed the issue:

The previous code above is writing string values for the GPS values - this won't work, they must be NS/CFNumbers (we extract a float value for EXIF).

I'll be filing a bug report to Apple against their documentation.

Although it took a week to get this response, I really do appreciate the support Apple provides to their developers. (thanks Scott!) ;-)

Below is the improved code and its correct output:

[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:videoConnection     completionHandler:^(CMSampleBufferRef imageSampleBuffer, NSError *error)     {         NSData *imageNSData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];          CGImageSourceRef imgSource = CGImageSourceCreateWithData((__bridge_retained CFDataRef)imageNSData, NULL);          //get all the metadata in the image         NSDictionary *metadata = (__bridge NSDictionary *)CGImageSourceCopyPropertiesAtIndex(imgSource, 0, NULL);          //make the metadata dictionary mutable so we can add properties to it         NSMutableDictionary *metadataAsMutable = [metadata mutableCopy];          NSMutableDictionary *EXIFDictionary = [[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyExifDictionary]mutableCopy];         NSMutableDictionary *GPSDictionary = [[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyGPSDictionary]mutableCopy];         NSMutableDictionary *RAWDictionary = [[metadataAsMutable objectForKey:(NSString *)kCGImagePropertyRawDictionary]mutableCopy];          if(!EXIFDictionary)             EXIFDictionary = [[NSMutableDictionary dictionary] init];          if(!GPSDictionary)             GPSDictionary = [[NSMutableDictionary dictionary] init];          if(!RAWDictionary)             RAWDictionary = [[NSMutableDictionary dictionary] init];           [GPSDictionary setObject:[NSNumber numberWithFloat:37.795]                           forKey:(NSString*)kCGImagePropertyGPSLatitude];          [GPSDictionary setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSLatitudeRef];          [GPSDictionary setObject:[NSNumber numberWithFloat:122.410]                           forKey:(NSString*)kCGImagePropertyGPSLongitude];          [GPSDictionary setObject:@"W" forKey:(NSString*)kCGImagePropertyGPSLongitudeRef];          [GPSDictionary setObject:@"2012:10:18"                           forKey:(NSString*)kCGImagePropertyGPSDateStamp];          [GPSDictionary setObject:[NSNumber numberWithFloat:300]                           forKey:(NSString*)kCGImagePropertyGPSImgDirection];          [GPSDictionary setObject:[NSNumber numberWithFloat:37.795]                           forKey:(NSString*)kCGImagePropertyGPSDestLatitude];          [GPSDictionary setObject:@"N" forKey:(NSString*)kCGImagePropertyGPSDestLatitudeRef];          [GPSDictionary setObject:[NSNumber numberWithFloat:122.410]                           forKey:(NSString*)kCGImagePropertyGPSDestLongitude];          [GPSDictionary setObject:@"W" forKey:(NSString*)kCGImagePropertyGPSDestLongitudeRef];          [EXIFDictionary setObject:@"[S.D.] kCGImagePropertyExifUserComment"                            forKey:(NSString *)kCGImagePropertyExifUserComment];          [EXIFDictionary setObject:[NSNumber numberWithFloat:69.999]                            forKey:(NSString*)kCGImagePropertyExifSubjectDistance];           //Add the modified Data back into the image’s metadata         [metadataAsMutable setObject:EXIFDictionary forKey:(NSString *)kCGImagePropertyExifDictionary];         [metadataAsMutable setObject:GPSDictionary forKey:(NSString *)kCGImagePropertyGPSDictionary];         [metadataAsMutable setObject:RAWDictionary forKey:(NSString *)kCGImagePropertyRawDictionary];           CFStringRef UTI = CGImageSourceGetType(imgSource); //this is the type of image (e.g., public.jpeg)          //this will be the data CGImageDestinationRef will write into         NSMutableData *newImageData = [NSMutableData data];          CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef)newImageData, UTI, 1, NULL);          if(!destination)             NSLog(@"***Could not create image destination ***");          //add the image contained in the image source to the destination, overidding the old metadata with our modified metadata         CGImageDestinationAddImageFromSource(destination, imgSource, 0, (__bridge CFDictionaryRef) metadataAsMutable);          //tell the destination to write the image data and metadata into our data object.         //It will return false if something goes wrong         BOOL success = NO;         success = CGImageDestinationFinalize(destination);          if(!success)             NSLog(@"***Could not create data from image destination ***");          CIImage *testImage = [CIImage imageWithData:newImageData];         NSDictionary *propDict = [testImage properties];         NSLog(@"Final properties %@", propDict);      }]; 

Which outputs this:

Final properties info {     ColorModel = RGB;     DPIHeight = 72;     DPIWidth = 72;     Depth = 8;     Orientation = 6;     PixelHeight = 2448;     PixelWidth = 3264;     "{Exif}" =     {         ApertureValue = "2.526069";         BrightnessValue = "0.547474";         ColorSpace = 1;         ComponentsConfiguration =         (             0,             0,             0,             1         );         ExifVersion =         (             2,             2,             1         );         ExposureMode = 0;         ExposureProgram = 2;         ExposureTime = "0.05";         FNumber = "2.4";         Flash = 16;         FlashPixVersion =         (             1,             0         );         FocalLenIn35mmFilm = 35;         FocalLength = "4.28";         ISOSpeedRatings =         (             320         );         MeteringMode = 5;         PixelXDimension = 3264;         PixelYDimension = 2448;         SceneCaptureType = 0;         SensingMethod = 2;         ShutterSpeedValue = "4.321929";         SubjectArea =         (             1631,             1223,             881,             881         );         SubjectDistance = "69.999";         UserComment = "[S.D.] kCGImagePropertyExifUserComment";         WhiteBalance = 0;     };     "{GPS}" =     {         DateStamp = "2012:10:18";         DestLatitude = "37.795";         DestLatitudeRef = N;         DestLongitude = "122.41";         DestLongitudeRef = W;         ImgDirection = 300;         Latitude = "37.795";         LatitudeRef = N;         Longitude = "122.41";         LongitudeRef = W;     };     "{JFIF}" =     {         DensityUnit = 1;         JFIFVersion =         (             1,             1         );         XDensity = 72;         YDensity = 72;     };     "{TIFF}" =     {         Orientation = 6;         ResolutionUnit = 2;         XResolution = 72;         YResolution = 72;         "_YCbCrPositioning" = 1;     }; } 

As you can see, all values are now properly embedded into the EXIF header and I've tested that this is correctly written with the JPG to the camera roll.

Enjoy! :)



回答2:

In Swift 3 I could not get CGImageDestinationAddImageFromSource working with my metadata dictionaries. Here's a little lower level Swift version which seems to work great: https://gist.github.com/lacyrhoades/09d8a367125b6225df5038aec68ed9e7



标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!