Retrieving Spotlight metadata

I did use mdls in the past. But I want to move away from UNIX. Using UNIX is almost always over-kill for whatever I’m doing. To me UNIX is just a pile of ridiculously named commands using tedious arguments that lead to results I often have to format to use.

It wasn’t hard to write this last script in ASObj-C. I have so many options to choose from ASObj-C. In the end for me things are always simpler without UNIX. Unix is alway a pain to work with, commands have cryptic names and the documentation is always harder to figure. ASObj-C make sense and is easier to use.

I type things into a script editor but don’t hit run in script editor. I save the script and run it from Fast Scripts. Running AppKit from a script editor is almost impossible. While Foundation is less problematic it still causes some problems. I always run the script from fast scripts. I don’t have any noticeably harder time doing it that way then running it from SD. Basically I have a different process for working with ASObj-C. The process is not that much harder then staying in SD the entire time. That’s why I keep choosing to write things in ASObj-C. When I only stay with the familiar I miss out on learning more powerful things like ASObj-C.

I can’t see UNIX as anything but being based on ancient syntax that has chosen to stay cryptic like its some kind of badge of honor while other systems grew into more user friendly systems.

Bill

I’ve never encountered any before this script. I can’t see why the issue I’m seeing would have anything to do with the call to “Foundation” framework in itself, but I haven’t tried to debug your script.

We all have our preferences. I prefer to be versatile. I’ll use whatever does the job at hand that’s either or both easiest for me and/or most effective for my users.

And while I won’t try to persuade you otherwise if you don’t like UNIX (i guess you mean using command line tools), I would point out that i. age is a testament to stability and ii. hey, if age is an argument against utilty, I think we’re all in trouble! :blush:

Understood. But I think most people know of the mdls method, and this thread was largely about pointing out that there’s another way.

Well, Bill did start this thread with:

I was trying to get the kMDItemPixelHeight and kMDItemPixelWidth metadata for downloaded you tube videos. But I was never able to figure out how to do it.

so I thought it was appropriate to offer what I saw as a far easier way to go about doing that.

Going back to your preferred method, I have problems with this:[quote=“ShaneStanley, post:18, topic:483”]
if (LengthInSeconds ≠ missing value) then
set theDate to current application’s NSDate’s dateWithTimeIntervalSinceReferenceDate:LengthInSeconds
set dFormatter to current application’s NSDateFormatter’s new()
dFormatter’s setDateFormat:"HH:mm:ss"
set VideoLength to (dFormatter’s stringFromDate:theDate) as text
else
set VideoLength to missing value
end if
[/quote]

For a video that’s 44:55 seconds, your method returns 7hrs 44m 55s. I suspect the 7hrs is the local time zone difference (I’m GMT+ 7 here), and my attempts to play around with setLocale didn’t work out (I always have a nightmare whenever dealing with NSDate and NSDateFormatter). Instead I wrote a simple mathematical handler to convert the seconds into an h:m:ss format. I posted a revised version of my script including that and a file size handler here.

Well that fell flat :disappointed:

When I said “just for the sake of it”, I wasn’t really suggesting it was a good idea – just as well, considering it was missing one important line:

	dFormatter's setTimeZone:(current application's NSTimeZone's timeZoneForSecondsFromGMT:0)

Even that’s probably not enough to redeem it.

Can we get a sample?

Ah, setTimeZone…I always hunt around like a headless chicken among all those timezone, data, calendar, etc APIs.

Sent you what I could find re sample and console logs. I see I have two email addresses for you now. I used the ‘myriad’ one.

Phil,
I see what the problem is when you work with date stuff. Headless chickens don’t have eyeballs. So the next time you work with timezones, data or calendars try imitating a chicken with it’s head still on :slight_smile:

Shane,

It kept bugging me not doing the video time with ASObj-C so I yet again changed my previously posted script that I was never going to change again and changed the way the video time length was calculated.

One thing I couldn’t figure out was I had to comment out 1 line to get my new code using ASObj-C to work. As the script is now it works but I was wondering why the commented out line didn’t work. The default action will only display the time parts actually needed. But I worked on it a bit and now I’m very curious what UnitsStyle is supposed to be set to.

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

set NumberOfSeconds to 3680
set TimeFormatter to current application's NSDateComponentsFormatter's alloc()'s init()
TimeFormatter's setIncludesApproximationPhrase:no
TimeFormatter's setIncludesTimeRemainingPhrase:no

--  TimeFormatter's setAllowedUnits:{NSCalendarUnitHour, NSCalendarUnitMinute, NSCalendarUnitSecond}
TimeFormatter's setUnitsStyle:(current application's NSDateComponentsFormatterUnitsStylePositional)

set TimeString to TimeFormatter's stringFromTimeInterval:NumberOfSeconds

Bill

Yes, you’re now using what I should have used!

They’re enums, not constants. So:

TimeFormatter's setAllowedUnits:((current application's NSCalendarUnitHour) + (get current application's NSCalendarUnitMinute) + (get current application's NSCalendarUnitSecond))

You set it to what you want – you’ve set it to the default anyway. Try changing it to something like NSDateComponentsFormatterUnitsStyleShort to see what happens. The options are listed under NSDateComponentsFormatterUnitsStyle in the docs.

Shane,

I never though of adding them. In that case I assume each enum has only a single 1 in the binary number and the sum of the bits forms a bit mask.

The Apple docs for NSDateComponentsFormatterUnitsStyle say “Constants for specifying how to spell out unit names.” But that same page below has “typedef enum NSDateComponentsFormatterUnitsStyle : NSInteger {”

How can I tell look at the declaration below and tell if they represents bits.

typedef enum NSDateComponentsFormatterUnitsStyle : NSInteger {
    NSDateComponentsFormatterUnitsStylePositional = 0,
    NSDateComponentsFormatterUnitsStyleAbbreviated,
    NSDateComponentsFormatterUnitsStyleShort,
    NSDateComponentsFormatterUnitsStyleFull,
    NSDateComponentsFormatterUnitsStyleSpellOut,
    NSDateComponentsFormatterUnitsStyleBrief
} NSDateComponentsFormatterUnitsStyle;

If I click the NSDateComponentsFormatterUnitsStylePositional link on the same page it brings up a new page that says the value is equal to zero. But none of the other links for the other enums say what the value is.

I tried reading the value directly in AppleScript with:

-- Line 1
current application's NSDateComponentsFormatterUnitsStyleAbbreviated as integer

-- Line 2
(current application's NSDateComponentsFormatterUnitsStyleAbbreviated) + 1

-- Line 3
set TheVar to (current application's NSDateComponentsFormatterUnitsStyleAbbreviated) + 1

-- Line 4
TheVar

Which produces pretty weird results.

Line 1 doesn’t produce an error but also produces no result. In line 2 I can add 1 to the value of the enum and I don’t get an error but I also get no result. In line 3 I can set the value of a variable to the enum + 1 and when the value of the variable is read it does produce a result which is one.

set TheVar2 to (current application's NSDateComponentsFormatterUnitsStyleShort) + 0
TheVar2 --> 

set TheVar2 to (current application's NSDateComponentsFormatterUnitsStyleAbbreviated) + 0
TheVar2 --> 1

set TheVar2 to (current application's NSDateComponentsFormatterUnitsStyleShort) + 0
TheVar2 --> 2

set TheVar2 to (current application's NSDateComponentsFormatterUnitsStyleFull) + 0
TheVar2 --> 3

set TheVar2 to (current application's NSDateComponentsFormatterUnitsStyleSpellOut) + 0
TheVar2 --> 4

-- But the 2 lines below both return errors saying they can't get the value for the enum
--	TheVar2 to (current application's NSDateComponentsFormatterUnitsStyleBrief) + 0
--	set TheVar2 to (current application's NSDateComponentsFormatterUnitsStyle) + 0

set TheVar2 to (current application's NSDateComponentsFormatterUnitsStyleBrief) + 0
TheVar2 --> 3

set TheVar2 to (current application's NSDateComponentsFormatterUnitsStyle) + 0
TheVar2 --> 3

The second list of examples implies the enum values were 1, 2, 3, 4, error, error. This does not suggest they are number with a single bit set. But if that is true then adding them together makes no sense.

How can I find the value of an enum? Is there a way to tell if these enums are numbers with just a single bit set? Is adding the values of enums something you do because the method only calles for a single input and I need multiple enums to specify what I want?

Bill

Not necessarily. NSDateComponentsFormatterUnitsStyle values are not, and don’t get added – the values are mutually exclusive. NSCalendarUnits are bit masks, and get added.

The fact that there are no values for all but the first means each is incremented by 1, which usually means they’re not bitmasks. But the best way to tell is to see if they look mutually exclusive, or if the docs refer to a mask.

It produces 1 here.

Type it in Script Debugger and hit F5 for code-completion; you’ll see the values in the list that appears.

You’re working from the wrong end of things. Consider what makes sense from the description and whether the values are mutually exclusive.

Right – it wants a bit mask, and this is the AS equivalent.

Shane,

I used code completion to find the values for each enum. I added the table below showing the decimal number, binary equivalent and then the enum name:
0 000 NSDateComponentsFormatterUnitsStylePositional
1 001 NSDateComponentsFormatterUnitsStyleAbbreviated
2 010 NSDateComponentsFormatterUnitsStyleShort
3 011 NSDateComponentsFormatterUnitsStyleFull
4 100 NSDateComponentsFormatterUnitsStyleSpellOut
5 101 NSDateComponentsFormatterUnitsStyleBrief

The enums themselves seem like something where I should only pick one. I would expect that to be a single 1 bit. That’s the way I always seen it done in the past before I was a scripter. Maybe I’m out of date.

If I add NSDateComponentsFormatterUnitsStyleAbbreviated and NSDateComponentsFormatterUnitsStyleShort together I get 011 binary which is equal to NSDateComponentsFormatterUnitsStyleFull. But adding abbreviated style to short style and getting full style doesn’t make any sense to me.

Abbreviated style is the shortest spelling for unit values. Short style uses abbreviations, but not the shortest abbreviations. Full style fully spells out the units. They can’t all be true at the same time.

I am definitely missing something here.

Bill

Yes: the point of my message :slight_smile:

The only time the binary values matter is when multiple values can be used at the same time; if they can’t, as in your example of NSDateComponentsFormatterUnitsStyle, the numbers can be anything (and are often consecutive integers, as in your example). If there’s no value listed, it’s the previous value + 1.

And even where enums are used as bitmasks – like NSCalendarUnitHour and NSCalendarUnitMinute – there can be multiple bits involved.

I think you’re focusing too much on implementation. Ignore the numbers.

Shane,

Sorry I misremembered which one you added the values with. I was thinking you added the ones for UnitsStyle instead of AllowedUnits. It didn’t make sense for the UnitsStyle. When reread what you actually wrote it was setAllowedUnits where you added. That does make sense to me. I can choose a number of units, but only 1 style. That was a bonehead mistake so I guess its time for me to got to bed. Thanks for your help.

Bill

For anyone following along, I still haven’t worked out why @sphil is the only person here seeing freezes, but I do have a suspect, or at least serious accomplice, in the frame.

Bill’s using a script object to return values. When the script object is removed from the code, the problem goes away.

I know that in ASObjC-based Xcode projects, the script object structure is actually used to define Objective-C subclasses, and I wonder if something similar is happening here. So I’d suggest @BillKopp delete the use of a script object to return results; a record should provide similar capability, if needed.

It might also be worth asking if anyone who ISN’T seeing a UI freeze from this script is running it on 10.11.6 (15G1217). That might help determine if it’s OS specific or not.

Shane,

SD does freeze up on me if I let it go far enough in the script. I normally don’t get a freeze like that but this script does freeze. But the script runs fine when it is run from Fast Scripts. So I change it, compile it and run it from fast scripts. So now you know of 2 people who have it freeze. As long as it didn’t freeze while being run from fast scripts it’s usable. I don’t know what freezes but it is towards the end of the script.

Bill

FYI, Bill, it’s this line:


		if (not (Successful of TheResult)) then # <--script stops here
				return false
			end if
			set WindowLevelOffest to WindowLevelOffest + 1
		end repeat
	end if

Phil & Shane,

On 3/20/2017 at 4:43pm pacific time I changed this script. Originally the script handlers MakeMiniBBEditWin and CreateResults just returned true or false. But this is not very modifiable. Many changes need to be made just to return a second value from the handler. So now the handlers return a record and it is easy to add more things to be returned from the script.


I changed the handlers to return true or false instead of setting something in a script object & returning the scrip object. This no longer freezes when I run it in SD. True is successful and false is unsuccessful.

Here is the script:

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

-- This script sends a sportligh search request to a single file at a time and sets the searchbounds to be the enclosing folder the file is in.
-- So it is a 1 file search.  But after doing this 1 file search all the spotligh atributes for the file can be accessed.

property AceptableFileExtensions : {"mp4", "3gp"} -- Add more video file extentions as I come across them
property WindowLevelOffest : 0

on MakeMiniBBEditWin(WindowName, DocText)
	-- Used a script object here in case I ever want to pass something back from this handler other then it succed or failed
	set ReturnRecord to {Successful:false}
	try
		tell application "BBEdit"
			make new document with properties {name:WindowName, text:DocText, bounds:{50 + (WindowLevelOffest * 40), 60 + (WindowLevelOffest * 40), 700 + (WindowLevelOffest * 40), 100 + (WindowLevelOffest * 40)}} -- {left, top, right, bottom}
		end tell
		
		set Successful of ReturnRecord to true
		return ReturnRecord
	on error errMsg number errNum
		display dialog "Error " & (errNum as string) & " occured while creating the BBEdit document." & return & return & errMsg ¬
			with title "Error"
		set Successful of ReturnRecord to false
		return ReturnRecord
	end try
end MakeMiniBBEditWin

-------------------------------------------------------------

on CreateResults(VideoName, PixelHeight, PixelWidth, FileSize, ItemKind, LengthInSeconds, AudioBitRate, VideoBitRate)
	-- Creates the text to be displayed for the user.  Most of this handler creates text to the MakeMiniBBEditWin handler which will display the output in a window
	
	-- Used a record here in case I ever want to pass something back from this handler other then it succed or failed
	set ReturnRecord to {Successful:false}
	
	set TheText to ""
	try
		-- This creates a special output for the file size.  Out put the number of kb, mb, tb, ... then displays the exact number of bytes to the right of the first number
		if (FileSize ≠ missing value) then
			-- Calculate the file size 
			set SizeFormatter to current application's NSByteCountFormatter's alloc()'s init()
			SizeFormatter's setCountStyle:(current application's NSByteCountFormatterCountStyleDecimal)
			SizeFormatter's setIncludesActualByteCount:true
			set ConvertedFileSize to SizeFormatter's stringFromByteCount:FileSize
		else
			set ConvertedFileSize to missing value
		end if
		
		if (VideoName ≠ missing value) then
			set TheText to TheText & "Video name: " & VideoName & return & return
		else
			set TheText to TheText & "Video name: Could not find the video file name" & return & return
			set VideoName to "Untitled"
		end if
		
		if ((PixelHeight ≠ missing value) and (PixelWidth ≠ missing value)) then
			set TheText to TheText & "Video size: " & PixelHeight & " X " & PixelWidth & return & return
		else if ((PixelHeight = missing value) and (PixelWidth = missing value)) then
			set TheText to TheText & "Video size: " & "\"Pixel height\" and \"pixel width\" not found." & return & return
		else if (PixelHeight = missing value) then
			set TheText to TheText & "Video size: " & "\"Pixel height not found.\"" & " X " & PixelWidth & return & return
		else if (PixelWidth = missing value) then
			set TheText to TheText & "Video size: " & PixelHeight & " X " & "\"Pixel width not found.\"" & return & return
		end if
		
		if (ConvertedFileSize ≠ missing value) then
			set TheText to TheText & "File size: " & ConvertedFileSize & return & return
		else
			set TheText to TheText & "File size: " & "missing value" & return & return
		end if
		
		if (LengthInSeconds ≠ missing value) then
			set TimeFormatter to current application's NSDateComponentsFormatter's alloc()'s init()
			TimeFormatter's setIncludesApproximationPhrase:no
			TimeFormatter's setIncludesTimeRemainingPhrase:no
			TimeFormatter's setUnitsStyle:(current application's NSDateComponentsFormatterUnitsStylePositional)
			set TimeString to TimeFormatter's stringFromTimeInterval:LengthInSeconds
			
			set TheText to TheText & "Video length: " & TimeString & return & return
		else
			set TheText to TheText & "Video length: " & "missing value" & return & return
		end if
		
		if (ItemKind ≠ missing value) then
			set TheText to TheText & "Item Kind: " & ItemKind & return & return
		else
			set TheText to TheText & "Item Kind: " & "missing value" & return & return
		end if
		if (VideoBitRate ≠ missing value) then
			set TheText to TheText & "Video Bit Rate: " & VideoBitRate & return & return
		else
			set TheText to TheText & "Video Bit Rate: " & "missing value" & return & return
		end if
		if (AudioBitRate ≠ missing value) then
			set TheText to TheText & "Audio Bit Rate: " & AudioBitRate
		else
			set TheText to TheText & "Audio Bit Rate: " & "missing value" & return & return
		end if
		set TheResult to MakeMiniBBEditWin(VideoName, TheText)
		if (not (Successful of TheResult)) then
			set (Successful of ReturnRecord) to false
			return ReturnRecord
		end if
		
		set (Successful of ReturnRecord) to true
		return ReturnRecord
	on error errMsg number errNum
		display dialog "Error " & (errNum as string) & " occured while creating the results to display." & return & return & errMsg buttons {"OK"} default button ¬
			"OK" with title "Error"
		set (Successful of ReturnRecord) to false
		return ReturnRecord
	end try
end CreateResults

-------------------------------------------------------------

on Make2Digits(TheNumber)
	-- Make sure 2 digits number string are always retuned even when given single digit numbers
	if (TheNumber < 10) then
		return "0" & (TheNumber as text)
	else
		return (TheNumber as text)
	end if
end Make2Digits

-------------------------------------------------------------

on SeachAndReplace(SearchStr, ReplaceStr, TheStr)
	set OldASDelimiters to text item delimiters of AppleScript
	set text item delimiters of AppleScript to {SearchStr as string}
	set TheList to text items of TheStr
	set text item delimiters of AppleScript to {ReplaceStr as string}
	set TheNewStr to TheList as string
	set text item delimiters of AppleScript to OldASDelimiters
	return TheNewStr
end SeachAndReplace

-------------------------------------------------------------

-- This is used to offset the window horizonatally and vertically when multiple windows are produced so the windows don't all end up completely covering the window below it.
set WindowLevelOffest to 0

tell application "Finder"
	-- This creates a list of the files selected in Finder
	set FileList to selection
	
	-- Checks that something is selected in Finder
	if (FileList = {}) then
		display dialog "!There are no items selected in the Finder.  Please select some video files." buttons {"OK"} default button "OK" with title "Error"
		return false
	end if
end tell

-- This assumes the AceptableFileExtensions list is complete and if a file extension does not appear in the list it is not a video file.
-- In practice if I run across a video not in the list I just add it to the AceptableFileExtensions list and eventually it will get fairly complete
if (FileList ≠ {}) then
	tell application "Finder"
		repeat with TheFile in FileList
			if (AceptableFileExtensions does not contain ((name extension of TheFile) as text)) then
				display dialog "1 or more itmes selected in the Finder is not a video file." buttons {"OK"} default button "OK" with title "Error"
				return false
			end if
		end repeat
	end tell
	
	-- This loop goes through all the files selected in Finder, determines the infor to display for each file and display that data in a data window
	repeat with TheFile in FileList
		-- Get the file path as a NSString
		tell application "Finder" to set TheFilePath to (current application's NSString's stringWithString:(POSIX path of (TheFile as text)))
		set FileName to TheFilePath's lastPathComponent() -- Uses NSString's lastPathComponent function
		
		-- NOTE: If there is a backslash in FileName then both AppleScript and the predicate will consider 2 backslashes in a string to represent
		-- a single backslash when the string is evaluated.  So the string 123\45 would be represented by 123\\\\45.  The predicate would 
		-- see 123\\\\45 as representing 123\\45 and later on apple would see 123\\45 as representing 123\\45.
		-- So if an backslashes are detected in FileName is changed to 4 backslashes.
		if ((offset of "\\" in (FileName as text)) ≠ 0) then
			-- There is at least 1 backslash in the filename
			set FileName to SeachAndReplace("\\", "\\\\", (FileName as text)) -- Replace every backslash in file name with 4 backslashes
		end if
		
		set EnclosingFolderPath to TheFilePath's stringByDeletingLastPathComponent() -- Uses NSString's stringByDeletingLastPathComponent function
		
		--This creates a SearchPredicate to add to the SearchQuery
		set SearchPredicate to current application's NSPredicate's predicateWithFormat_("kMDItemFSName == %@", FileName)
		-- Creates a blank SearchQuery
		set SearchQuery to current application's NSMetadataQuery's alloc()'s init()
		
		(SearchQuery's setPredicate:SearchPredicate) --Adds the Predicate to the SearchQuery
		(SearchQuery's setSearchScopes:{EnclosingFolderPath}) -- Tells SearchQuery where to search
		SearchQuery's startQuery() -- Starts the search process
		
		-- Once the search has stopped gathering information "isGathering" turns to false and control drops out of the loop
		-- Then the script starts process line in the script after the loop
		repeat while SearchQuery's isGathering() as boolean
			delay 0.01
		end repeat
		
		-- This tell the query the search is done
		SearchQuery's stopQuery()
		
		-- This is a check in case the file  being access has none attributes being looked for.  If this is the case then script returns a default response and quits.
		if ((SearchQuery's results()) as list = {}) then
			-- The results list is empty meaning nothing was found
			set ErrorText to "Unable to find any metadata information for the file."
			my MakeMiniBBEditWin(FileName as text, ErrorText)
			return false
		end if
		
		-- The output for the search is returned in a array.  The results that contain the info being looked for are in the 0th position in the array
		set MetadataItem to (SearchQuery's resultAtIndex:0)
		
		-- This part tell the MetadataItem what thing to create a dictionary for.
		set TheDictionary to (MetadataItem's valuesForAttributes:{current application's kMDItemPixelHeight, current application's kMDItemPixelWidth, current application's kMDItemFSSize, ¬
			current application's kMDItemKind, current application's kMDItemAudioBitRate, current application's kMDItemVideoBitRate, current application's kMDItemFSName, ¬
			current application's kMDItemDurationSeconds})
		
		-- This part reads those values from the dictionary
		set PixelHeight to (TheDictionary's objectForKey:"kMDItemPixelHeight") as text
		set PixelWidth to (TheDictionary's objectForKey:"kMDItemPixelWidth") as text
		set FileSize to (TheDictionary's objectForKey:"kMDItemFSSize")
		set ItemKind to (TheDictionary's objectForKey:"kMDItemKind") as text
		set AudioBitRate to (TheDictionary's objectForKey:"kMDItemAudioBitRate") as text
		set VideoBitRate to (TheDictionary's objectForKey:"kMDItemVideoBitRate") as text
		set VideoName to (TheDictionary's objectForKey:"kMDItemFSName") as text
		set LengthInSeconds to (TheDictionary's objectForKey:"kMDItemDurationSeconds") as real
		
		-- Call the handler that creates the text to be displayed for the user and calls a handler to display that text
		set TheResult to my CreateResults(VideoName, PixelHeight, PixelWidth, FileSize, ItemKind, LengthInSeconds, AudioBitRate, VideoBitRate)
		if (not (Successful of TheResult)) then
			return false
		end if
		set WindowLevelOffest to WindowLevelOffest + 1
	end repeat
end if

Bill

I changed the script I posted yesterday which showed a new script that wouldn’t cause Script Debugger to freeze. I just now change the script I posted yesterday.

The newest post now starts out saying it have been changed and gives the time and date of the change as well as the reason why the change was made. Script Debugger does not freeze when running with this newest version.

It appears returning a script object from a handler can be problematic. So I switched to returning a record which does not cause problems.

Bill

I’m resurrecting this thread to point to a discussion you might be interested in elsewhere:

http://macscripter.net/viewtopic.php?id=45629

If you look at posts #16 and #19, they’re examples of the two different approaches. Whatever.

But what’s perhaps more interesting is near the end of the discussion. it appears that mdls has an issue with Unicode characters that (potentially) consist of more than one code point. So the character é will be returned as the string e\U0301. This only happens when the field is one that contains a list of strings, such as the kMDItemAuthors field.

All of which suggests the best choice may not be the simplest.