Skip to content

Instantly share code, notes, and snippets.

@nyg
Last active January 22, 2024 13:41
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save nyg/c90f36abbd30f72c8b6681ef23db886b to your computer and use it in GitHub Desktop.
Save nyg/c90f36abbd30f72c8b6681ef23db886b to your computer and use it in GitHub Desktop.
Get and set an EXIF UserComment to a JPEG image using the ImageIO framework.
// Note: to add a JPEG COM marker go here:
// https://gist.github.com/nyg/bdeae8190a41b4b56bde8e13dd471ecc
import Foundation
import ImageIO
#if os(iOS)
import MobileCoreServices
#endif
// See CGImageMetadataCopyTagWithPath documentation for an explanation
let exifUserCommentPath = "\(kCGImageMetadataPrefixExif):\(kCGImagePropertyExifUserComment)" as CFString
/* Set EXIF UserComment */
// using metadata functions
func setEXIFUserComment(_ comment: String, using sourceURL: URL, destination destinationURL: URL) {
guard let imageDestination = CGImageDestinationCreateWithURL(destinationURL as CFURL, kUTTypeJPEG, 1, nil)
else { fatalError("Image destination not created") }
guard let metadataTag = CGImageMetadataTagCreate(kCGImageMetadataNamespaceExif, kCGImageMetadataPrefixExif, kCGImagePropertyExifUserComment, .string, comment as CFString)
else { fatalError("Metadata tag not created") }
let metadata = CGImageMetadataCreateMutable()
CGImageMetadataSetTagWithPath(metadata, nil, exifUserCommentPath, metadataTag)
guard let imageSource = CGImageSourceCreateWithURL(sourceURL as CFURL, nil)
else { fatalError("Image source not created") }
guard let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil)
else { fatalError("Image not created from source") }
CGImageDestinationAddImageAndMetadata(imageDestination, image, metadata, nil)
CGImageDestinationFinalize(imageDestination)
}
// using image property functions
func setEXIFUserComment2(_ comment: String, using sourceURL: URL, destination destinationURL: URL) {
guard let outputImage = CGImageDestinationCreateWithURL(destinationURL as CFURL, kUTTypeJPEG, 1, nil)
else { fatalError("Image destination not created") }
guard let imageSource = CGImageSourceCreateWithURL(sourceURL as CFURL, nil)
else { fatalError("Image source not created") }
let exifDictionary: [NSString: AnyObject] = [ kCGImagePropertyExifUserComment: comment as CFString ]
let properties: [NSString: AnyObject] = [ kCGImagePropertyExifDictionary: exifDictionary as CFDictionary ]
CGImageDestinationAddImageFromSource(outputImage, imageSource, 0, properties as CFDictionary)
CGImageDestinationFinalize(outputImage)
}
/* Get EXIF UserComment */
// using image metadata functions
func getEXIFUserComment(from image: URL) -> String? {
guard let imageSource = CGImageSourceCreateWithURL(image as CFURL, nil)
else { fatalError("Image source not created") }
guard let metadata = CGImageSourceCopyMetadataAtIndex(imageSource, 0, nil)
else { return nil }
// // Solution 1
//
//// // Solution 1a
//// guard let userCommentTag = CGImageMetadataCopyTagWithPath(metadata, nil, exifUserCommentPath)
//// else { return nil }
////
//// // Solution 1b
//// guard let userCommentTag = CGImageMetadataCopyTagMatchingImageProperty(metadata, kCGImagePropertyExifDictionary, kCGImagePropertyExifUserComment)
//// else { return nil }
//
// guard let userComment = CGImageMetadataTagCopyValue(userCommentTag) as? String
// else { return nil }
// Solution 2
guard let userComment = CGImageMetadataCopyStringValueWithPath(metadata, nil, exifUserCommentPath) as String?
else { return nil }
return userComment
}
// using image property functions
func getEXIFUserComment2(from image: URL) -> String? {
guard let imageSource = CGImageSourceCreateWithURL(image as CFURL, nil)
else { fatalError("Image source not created") }
guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [NSString: AnyObject],
let exifDictonary = properties[kCGImagePropertyExifDictionary],
let userComment = exifDictonary[kCGImagePropertyExifUserComment] as? String
else { return nil }
return userComment
}
/* Debug */
func getProperties(from image: URL) -> [NSString: AnyObject]? {
guard let source = CGImageSourceCreateWithURL(image as CFURL, nil)
else { fatalError("Image source not created") }
return CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [NSString: AnyObject]
}
func getMetadata(from image: URL) -> [[String: Any]]? {
guard let imageSource = CGImageSourceCreateWithURL(image as CFURL, nil)
else { fatalError("Image source not created") }
guard let metadata = CGImageSourceCopyMetadataAtIndex(imageSource, 0, nil)
else { return nil }
guard let tags = CGImageMetadataCopyTags(metadata) as? [CGImageMetadataTag]
else { return nil }
return tags.map { tag in
var info = [String: Any]()
if let namespace = CGImageMetadataTagCopyNamespace(tag) as String? {
info["namespance"] = namespace
}
if let prefix = CGImageMetadataTagCopyPrefix(tag) as String? {
info["prefix"] = prefix
}
if let name = CGImageMetadataTagCopyName(tag) as String? {
info["name"] = name
}
if let value = CGImageMetadataTagCopyValue(tag) as AnyObject? {
info["value"] = value
}
info["type"] = CGImageMetadataTagGetType(tag).rawValue
if let qualifiers = CGImageMetadataTagCopyQualifiers(tag) as? [CGImageMetadataTag] {
info["qualifiers"] = qualifiers
}
return info
}
}
/* Testing */
guard let sourceURL = Bundle.main.url(forResource: "jpg_comm", withExtension: "jpg")
else { fatalError("Image not found") }
print(getEXIFUserComment(from: sourceURL) ?? "No EXIF UserComment")
//print(getProperties(from: sourceURL) ?? "No properties")
//print(getMetadata(from: sourceURL) ?? "No metadata")
guard let destinationURL = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent("img2.jpg")
else { fatalError("Destination URL not created") }
setEXIFUserComment("Here is an EXIF UserComment!", using: sourceURL, destination: destinationURL)
print(getEXIFUserComment(from: destinationURL) ?? "No EXIF UserComment")
//print(getProperties(from: destinationURL) ?? "No properties")
//print(getMetadata(from: destinationURL) ?? "No metadata")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment