Modifying Image Metadata

core-foundation, swift

Solution

I’m answering this myself since I found out why it wasn’t saving the data and it looks like this question can help others.

My code is correct, the only issue is that I wasn’t formatting the date properly. Since the date wasn’t in the correct format it was getting pruned by the frameworks. I formatted the date like this and it saved and displayed properly:

let formatter = DateFormatter()
formatter.dateFormat = "yyyy:MM:dd HH:mm:ss"
exif[ImagePropertyExifDateTimeDigitized] = formatter.string(from: Date())

This was in place of the line:

exif[ImagePropertyExifDateTimeDigitized] = Date() as CFDate

The output is now (again pruned to only the relevant properties):

unmodified properties
  {
    "{Exif}" =     {
      DateTimeDigitized = "2007:07:31 17:42:01";
      DateTimeOriginal = "2007:07:31 17:42:01";
    };
}

modified properties
  {
    "{Exif}" =     {
      DateTimeDigitized = "2017:05:12 01:04:14";
      DateTimeOriginal = "2007:07:31 17:42:01";
    };
}

saved properties
  {
    "{Exif}" =     {
      DateTimeDigitized = "2017:05:12 01:04:14";
      DateTimeOriginal = "2007:07:31 17:42:01";
    };
}

Problem

I’m trying to modify the metadata contained in a JPEG image. It can be any of the metadata in the image, in my example I’m attempting to change to `DateTimeDigitized` property to the current date. My code seems to mostly work, however the set property is being removed rather than changed. I’m not certain why this is happening, can anyone tell me what I’m doing wrong? I’d welcome any advice on frameworks that can help perform the task but I’d especially be interested in what I’m doing incorrectly with this approach. I’m running this code in a Playground where the image named “foo.jpg” is stored in the path `~/Documents/Shared Playground Data/`. ``` import Foundation import ImageIO // CGImage functions import PlaygroundSupport let ImagePropertyExifDictionary = kCGImagePropertyExifDictionary as String let ImagePropertyExifDateTimeDigitized = kCGImagePropertyExifDateTimeDigitized as String func updateEXIFDateDigitized() { // Create URL for source and destination let sourceURL = playgroundSharedDataDirectory.appendingPathComponent("foo.jpg") as CFURL let destinationURL = playgroundSharedDataDirectory.appendingPathComponent("bar.jpg") as CFURL // Read source and get properties guard let sourceRef = CGImageSourceCreateWithURL(sourceURL, nil), var metadata = CGImageSourceCopyPropertiesAtIndex(sourceRef, 0, nil) as? [String:Any] else { return } print("unmodified properties", metadata, separator:"\n") // Modify EXIF DateTimeDigitized property guard var exif = metadata[ImagePropertyExifDictionary] as? [String:Any] else { return } exif[ImagePropertyExifDateTimeDigitized] = Date() as CFDate metadata[ImagePropertyExifDictionary] = exif as CFDictionary print("", "modified properties", metadata, separator:"\n") // Set up destination guard let destinationRef = CGImageDestinationCreateWithURL(destinationURL, kUTTypeJPEG, 1, nil) else { return } // Add image from source to destination with new properties CGImageDestinationAddImageFromSource(destinationRef, sourceRef, 0, metadata as CFDictionary) // Save destination guard CGImageDestinationFinalize(destinationRef) else { return } guard let sourceRef2 = CGImageSourceCreateWithURL(destinationURL, nil), let metadata2 = CGImageSourceCopyPropertiesAtIndex(sourceRef2, 0, nil) else { return } print("", "saved properties", metadata2, separator:"\n") } updateEXIFDateDigitized() ``` The relevant bits of the result, I removed the other fields for brevity: ``` unmodified properties { "{Exif}" = { DateTimeDigitized = "2007:07:31 17:42:01"; DateTimeOriginal = "2007:07:31 17:42:01"; }; } modified properties { "{Exif}" = { DateTimeDigitized = "2017-05-11 15:45:38 +0000"; DateTimeOriginal = "2007:07:31 17:42:01"; }; } saved properties { "{Exif}" = { DateTimeOriginal = "2007:07:31 17:42:01"; }; } ```

Original source

Related problems