Error when obtaining metadata from files

I’ve been working on a script that sorts and moves digital photos by orientation and have given up for now. The line that begins “set itemOrientation” occasionally returns missing value when clearly it shouldn’t. Anyways, I thought I’d post the handler just in case someone could spot some error.

BTW, this issue seems to arise most often when the file or files have just been copied to theFolder, and I have to wonder if the issue arises due to a delay in updating the metadata. I added some one-second delays to the script but that did not resolve the issue, and, unfortunately, my knowledge of this topic is quite limited. Thanks for the help.

on getFiles(theFolder, theOrientation)
	set fileManager to current application's NSFileManager's defaultManager()
	set theFolder to current application's |NSURL|'s fileURLWithPath:(POSIX path of theFolder)
	set folderContents to fileManager's contentsOfDirectoryAtURL:theFolder includingPropertiesForKeys:{} options:4 |error|:(missing value)
	set thePredicate to current application's NSPredicate's predicateWithFormat:"pathExtension ==[c] 'jpg'"
	set folderContents to folderContents's filteredArrayUsingPredicate:thePredicate
	
	set theFiles to current application's NSMutableArray's new()
	repeat with anItem in folderContents
		set mdItem to (current application's NSMetadataItem's alloc()'s initWithURL:anItem)
		set itemOrientation to (mdItem's valueForAttribute:"kMDItemOrientation")
		if (itemOrientation is not missing value) then
			if (itemOrientation's isEqualToNumber:theOrientation) as boolean is true then (theFiles's addObject:anItem)
		end if
	end repeat
	return theFiles as list
end getFiles

I found the issue. The digital photo is on an external SSD, and this drive is not reliably indexed, which appears to be a known issue with Monterey. I checked all Spotlight and read/write settings and forced reindexing of the drive, but this didn’t help.

You should provide variable theOrientation as NSNumber 0 or NSNumber 1. Then compare the NSIntegers this way:


use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions

set theFolder to choose folder
my getFiles(theFolder, 0) -- get landscape images

on getFiles(theFolder, theOrientation)
	-- coerce integer to NSNumber (NSNumber 0 = Landscape, NSNumber 1 = Portrait)
	set theOrientation to current application's NSNumber's numberWithInteger:0 -- ADDED
	set fileManager to current application's NSFileManager's defaultManager()
	set theFolder to current application's |NSURL|'s fileURLWithPath:(POSIX path of theFolder)
	set folderContents to fileManager's contentsOfDirectoryAtURL:theFolder includingPropertiesForKeys:{} options:4 |error|:(missing value)
	set thePredicate to current application's NSPredicate's predicateWithFormat:"pathExtension ==[c] 'jpg'"
	set folderContents to folderContents's filteredArrayUsingPredicate:thePredicate
	set theFiles to current application's NSMutableArray's new()
	repeat with anItem in folderContents
		set mdItem to (current application's NSMetadataItem's alloc()'s initWithURL:anItem)
		set itemOrientation to (mdItem's valueForAttribute:"kMDItemOrientation")
		if (itemOrientation's isEqualToNumber:theOrientation) then (theFiles's addObject:anItem) --  EDITED
	end repeat
	return theFiles as list
end getFiles

KniazidisR. Thanks for looking at my post and for the suggestion. I tested your script with a test photo, and it worked correctly if the photo is on my boot drive but returns the following error if the photo is on my external SSD:

Have a look at this page where they discuss how MetaData is lazyily fetched and they
Show how to use the AVSynchronus Loading protocol:

https://developer.apple.com/documentation/avfoundation/media_assets/loading_media_data_asynchronously?language=objc

This is by using the AVFoundation framework.
And creating a AVAsset, NSURLAsset.
Since your already working with a NSURL I would create
A NSURLAsset as it’s super easy from a NSURL.

The you can provide specific keys that you
want to load asynchronously. You can then Check the status of the
Loading to make sure it was fetched before you get the value.

https://developer.apple.com/documentation/avfoundation/avasynchronouskeyvalueloading/1387321-loadvaluesasynchronouslyforkeys?language=objc

The other route you wanna look into is NSMetaDataQuery
I believe Shane also has an appleScriptLib too
https://forum.latenightsw.com/t/spotlight-metadata-library/688

https://developer.apple.com/documentation/foundation/nsmetadataquery?language=objc

This will allow you to also set a predicate so that
It only finds certain items (ie: shootDate, photoSize etc)

When you creat the query you also provide a list of keys
That are prefetched for you. (ie: photoRotation, isLandscape etc)

Once you create the query. And then start it.
It does it’s own synchronous loading and you
Just have to adapt to its KVO protocol to be
Notified when it’s gathered data, finished gathering,
Failed etc

technomorph. Thanks for the posts and suggestions.

I had previously come to the tentative conclusion that indexing on my external drive was broken, but the idea that metadata on this drive is lazily fetched is probably the more reasonable conclusion. This doesn’t appear to be the case with the boot drive where my script returns the photo files under all circumstances.

I did test Shane’s metadata lib script library and did a minor rewrite of a section of Shane’s script, but neither recognized photos recently added to my external drive. Prefetching the required metadata seems a great idea, but I think that’s a bit beyond my current knowledge level (at least based on the referenced documentation). I also tested the mdfind shell command, but it did not resolve this issue.

use framework "Foundation"
use scripting additions

set theFolder to POSIX path of (choose folder)
set thePredicate to "(kMDItemOrientation == 1) AND (kMDItemFSName ENDSWITH[c] '.jpg')"
set theFiles to getFiles(theFolder, thePredicate)

on getFiles(theFolder, thePredicate)
	set theFolder to current application's |NSURL|'s fileURLWithPath:theFolder
	set thePredicate to current application's NSPredicate's predicateWithFormat:thePredicate
	set theQuery to current application's NSMetadataQuery's new()
	theQuery's setSearchScopes:{theFolder}
	theQuery's setPredicate:thePredicate
	theQuery's startQuery()
	repeat while theQuery's isGathering() as boolean
		delay 0.01
	end repeat
	theQuery's stopQuery()
	set theCount to theQuery's resultCount()
	set theFiles to current application's NSMutableArray's array()
	repeat with i from 0 to (theCount - 1)
		set aResult to (theQuery's resultAtIndex:i)
		set thePath to (aResult's valueForAttribute:"kMDItemPath")
		(theFiles's addObject:thePath)
		-- if ((thePath's stringByDeletingLastPathComponent())'s isEqualToString:(theFolder's |path|())) as boolean is true then (theFiles's addObject:thePath) -- instead of above to not recurse
	end repeat
	return theFiles
end getFiles