Modifying Image Metadata

2019-02-18 04:27发布

问题:

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

回答1:

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