Straightforward approach to getting a file's metadata attributes

One way of accessing a file’s metadata attributes is via bash’s mdls command. The challenge with that approach lies in the parsing of the output text for the file’s attribute names and their values.

The current handler simplifies that process by taking advantage of the mdls command’s plist option, which saves the output as a plist file. The plist file is then readily converted to a Cocoa NSDictionary via NSDictionary’s dictionaryWithContentsOfFile: method. Finally, the NSDictionary is coerced to an AppleScript record.

The handler takes as its input argument a reference to the file whose metadata is to be accessed. The reference may be in the form of an HFS path, POSIX path, or AppleScript alias. The handler’s return value is the file’s metadata attributes in the form of an AppleScript record.

An alternative solution is the AST metadata for file command of DJ Bazzie Wazzie’s AppleScript Toolbox plugin. This command combines both execution speed and coding efficiency. It’s only drawback is the need to download and store the OSAX before it can be used.


use framework "Foundation"
use scripting additions

on fileMetadata(fileRef)
	try
		set posixPath to (fileRef as alias)'s POSIX path
	on error
		try
			set posixPath to (fileRef as POSIX file as alias)'s POSIX path
		on error
			error "File not found."
		end try
	end try
	set plistPath to do shell script "f=\"/tmp/$(uuidgen).plist\"; mdls " & posixPath's quoted form & " -plist $f; echo $f"
	set theMetadata to (current application's NSDictionary's dictionaryWithContentsOfFile:plistPath) as record
	current application's NSFileManager's defaultManager()'s removeItemAtPath:plistPath |error|:(missing value)
	return theMetadata
end fileMetadata

For example:


fileMetadata("/path/to/a/jpeg/file.jpg") --> {kMDItemKind:"JPEG image", kMDItemColorSpace:"RGB", kMDItemPixelWidth:4032, kMDItemPixelHeight:3024, ...}

Another alternative is my Metadata Lib:

https://www.macosxautomation.com/applescript/apps/Script_Libs.html

like so:

use scripting additions
use script "Metadata Lib" version "2.0.0"

set theFile to choose file
set theMetadata to fetch metadata for item theFile

But if you’re running 10.11 or later, you can also do it it directly in ASObjC:

use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

set theFile to choose file
set mdItem to current application's NSMetadataItem's alloc()'s initWithURL:theFile
set theMetadata to (mdItem's valuesForAttributes:(mdItem's attributes())) as record

Your NSMetadataItem solution returns the same results as the mdls one I presented and is lightning fast. That does appear to be the preferred approach. Very nice!

I incorporated it into my original handler so that it will accept input in the form of an HFS path, POSIX path, or AppleScript alias:


on fileMetadata(fileRef)
	try
		set fileAlias to fileRef as alias
	on error
		try
			set fileAlias to fileRef as POSIX file as alias
		on error
			error "File not found."
		end try
	end try
	set mdItem to current application's NSMetadataItem's alloc()'s initWithURL:fileAlias
	set theMetadata to (mdItem's valuesForAttributes:(mdItem's attributes())) as record
	return theMetadata
end fileMetadata

Can you kindly explain why the initWithURL: method accepts an AppleScript alias argument? I have always created an NSURL object in those situations, but using an AppleScript alias would oftentimes be easier.

Avoiding the overhead of do shell script is nearly always going to be a big win in performance.

Which is what the library version does (including abbreviated POSIX paths). You’re reinventing the wheel :wink:

As of macOS 10.11, aliases and file references («class furl») are automatically bridged to NSURLs in this sort of situation. That’s one of the reasons my code includes the AppleScript 2.5 use statement (the other being that 10.11 is also needed for automatic conversion between Cocoa dates and AppleScript dates. The library version handles both conversions pre-10.11, at some potential cost to performance.)

By the way, sadly DJ’s AppleScript Toolbox is no longer:

https://astoolbox.wordpress.com/2018/10/02/the-end-of-applescript-toolbox/

Thanks for the explanation. Very helpful info.

That’s very unfortunate. The AppleScript Toolbox was a great resource.

A minor modification was made to the handler so that it now returns an empty “record” (really an empty list) instead of throwing an error when it encounters the occasional file system item that returns no metadata attributes:


use AppleScript version "2.5"
use framework "Foundation"
use scripting additions

on fileMetadata(fileRef)
	try
		set fileAlias to fileRef as alias
	on error
		try
			set fileAlias to fileRef as POSIX file as alias
		on error
			error "File not found."
		end try
	end try
	set mdItem to current application's NSMetadataItem's alloc()'s initWithURL:fileAlias
	set theMetadata to {}
	try
		set theMetadata to (mdItem's valuesForAttributes:(mdItem's attributes())) as record
	end try
	return theMetadata
end fileMetadata

The script included below outputs a selected file’s Spotlight metadata to a text file on the desktop. A few comments:

  • Just as a matter of personal preference, attribute keys and their values are written on separate lines, but this is easily changed.
  • The script trims underscores and “kMDItem” from the front of the attribute keys.
  • Date objects are localized
  • Some attribute keys have no value, and the values are shown as “not available.”
  • The timing result with a JPG digital image was 38 milliseconds (not including file write time).
  • To test this script, open it in a script editor and run.
use framework "Foundation"
use scripting additions

on main()
	set theFile to POSIX path of (choose file)
	set theFile to current application's |NSURL|'s fileURLWithPath:theFile
	set theMetadata to current application's NSMetadataItem's alloc()'s initWithURL:theFile
	if theMetadata is missing value then errorAlert("Spotlight metadata not available for the selected file")
	set theKeys to theMetadata's attributes()'s sortedArrayUsingSelector:"localizedStandardCompare:"
	set theText to ""
	set thePattern to "(?i)^_|kMDItem"
	set text item delimiters to ", "
	repeat with aKey in theKeys
		set theValue to (theMetadata's valueForAttribute:aKey)
		set theValue to getValue(theValue)
		set aKey to (aKey's stringByReplacingOccurrencesOfString:thePattern withString:"" options:1024 range:{0, aKey's |length|()})
		set theText to theText & aKey & ":" & linefeed & theValue & linefeed & linefeed
	end repeat
	set text item delimiters to ""
	writeFile(theText)
end main

on getValue(theValue)
	try
		set isString to (theValue's isKindOfClass:(current application's NSString))
		if isString as boolean is true then return theValue as text
		set isNumber to (theValue's isKindOfClass:(current application's NSNumber))
		if isNumber as boolean is true then return theValue's stringValue() as text
		set isDate to (theValue's isKindOfClass:(current application's NSDate))
		if isDate as boolean is true then return theValue as date as text
		set isArray to (theValue's isKindOfClass:(current application's NSArray))
		if isArray as boolean is true then return theValue as list as text
	on error
		return "Not Available"
	end try
end getValue

on writeFile(theText)
	set theFile to (current application's NSHomeDirectory()'s stringByAppendingPathComponent:"Desktop")'s stringByAppendingPathComponent:"Spotlight Metadata.txt"
	(current application's NSString's stringWithString:theText)'s writeToFile:theFile atomically:true encoding:(current application's NSUTF8StringEncoding) |error|:(missing value)
end writeFile

on errorAlert(dialogMessage)
	display alert "An error has occurred" message dialogMessage as critical
	error number -128
end errorAlert

main()