Search code examples
swiftmetadataexif

access EXIF data in swift


I try to access Exif data with this sample code :

let fileextension = NSURL(fileURLWithPath: mDir + "/" + f).pathExtension

            if fileextension == "PSD" || fileextension == "NEF" || fileextension == "3FR" || fileextension == "CR2" || fileextension == "DNG" || fileextension == "JPEG" || fileextension == "JPG" || fileextension == "PSB" || fileextension == "RAF" || fileextension == "TIF" {

                let fileattr = try fileManager.attributesOfItem(atPath: mDir + "/" + f)
                let filesize = fileattr[FileAttributeKey.size] as! Int64

                let UrlPath = URL(fileURLWithPath: mDir + "/" + f)
                let imageSource = CGImageSourceCreateWithURL(UrlPath as CFURL, nil)
                let result = CGImageSourceCopyMetadataAtIndex(imageSource!, 0, nil)
                let d = result as! [AnyHashable:Any]
                let tiffDict = d["{TIFF}"] as! [AnyHashable:Any]
                let filedate = tiffDict["DateTime"] as! Date

and I have this error :

Could not cast value of type '__NSCFType' (0x7fff89750188) to 'NSDictionary' (0x7fff89750fe8).

But this sample code work in a playground.

Any explanation ?


Solution

  • To answer the actual question ;), it looks you are calling the wrong function to get it directly as a dictionary, instead of copying metadata using CGImageSourceCopyMetadataAtIndex use CGImageSourceCopyPropertiesAtIndex instead

    if let props = CGImageSourceCopyPropertiesAtIndex(imageSource!, 0, nil) as? [String: Any], 
       let tiffData = props["{TIFF}"] {
        print(tiffData)
    }
    

    Original answer

    I used CGImageMetadataCopyTags to extract metadata as an array and then used CGImageMetadataTagCopyName and CGImageMetadataTagCopyValue to map the data into a swift dictionary

    let extensions = ["PSD",  "NEF",  "3FR",  "CR2",  "DNG",  "JPEG",  "JPG",  "PSB",  "RAF",  "TIF"]
    let fileManager = FileManager.default
    let url = URL(fileURLWithPath: mDir + "/" + f)
    
    var metaData = [String: Any]()
    if extensions.contains(url.pathExtension) {
        let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil)
        if let result = CGImageSourceCopyMetadataAtIndex(imageSource!, 0, nil) {
            if let array = CGImageMetadataCopyTags(result) as? [CGImageMetadataTag] {
                metaData = array.reduce(into: [String: Any]()) {
                    guard let name = CGImageMetadataTagCopyName($1),
                        let value = CGImageMetadataTagCopyValue($1)
                    else {
                        return
                    }
                    $0[String(name)] = value
                }
            }
        }
    }
    

    Note that this only gives the tag names like "ShutterSpeedValue", to include the type of tag like "exif", "tiff", "xmp" we can also get the prefix:

    guard let prefix = CGImageMetadataTagCopyPrefix($1), 
          let name = CGImageMetadataTagCopyName($1),
          let value = CGImageMetadataTagCopyValue($1)
    else { return }
    
    $0["\(prefix):\(name)"] = value