Search code examples
swiftmacospng

Swift, how to get an image "where from" metadata field?


I'm reading a file from the file system on macOS, I tried to extract the metadata with the following code:

let imagedata = try? Data(contentsOf: fileUrl)

if imagedata == nil {
    print("Could not read contents of image")
    return
}

var source: CGImageSource = CGImageSourceCreateWithData((imagedata as! CFMutableData), nil)!
var metadata = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [AnyHashable: Any]

print("image metadata", metadata)

And this is the output that I get:

image metadata Optional([AnyHashable("ColorModel"): RGB, AnyHashable("DPIHeight"): 72, AnyHashable("PixelHeight"): 840, AnyHashable("ProfileName"): sRGB IEC61966-2.1, AnyHashable("PixelWidth"): 840, AnyHashable("{PNG}"): {
    Chromaticities =     (
        "0.3127",
        "0.329",
        "0.64",
        "0.33",
        "0.3",
        "0.6000000000000001",
        "0.15",
        "0.06"
    );
    Gamma = "0.45455";
    InterlaceType = 0;
    XPixelsPerMeter = 2834;
    YPixelsPerMeter = 2834;
    sRGBIntent = 0;
}, AnyHashable("DPIWidth"): 72, AnyHashable("Depth"): 8])

And that extracts the usual metadata, but I actually would like to know where the image was downloaded from, when I drag n' drop an image into the desktop and then right click and then get info, I see the field:

enter image description here

Is there any way to retrieve this field?

Edit: I tried this:

if let attr = try? FileManager.default.attributesOfItem(atPath: fileUrl.path) {
    print("attributes", attr)
}

and now I can see the kMDItemWhereFroms field, but I don't know how to reach it or parse. Maybe someone has an idea here?


Solution

  • I think it's always nice to show how tinker, how to find the data, so I'll show how I got it, while I didn't know at all about it.

    First thought, it might not be in image Metadata, but in "File System" ones, so let's look at FileManager. It's in FileManager.attributesOfItem(atPath:)

    Step 1:

    let attributes = try fileManager.attributesOfItem(atPath: filePath)
    

    This gives me:

    [__C.NSFileAttributeKey(_rawValue: NSFileGroupOwnerAccountName): staff, 
    __C.NSFileAttributeKey(_rawValue: NSFileReferenceCount): 1, 
    __C.NSFileAttributeKey(_rawValue: NSFileModificationDate): 2020-05-13 15:16:20 +0000, 
    __C.NSFileAttributeKey(_rawValue: NSFileSystemFileNumber): 8635383780, 
    __C.NSFileAttributeKey(_rawValue: NSFilePosixPermissions): 420, 
    __C.NSFileAttributeKey(_rawValue: NSFileType): NSFileTypeRegular, 
    __C.NSFileAttributeKey(_rawValue: NSFileOwnerAccountID): 501, 
    __C.NSFileAttributeKey(_rawValue: NSFileGroupOwnerAccountID): 20, 
    __C.NSFileAttributeKey(_rawValue: NSFileExtensionHidden): 0, 
    __C.NSFileAttributeKey(_rawValue: NSFileHFSTypeCode): 0, 
    __C.NSFileAttributeKey(_rawValue: NSFileCreationDate): 2020-05-13 15:16:20 +0000, 
    __C.NSFileAttributeKey(_rawValue: NSFileOwnerAccountName): Larme, 
    __C.NSFileAttributeKey(_rawValue: NSFileSize): 1258299, 
    __C.NSFileAttributeKey(_rawValue: NSFileSystemNumber): 16777220, 
    __C.NSFileAttributeKey(_rawValue: NSFileExtendedAttributes): {
        "com.apple.lastuseddate#PS" = {length = 16, bytes = 0x440fbc5e00000000503f091700000000};
        "com.apple.macl" = {length = 72, bytes = 0x02009685 15175d5d 47089537 71b6a786 ... 00000000 00000000 };
        "com.apple.metadata:kMDItemDownloadedDate" = {length = 53, bytes = 0x62706c69 73743030 a1013341 c2362362 ... 00000000 00000013 };
        "com.apple.metadata:kMDItemWhereFroms" = {length = 414, bytes = 0x62706c69 73743030 a201025f 11013b68 ... 00000000 00000178 };
        "com.apple.quarantine" = {length = 57, bytes = 0x30303833 3b356562 63306634 343b5361 ... 44344445 45343845 };
    }, __C.NSFileAttributeKey(_rawValue: NSFileHFSCreatorCode): 0]
    

    What to look in this info:

    [...
    __C.NSFileAttributeKey(_rawValue: NSFileExtendedAttributes): {
    ...
        "com.apple.metadata:kMDItemWhereFroms" = {length = 414, bytes = 0x62706c69 73743030 a201025f 11013b68 ... 00000000 00000178 };
    ...
    ]
    

    Ok, so let's continue to step 2, let's dig deeper:

    let extendedAttributes = attributes[FileAttributeKey(rawValue: "NSFileExtendedAttributes")] as! [String: Any]
    let data = extendedAttributes["com.apple.metadata:kMDItemWhereFroms"] as! Data //that's the new output for `Data` in new systems (iOS13+, etc.)
    

    As always, I tend to do just in case to get more info: let str = String(data: data, encoding: .utf8) which returns nil. Let's remember that not all data is convertible into utf8 string like that. For instance, an image won't make it.

    But I know a few tricks, let's do instead: let str = String(data: data, encoding: .ascii), this gives me:

    "bplist00¢\u{01}\u{02}_\u{11}\u{01};https://doc-04-6k-docs.googleusercontent.com/docs/securesc/teo5l5s1g996hre86q7qv786r7me56c3/3nneqai4sh4lvk3bdlnpokt8793t4t5t/1589382975000/11847256646728493158/11847256646728493158/0B1U9OypmOvxpODVFb2VGZEdpckk?e=download&authuser=0&nonce=faav7fnl402re&user=11847256646728493158&hash=vl51p8m313rnnao3dleqsl348rp2vo82_\u{10}+https://drive.google.com/drive/u/0/my-drive\0\u{08}\0\u{0B}\u{01}J\0\0\0\0\0\0\u{02}\u{01}\0\0\0\0\0\0\0\u{03}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\u{01}x"
    

    I downloaded my sample from my Google Drive, info seems making sense. What do see there?: bplist, that's the most important one here.

    A PropertyList? Let's decode it then:

    let urls = try PropertyListDecoder().decode([String].self, from: data)
    

    It made sense for me to be a [String], it would have been different, that would had be harder. But it's a simple one.

    Final code:

    static func downloadedFromURLs(for filePath: String) -> [String]? {
        let fileManager = FileManager.default
        guard fileManager.fileExists(atPath: filePath) else { print("File doesn't exists"); return nil }
    
        do {
            let attributes = try fileManager.attributesOfItem(atPath: filePath)
            guard let extendedAttributes = attributes[FileAttributeKey(rawValue: "NSFileExtendedAttributes")] as? [String: Any] else {
                print("Didn't find NSFileExtendedAttributes value")
                return nil
            }
            guard let data = extendedAttributes["com.apple.metadata:kMDItemWhereFroms"] as? Data else {
                print("Didn't find com.apple.metadata:kMDItemWhereFroms value")
                return nil
            }
            let urls = try PropertyListDecoder().decode([String].self, from: data)
            return urls
        } catch {
            print("Error: \(error)")
            return nil
        }
    }
    

    Side note: There might be some constant instead of writing hard coded strings for "com.apple.metadata:kMDItemWhereFroms" and NSFileExtendedAttributes, but I didn't see it here.

    Some related interested discussion. https://apple.stackexchange.com/questions/110239/where-is-the-where-from-meta-data-stored-when-downloaded-via-chrome