Getting metadata from images on iOS

Original Author: Gustavo Ambrozio

Recap

My latest post was on my repo on GitHub. Cool, I got code stalkers!

One thing was missing from the post though: how to get metadata from existing images. In this post I’ll show you a few methods to do this as well as how to use my NSMutableDictionary category to do this.

Getting images using UIImagePickerController

If you’re getting images from an UIImagePickerController you have to implement this delegate method:

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info

In iOS 4.1 or greater your info dictionary has a key called UIImagePickerControllerReferenceURL (for images from the library) or UIImagePickerControllerMediaMetadata (for images taken from the camera). If your info has the UIImagePickerControllerMediaMetadata key, then you just have to initialize your NSMutableDictionary with the NSDictionary you get from the info dictionary:

NSMutableDictionary *metadata = [[NSMutableDictionary alloc] initWithDictionary:[info objectForKey:UIImagePickerControllerMediaMetadata]];

But if you took an image from the library things are a little more complicated and not obvious at first sight. All you get in a NSURL object. How to get the metadata from this?? Using the AssetsLibrary framework, that’s how!

NSMutableDictionary *imageMetadata = nil;
 
  NSURL *assetURL = [info objectForKey:UIImagePickerControllerReferenceURL];
 
   
 
  ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
 
  [library assetForURL:assetURL
 
      resultBlock:^(ALAsset *asset)  {
 
          NSDictionary *metadata = asset.defaultRepresentation.metadata;
 
          imageMetadata = [[NSMutableDictionary alloc] initWithDictionary:metadata];
 
          [self addEntriesFromDictionary:metadata];
 
      }
 
      failureBlock:^(NSError *error) {
 
      }];
 
  [library autorelease];

One caveat on using this: because it uses blocks, there’s no guarantee that your imageMetadata dictionary will be populated when this code runs. In some testing I’ve done it sometimes runs the code inside the block even before the [library autorelease] is executed. But the first time you run this, the code inside the block will only run on another cycle of the apps main loop. So, if you need to use this info right away, it’s better to schedule a method on the run queue for later with:

[self performSelectorOnMainThread:SELECTOR withObject:SOME_OBJECT waitUntilDone:NO];

To make things easier, I’ve created an init method to my category:

- (id)initWithInfoFromImagePicker:(NSDictionary *)info;

You just have to add the NSMutableDictionary+ImageMetadata.h to your file and then use:

NSMutableDictionary *metadata = [[NSMutableDictionary alloc] initWithInfoFromImagePicker:info];

And you’re done! The category checks for the iOS version and for the correct keys and does everything for you. Just be careful about the issue with blocks I mentioned above.

Reading from the asset library

Well, I kinda spoiled the answer to this one already. If you’re using the AssetsLibrary to read images, you can use the method above, with the same caveat: it might not be accessible until some time after the method is called.

Again I created an init method in my category:

- (id)initFromAssetURL:(NSURL*)assetURL;

Using AVFoundation

iOS 4.0 introduced AVFoundation. AVFoundation gives us a lot of possibilities to work with pictures and the camera. Before iOS 4 if you wanted to take a picture you’d have to use an UIImagePickerController. Now you can use AVFoundation and have a lot of control over the camera, the flash, the preview, etc…

If you use AVFoundation to capture photos you’ll probably use AVCaptureStillImageOutput‘s:

- (void)captureStillImageAsynchronouslyFromConnection:(AVCaptureConnection *)connection 
 
                                      completionHandler:(void (^)(CMSampleBufferRef imageDataSampleBuffer, NSError *error))handler

The completion handler gives you a CMSampleBufferRef that has the metadata. But how to get it out f there is not clear from the documentation. It turns out it’s really simple:

CFDictionaryRef metadataDict = CMCopyDictionaryOfAttachments(NULL, imageDataSampleBuffer, kCMAttachmentMode_ShouldPropagate);

Since CFDictionaryRef is toll free bridged with NSDictionary, the whole process would look like this:

CFDictionaryRef metadataDict = CMCopyDictionaryOfAttachments(NULL, imageDataSampleBuffer, kCMAttachmentMode_ShouldPropagate);
 
  NSMutableDictionary *metadata = [[NSMutableDictionary alloc] initWithDictionary:(NSDictionary*)metadataDict];
 
  CFRelease(metadataDict);

At the risk of repeating myself, I again created an init method for this:

- (id)initWithImageSampleBuffer:(CMSampleBufferRef) imageDataSampleBuffer;

Wrapping up

So, there you have it, now you can read and write metadata.

What’s still missing are some methods to easily extract information from this dictionary. I have already created another method to extract the CLLocation information from it. As I now have a way to get and set this information I even converted it to a @property on the category, giving our NSMutableDictionary a nice way to access the location using the dot notation.

It’s very easy to add getter methods for every property but I have not done so yet. Feel free to fork my repo on GitHub and send pull requests for me to incorporate.

I also added another method to add the image’s digital zoom as the next update of Snap will have digital zoom and I’m writing this information to the pictures as well.

Oh, and have I mentioned that you should get Snap!

I’ll be writing every 15 days here but I try to post at least once a week on visit my blog and subscribe.