Проблема записи метаданных в изображение - PullRequest
3 голосов
/ 28 октября 2010

Я использую AvFoundation для съемки неподвижного изображения и добавления информации GPS в метаданные и сохранения в фотоальбом с использованием библиотеки активов, но информация GPS не сохраняется вообще.

вот мой код ...

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

if (imageDataSampleBuffer != NULL) 

    {

            CFDictionaryRef exifAttachments = CMGetAttachment(imageDataSampleBuffer,kCGImagePropertyExifDictionary, NULL);
            CFDictionaryRef metadataDict = CMCopyDictionaryOfAttachments(NULL, imageDataSampleBuffer, kCMAttachmentMode_ShouldPropagate);


        NSDictionary *gpsDict = [NSDictionary dictionaryWithObjectsAndKeys:@"1",kCGImagePropertyGPSVersion,
                                 @"78.4852",kCGImagePropertyGPSLatitude,@"32.1456",kCGImagePropertyGPSLongitude, nil];

        CMSetAttachment(imageDataSampleBuffer,kCGImagePropertyGPSDictionary,gpsDict,kCMAttachmentMode_ShouldPropagate);

        CFDictionaryRef newMetadata = CMCopyDictionaryOfAttachments(NULL, imageDataSampleBuffer, kCMAttachmentMode_ShouldPropagate);
        CFDictionaryRef gpsAttachments = CMGetAttachment(imageDataSampleBuffer,kCGImagePropertyGPSDictionary, NULL);

        if (exifAttachments) 
        { // Attachments may be read or additional ones written

        }

        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
        UIImage *image = [[UIImage alloc] initWithData:imageData];  

        ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
        /


        NSDictionary *newDict = (NSDictionary *)newMetadata;

        [library writeImageToSavedPhotosAlbum:[image CGImage] 
                                     metadata:newDict completionBlock:^(NSURL *assetURL, NSError *error)
         {
             if (error) 
             {

             }                                                                                               
         }];

        [library release];
        [image release];
        CFRelease(metadataDict);
        CFRelease(newMetadata);

    } 
    else if (error) 
    {

    }

}];

Ответы [ 3 ]

9 голосов
/ 21 февраля 2011

У меня была точно такая же проблема. Я думаю, что документация по этой теме невелика, поэтому я решил ее в конце, посмотрев на метаданные фотографии, сделанной приложением «Камера», и попытаясь воспроизвести ее.

Вот список свойств, которые сохраняет приложение Camera:

  • kCGImagePropertyGPSLatitude (NSNumber) (Широта в десятичном формате)
  • kCGImagePropertyGPSLongitude (NSNumber) (долгота в десятичном формате)
  • kCGImagePropertyGPSLatitudeRef (строка NSS) (N или S)
  • kCGImagePropertyGPSLongitudeRef (NSString) (E или W)
  • kCGImagePropertyGPSTimeStamp (NSString) (в формате 04: 30: 51.71 (Метка времени UTC))

Если вы будете придерживаться этого, у вас все будет хорошо. Вот пример:

CFDictionaryRef metaDict = CMCopyDictionaryOfAttachments(NULL, imageDataSampleBuffer, kCMAttachmentMode_ShouldPropagate);
CFMutableDictionaryRef mutable = CFDictionaryCreateMutableCopy(NULL, 0, metaDict);

NSDictionary *gpsDict = [NSDictionary 
  dictionaryWithObjectsAndKeys:
  [NSNumber numberWithFloat:self.currentLocation.coordinate.latitude], kCGImagePropertyGPSLatitude,
  @"N", kCGImagePropertyGPSLatitudeRef,
  [NSNumber numberWithFloat:self.currentLocation.coordinate.longitude], kCGImagePropertyGPSLongitude,
  @"E", kCGImagePropertyGPSLongitudeRef,
  @"04:30:51.71", kCGImagePropertyGPSTimeStamp,
  nil];

CFDictionarySetValue(mutable, kCGImagePropertyGPSDictionary, gpsDict);

//  Get the image
NSData *imageData = [AVCaptureStillImageOutput  jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage *image = [[UIImage alloc] initWithData:imageData];

//  Get the assets library
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library writeImageToSavedPhotosAlbum:[image CGImage] metadata:mutable completionBlock:captureComplete];

Это все в завершениеHandler метода captureStillImageAsynchronouslyFromConnection вашего объекта AVCaptureConnection, а self.currentLocation является просто CLLocation. В качестве примера я жестко закодировал временную метку и ссылки на широты и долготы.

Надеюсь, это поможет!

2 голосов
/ 15 сентября 2011

Ответ Мэйсона действительно помог мне.Вам потребуются некоторые изменения, такие как установка абсолютного значения долготы и широты.Вот фрагмент кода использования CoreLocation + Image I / O для записи UIImage на диск с информацией GPS:

- (BOOL)writeCGImage:(CGImageRef)theImage toURL:(NSURL*)url withType:(CFStringRef)imageType andOptions:(CFDictionaryRef)options {
    CGImageDestinationRef myImageDest   = CGImageDestinationCreateWithURL((CFURLRef)url, imageType, 1, nil);
    CGImageDestinationAddImage(myImageDest, theImage, options);
    BOOL success                        = CGImageDestinationFinalize(myImageDest);

    // Memory Mangement
    CFRelease(myImageDest);
    if (options)
        CFRelease(options);

    return success;
}

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
    if (newLocation) {
        [manager stopUpdatingLocation];

        // Create formatted date
        NSTimeZone      *timeZone   = [NSTimeZone timeZoneWithName:@"UTC"];
        NSDateFormatter *formatter  = [[NSDateFormatter alloc] init]; 
        [formatter setTimeZone:timeZone];
        [formatter setDateFormat:@"HH:mm:ss.SS"];

        // Create GPS Dictionary
        NSDictionary *gpsDict   = [NSDictionary dictionaryWithObjectsAndKeys:
                                     [NSNumber numberWithFloat:fabs(newLocation.coordinate.latitude)], kCGImagePropertyGPSLatitude
                                   , ((newLocation.coordinate.latitude >= 0) ? @"N" : @"S"), kCGImagePropertyGPSLatitudeRef
                                   , [NSNumber numberWithFloat:fabs(newLocation.coordinate.longitude)], kCGImagePropertyGPSLongitude
                                   , ((newLocation.coordinate.longitude >= 0) ? @"E" : @"W"), kCGImagePropertyGPSLongitudeRef
                                   , [formatter stringFromDate:[newLocation timestamp]], kCGImagePropertyGPSTimeStamp
                                   , nil];

        // Memory Management
        [formatter release];

        // Set GPS Dictionary to be part of media Metadata
        // NOTE: mediaInfo in this sample is dictionary object returned in UIImagePickerController delegate:
        // imagePickerController:didFinishPickingMediaWithInfo
        if (mediaInfo && [mediaInfo objectForKey:UIImagePickerControllerMediaMetadata] && gpsDict) {
            [[mediaInfo objectForKey:UIImagePickerControllerMediaMetadata] setValue:gpsDict forKey:@"{GPS}"];
        }

        // Save Image
        if([self writeCGImage:[image CGImage] toURL:imageSaveURL withType:kUTTypeJPEG andOptions:(CFDictionaryRef)[mediaInfo objectForKey:UIImagePickerControllerMediaMetadata]]) {
            // Image is written to device
        }
    } 
}
0 голосов
/ 01 октября 2013

Есть несколько ответов выше и в других местах на SO, которые дают большую часть того, что вы хотите. Вот рабочий код, который я сгенерировал из всех этих источников, и он, кажется, работает просто отлично.

Все это делается в блоке captureStillImageAsynchronouslyFromConnection. Спасибо всем различным авторам:

    [stillCapture
 captureStillImageAsynchronouslyFromConnection: stillConnection
 completionHandler:
 ^(CMSampleBufferRef imageSampleBuffer, NSError *error) {
     if (error) {
         NSLog(@"snap capture error %@", [error localizedDescription]);
         return;
     }

     NSData *jpegData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageSampleBuffer];

     CFDictionaryRef attachments = CMCopyDictionaryOfAttachments(kCFAllocatorDefault, imageSampleBuffer, kCMAttachmentMode_ShouldPropagate);
     CFMutableDictionaryRef mutableAttachments = CFDictionaryCreateMutableCopy(NULL, 0, attachments);

     // Create GPS Dictionary
     NSMutableDictionary *gps = [NSMutableDictionary dictionary];

     CLLocation *location = <your location source here>;

     // GPS tag version
     [gps setObject:@"2.2.0.0" forKey:(NSString *)kCGImagePropertyGPSVersion];

     // Time and date must be provided as strings, not as an NSDate object
     NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
     [formatter setDateFormat:@"HH:mm:ss.SSSSSS"];
     [formatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
     [gps setObject:[formatter stringFromDate:location.timestamp] forKey:(NSString *)kCGImagePropertyGPSTimeStamp];

     // Latitude
     [gps setObject: (location.coordinate.latitude < 0) ? @"S" : @"N"
             forKey:(NSString *)kCGImagePropertyGPSLatitudeRef];
     [gps setObject:[NSNumber numberWithDouble:fabs(location.coordinate.latitude)]
             forKey:(NSString *)kCGImagePropertyGPSLatitude];

     // Longitude
     [gps setObject: (location.coordinate.longitude < 0) ? @"W" : @"E"
             forKey:(NSString *)kCGImagePropertyGPSLongitudeRef];
     [gps setObject:[NSNumber numberWithDouble:fabs(location.coordinate.longitude)]
             forKey:(NSString *)kCGImagePropertyGPSLongitude];

     // Altitude
     if (!isnan(location.altitude)){
         // NB: many get this wrong, it is an int, not a string:
         [gps setObject:[NSNumber numberWithInt: location.altitude >= 0 ? 0 : 1]
                 forKey:(NSString *)kCGImagePropertyGPSAltitudeRef];
         [gps setObject:[NSNumber numberWithDouble:fabs(location.altitude)]
                 forKey:(NSString *)kCGImagePropertyGPSAltitude];
     }

     // Speed, must be converted from m/s to km/h
     if (location.speed >= 0){
         [gps setObject:@"K" forKey:(NSString *)kCGImagePropertyGPSSpeedRef];
         [gps setObject:[NSNumber numberWithDouble:location.speed*3.6] forKey:(NSString *)kCGImagePropertyGPSSpeed];
     }

     // Heading
     if (location.course >= 0){
         [gps setObject:@"T" forKey:(NSString *)kCGImagePropertyGPSTrackRef];
         [gps setObject:[NSNumber numberWithDouble:location.course] forKey:(NSString *)kCGImagePropertyGPSTrack];
     }

     CFDictionarySetValue(mutableAttachments, kCGImagePropertyGPSDictionary, CFBridgingRetain(gps));

//         NSDictionary *ma = (__bridge NSDictionary *)(mutableAttachments);
//         NSLog(@"photo attachments: %@", ma);

     [ROOTVC.library
      writeImageDataToSavedPhotosAlbum:jpegData
      metadata:(__bridge id)mutableAttachments
      completionBlock:^(NSURL *assetURL, NSError *error) {
          if (error) {
              NSLog(@"XXX save to assets failed: %@", [error localizedDescription]);
          } else {
              [self processAsset:assetURL inGroup:ROOTVC.venue forMessage:savingMessage];
          }
      }];

     if (mutableAttachments)
         CFRelease(mutableAttachments);
     if (attachments)
         CFRelease(attachments);
 }];

Как всегда, я волнуюсь, правильно ли я делаю релизы. Вот пример (1) результата:

File name    : 20131001_082119/photo.jpeg
File size    : 82876 bytes
File date    : 2013:10:01 08:21:19
Resolution   : 480 x 360
Orientation  : rotate 90
Flash used   : No
Focal length :  4.1mm  (35mm equivalent: 30mm)
Exposure time: 0.0083 s  (1/120)
Aperture     : f/2.2
ISO equiv.   : 6400
Whitebalance : Auto
Metering Mode: pattern
Exposure     : program (auto)
GPS Latitude : N 40d 43m 22.32s
GPS Longitude: W 74d 34m 39.15s
GPS Altitude :  117.92m
...