修改后的EXIF数据不能正确保存

经过无数的尝试和筛选通过每个SO答案+谷歌的结果,让我感到困惑的是,在iOS上使用EXIF是如此令人沮丧。

以下是其结果的工作代码。

[[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:@"[SD] 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 (eg, 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); }]; 

哪个输出这个:

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

和这个:

 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 = "[SD] 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; }; } 

而且,一切完成后,这个:

 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 = "[SD] kCGImagePropertyExifUserComment"; WhiteBalance = 0; }; "{JFIF}" = { DensityUnit = 1; JFIFVersion = ( 1, 1 ); XDensity = 72; YDensity = 72; }; "{TIFF}" = { Orientation = 1; ResolutionUnit = 2; XResolution = 72; YResolution = 72; "_YCbCrPositioning" = 1; }; } 

如示例所示,您可以看到图像的原始元数据,其修改,然后是其最终输出。

最终的输出是困扰我的,因为不pipe我做什么,我都无法让我的修改值坚持下去!

有一些非常具体的格式我错过了吗? 为什么iOS剥离我的修改? 我需要做些什么来添加这些额外的值? 他们被列在.header中,并认为它应该被容易接受。

苹果公司的开发人员斯科特(Scott)回复了我的事件报告,并解决了这个问题:

上面的代码是为GPS值编写string值这是行不通的 ,它们必须是NS / CFNumbers (我们为EXIF提取浮点值)。

我将向苹果公司提交一份关于他们文档的错误报告。

虽然花了一个星期的时间才得到这个回应,但我真的很感谢苹果公司给开发者的支持。 (谢谢Scott!) 😉

下面是改进的代码和正确的输出:

 [[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:@"[SD] 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 (eg, 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); }]; 

哪个输出这个:

 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 = "[SD] 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; }; } 

正如你所看到的,现在所有的值都被正确地embedded到EXIF头文件中,并且我已经testing了这一点是正确的用JPG格式写入相机胶卷的。

请享用! 🙂

在Swift 3中,我无法使用CGImageDestinationAddImageFromSource处理我的元数据字典。 这里有一个较低级别的Swift版本,它似乎很好用: https : //gist.github.com/lacyrhoades/09d8a367125b6225df5038aec68ed9e7