Retrieving Spotlight metadata

finder
asobjc
foundation

#1

Shane,

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. The script below shows what I was doing. But now matter what I put for the first argument in the MDItemCreateWithURL call I always got an “unrecognized selector sent to object” error. I included a sample of the error below.


-[BAGenericObjectNoDeleteOSAID MDItemCreateWithURL:]: unrecognized selector sent to object <BAGenericObjectNoDeleteOSAID @0x7fe04f6a55c0: OSAID(8) ComponentInstance(0x810017)>

I’ve never used CoreServices before so perhaps there is some trick to this I don’t know about. I left the first 4 tries in to get this line to work in the script. But I tried a ton of different things. The first 4 were just the only one’s I thought might actually work. The rest were just guesses. I also left the last line in as this is what I planning to do to get the desired metadata value. I left it in there in case I have done that wrong as well. I never got far enough to test that line.

I did try use framework “CoreFoundation” first, then tried use framework “CoreServices” and then a a few million other framework name variations.

Can you save my poor little sick script? I’m hoping the ASObj-C doctor can fix it :slight_smile:

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

set TheURL to current application's NSURL's URLWithString:" /Volumes/Video disk/Military stuff/NEW TECHNOLOGY | Russian military NASTY SUPPRISE for US military #MIND BLOW.mp4"
-- set MDItem to MDItemCreateWithURL(missing value, TheURL)
-- set MDItem to MDItemCreateWithURL((missing value), TheURL)
-- set MDItem to MDItemCreateWithURL_(missing value, TheURL)
set MDItem to MDItemCreateWithURL(current application's kCFAllocatorDefault, TheURL)
-- MDItemCopyAttribute(MDItem, kMDItemFinderComment)

Bill


(Shane Stanley) #2

First off, don’t use URLWithString: for file URLs – always use fileURLWithPath:.

Second, you you can’t use CoreServices from ASObjC because it’s a C-based API that uses types that can’t be bridged. You need to use NSMetadataQuery with NSPredicate.

If you were doing it in Objective-C, you would typically register for notifications (NSMetadataQueryDidUpdateNotification and NSMetadataQueryDidFinishGatheringNotification), and then start the query – queries run asynchronously.

For ASObjC, I just tend to use a repeat loop that keeps repeating while the query’s isGathering() method returns NO, and then get the results from the query. It’s simpler, and works fine.

Here’s some sample code:

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

set theFile to POSIX path of (choose file)
-- build predicate
set theFile to current application's NSString's stringWithString:theFile
set theName to theFile's lastPathComponent()
set theContainer to theFile's stringByDeletingLastPathComponent()
set thePred to current application's NSPredicate's predicateWithFormat_("kMDItemFSName == %@", theName)
-- build and start query
set theQuery to current application's NSMetadataQuery's new()
theQuery's setPredicate:thePred
theQuery's setSearchScopes:{theContainer}
theQuery's startQuery()
-- loop until it's stopped gathering
repeat while theQuery's isGathering() as boolean
	delay 0.1
end repeat
-- stop and get the first NSMetadataItem (there can only be one in this case)
theQuery's stopQuery()
set metaItem to theQuery's resultAtIndex:0
-- get array of the attributes that were found
set theAttrs to metaItem's attributes()
-- get the values for those attributes
return (metaItem's valuesForAttributes:theAttrs) as record

In your case you wouldn’t need to get all the attributes, but instead use valueForAttribute: or valueForAttributes: to get just the ones you’re interested in.


#3

Shane,

So basically what you are telling me the only parts I got wrong was everything in the script. Well if I had tried core foundation before I would have know that by now. I’ve done a lot predicates before but this is the first time I tried using ASObj-C for Metadata. But I should definitely be able to get it from your very complete example. I’ve always used shell scripts in the past when working with Metadata. I decided after one of your recent posts to me to stop using shell script and figure them out with ASObj-C.

Thanks Shane. This is probably what comes of trying to do things at 3am. I’m getting a bit puncy by now.

So just to make sure I have it right. Never use core services for anything?

Thanks again.

Bill


(Shane Stanley) #4

Never say never :slight_smile:, but pretty much.


#5

Shane,

I did finish my script and have been using it. No mre core foundation :slight_smile:. It’s longer then the ones I usually do. I have so many you tube videos where I have identical ones except for being a different size, ones that are the same size but half is cut off on one them ,… This script really helps to figure out which of the duplicates to keep. Thanks for the help :slight_smile:

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

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

on MakeMiniBBEditWin(TheName, DocText)
	script ReturnObj
		property Successful : false
	end script
	
	try
		tell application "BBEdit"
			make new document with properties {name:TheName, text:DocText, bounds:{50 + (WindowLevelOffest * 40), 60 + (WindowLevelOffest * 40), 700 + (WindowLevelOffest * 40), 100 + (WindowLevelOffest * 40)}} -- {left, top, right, bottom}
		end tell
		
		set (Successful of ReturnObj) to true
		return ReturnObj
	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 ReturnObj) to false
		return ReturnObj
	end try
end MakeMiniBBEditWin

on CreateResults(VideoName, PixelHeight, PixelWidth, FileSize, ItemKind, AudioBitRate, VideoBitRate)
	-- Creates the text to be displayed	
	
	-- Used a script object here in case I ever want to pass something back from this handler other then it succed or failed
	script ReturnObj
		property Successful : false
		property TheText2 : ""
	end script
	
	try
		
		set theFormatter to current application's NSByteCountFormatter's alloc()'s init()
		theFormatter's setCountStyle:(current application's NSByteCountFormatterCountStyleDecimal)
		theFormatter's setIncludesActualByteCount:true
		set ConvertedFileSize to theFormatter's stringFromByteCount:FileSize
		
		set TheText to "Video name: " & VideoName & return & return
		set TheText to TheText & "Video size: " & PixelHeight & " X " & PixelWidth & return & return
		set TheText to TheText & "File size: " & ConvertedFileSize & return & return
		set TheText to TheText & "Item Kind: " & ItemKind & return & return
		set TheText to TheText & "Video Bit Rate: " & VideoBitRate & return & return
		set TheText to TheText & "Audio Bit Rate: " & AudioBitRate
		
		set TheResult to MakeMiniBBEditWin(VideoName, TheText)
		if (not (Successful of TheResult)) then
			set (Successful of ReturnObj) to false
			return ReturnObj
		end if
		
		set (Successful of ReturnObj) to true
		return ReturnObj
	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 ReturnObj) to false
		return ReturnObj
	end try
end CreateResults

on FindMetaData(TheSender)
	set TheObject to TheSender's object
	TheSender's stopQuery()
	current application's NSNotificationCenter's defaultCenter()'s removeObserver:me |name|:(current application's NSMetadataQueryDidFinishGatheringNotification) object:TheObject
	set SearchResults to TheObject's results()
end FindMetaData

tell application "Finder"
	set WindowLevelOffest to 0
	
	set FileList to selection
	
	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
	
	if (FileList ≠ {}) then
		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
		
		repeat with TheFile in FileList
			set TheFilePath to (current application's NSString's stringWithString:(POSIX path of (TheFile as text)))
			
			set FileName to TheFilePath's lastPathComponent()
			
			set EnclosingFolderPath to TheFilePath's stringByDeletingLastPathComponent()
			
			set SearchPredicate to current application's NSPredicate's predicateWithFormat_("kMDItemFSName == %@", FileName)
			
			set SearchQuery to current application's NSMetadataQuery's alloc()'s init()
			
			(current application's NSNotificationCenter's defaultCenter()'s addObserver:(me) selector:"FindMetaData:" |name|:(current application's NSMetadataQueryDidFinishGatheringNotification) object:FindMetaData)
			
			(SearchQuery's setPredicate:SearchPredicate)
			(SearchQuery's setSearchScopes:{EnclosingFolderPath})
			SearchQuery's startQuery()
			
			repeat while SearchQuery's isGathering() as boolean
				delay 0.01
			end repeat
			
			SearchQuery's stopQuery()
			
			set MetadataItem to (SearchQuery's resultAtIndex:0)
			
			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})
			
			
			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 TheResult to my CreateResults(VideoName, PixelHeight, PixelWidth, FileSize, ItemKind, AudioBitRate, VideoBitRate)
			if (not (Successful of TheResult)) then
				return false
			end if
			set WindowLevelOffest to WindowLevelOffest + 1
		end repeat
	end if
end tell

Bill Kopp


(Jim Underwood) #6

Bill, thanks for sharing your script.

I thought you might be interested in a faster way to search video files than:

property AceptableFileExtensions : {"mp4"} -- Add more video file extentions as I come across them
# . . .

tell application "Finder"
  set WindowLevelOffest to 0
  
  set FileList to selection
  
  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
  
  if (FileList ≠ {}) then
    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

# . . .

This script will search for all types of video/movie formats and file extensions.
Of course, you can limit it if you wish.

This script is based on scripts posted by @ccstone and @ShaneStanley long ago. (many thanks guys!).

Of course, I’m sure that by now @ccstone and @ShaneStanley have better scripts than mine.

###Script to Get List of Files Using Spotlight Meta Keys & ASObjC

(*
===============================================================================
  Get List of Files Using Spotlight Meta Keys & ASObjC
===============================================================================

VER:   1.1    LAST UPDATE:   2017-03-13

AUTHOR:  JMichaelTX
            • Based on scripts by Chris Stone (@ccstone) and @ShaneStanley
            
METHOD:
  • This relies heavily on the BridgePlus script library by Shane Stanley
  • There may be alternate/better methods using ASObjC directly.
  • But this is simple and fast.
  
REQUIRES:
  • macOS
      • Yosemite (10.10) or later
  • Script Libraries:
      • BridgePlus

REFERENCES:

  1. BridgePlus Script Library v1.3.2 by Shane Stanley
        https://www.macosxautomation.com/applescript/apps/BridgePlus.html
  
      BridgePlus.framework, SMSForder Class Methods
        https://www.macosxautomation.com/applescript/apps/BridgePlus_Manual/Pages/runSpotlightQuery_inFolders_error_.html
  
  2. Common Spotlight Metadata Attribute Keys
    https://developer.apple.com/library/mac/documentation/CoreServices/Reference/MetadataAttributesRef/Reference/CommonAttrs.html
  
  3. System-Declared Uniform Type Identifiers (UTI)
    https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html#//apple_ref/doc/uid/TP40009259-SW1
  
  4. Spotlight syntax, mdfind examples, and metadata attributes
    http://osxnotes.net/spotlight.html
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*)
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"

use scripting additions
use script "BridgePlus" ## REQUIRED SCRIPT LIB ###
load framework

--- SEARCH FOR ALL VIDEO (MOVIE) TYPES ---
--    public.movie:  Base type for movies (video with optional audio or other tracks).
--    See: https://tinyurl.com/Apple-UTI

set mdQueryStr to "kMDItemContentTypeTree CONTAINS 'public.movie'"

set folderToSearch to (choose folder) as alias

set fileList to my spotSearch(folderToSearch, mdQueryStr)

--~~~~~~~~~~~~~~~~~~~ END MAIN SCRIPT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

on spotSearch(pFolderToSearchAlias, pQueryStr)
  
  set nsFileList to current application's SMSForder's runSpotlightQuery:pQueryStr inFolders:{pFolderToSearchAlias} |error|:(missing value)
  
  --- ALTERNATE QUERY ---
  --set theResult to current application's SMSForder's runSpotlightQuery:pQueryStr queryValues:{pQueryValues} inFolders:{pFolderToSearchAlias} |error|:(missing value)
  
  ASify from nsFileList
  
  --- RETURN as POSIX File List ---
  return nsFileList as list
  
end spotSearch

(Shane Stanley) #7

You don’t need that.

Or that.


#8

Jim,

Thanks for the sample. However in my case I was not searching for videos. I was manually selecting videos and displaying their specifications from Finder for the purposes of making better comparisons. But thanks for showing a fast search.

Bill


#9

As I used my script I ran into problems with certain attributes being in some videos and not others. So I added some code to fix that and fix a couple of other problems. It now seems to run pretty stable. This can serve as a guide to doing any kind file inspection on files by reading spotlight attributes. This script does not search for files, it find attributes of the selected files. It works on all selected files, and only on selected files in Finder.

The general pattern I followed isn’t limited to videos, multimedia, … Anything that has the spotlight data should work pretty close to what I used. It makes for a handy utility. I just select files and get the values without having to open anything.

I sent all my data to a BBEdit window. But that is all taken care of in the handler “MakeMiniBBEditWin(WindowName, DocText)” By editing just this handler the data can be send to whatever you like. Text Wrangler has the same AppleScript as BBEdit so if you have that you can just change the tell application “BBEdit” to tell application “Text Wrangler” and then it should work with “Text Wrangler.” “Text Wrangler” is a free application from http://www.barebones.com/products/textwrangler/ but some day they are planning to retire the app. But you can download it for free as long as it is up there. Any editor that can handle text should work.

If anyone is interested here is the final debugged version of the script:

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

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

on MakeMiniBBEditWin(WindowName, DocText)
	script ReturnObj
		property Successful : false
	end script
	
	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 ReturnObj) to true
		return ReturnObj
	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 ReturnObj) to false
		return ReturnObj
	end try
end MakeMiniBBEditWin

on CreateResults(VideoName, PixelHeight, PixelWidth, FileSize, ItemKind, VideoLength, AudioBitRate, VideoBitRate)
	-- Creates the text to be displayed	
	
	-- Used a script object here in case I ever want to pass something back from this handler other then it succed or failed
	script ReturnObj
		property Successful : false
	end script
	
	set TheText to ""
	try
		if (FileSize ≠ missing value) then
			-- Calculate the file size 
			set theFormatter to current application's NSByteCountFormatter's alloc()'s init()
			theFormatter's setCountStyle:(current application's NSByteCountFormatterCountStyleDecimal)
			theFormatter's setIncludesActualByteCount:true
			set ConvertedFileSize to theFormatter'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 (VideoLength ≠ missing value) then
			set TheText to TheText & "Video length: " & VideoLength & 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 ReturnObj) to false
			return ReturnObj
		end if
		
		set (Successful of ReturnObj) to true
		return ReturnObj
	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 ReturnObj) to false
		return ReturnObj
	end try
end CreateResults

on FindMetaData(TheSender)
	set TheObject to TheSender's object
	TheSender's stopQuery()
	current application's NSNotificationCenter's defaultCenter()'s removeObserver:me |name|:(current application's NSMetadataQueryDidFinishGatheringNotification) object:TheObject
	set SearchResults to TheObject's results()
end FindMetaData

on Make2Digits(TheNumber)
	if (TheNumber < 10) then
		return "0" & (TheNumber as text)
	else
		return (TheNumber as text)
	end if
end Make2Digits

tell application "Finder"
	set WindowLevelOffest to 0
	
	set FileList to selection
	
	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
	
	if (FileList ≠ {}) then
		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
		
		repeat with TheFile in FileList
			set TheFilePath to (current application's NSString's stringWithString:(POSIX path of (TheFile as text)))
			
			set FileName to TheFilePath's lastPathComponent()
			
			set EnclosingFolderPath to TheFilePath's stringByDeletingLastPathComponent()
			
			set SearchPredicate to current application's NSPredicate's predicateWithFormat_("kMDItemFSName == %@", FileName)
			
			set SearchQuery to current application's NSMetadataQuery's alloc()'s init()
			
			(current application's NSNotificationCenter's defaultCenter()'s addObserver:(me) selector:"FindMetaData:" |name|:(current application's NSMetadataQueryDidFinishGatheringNotification) object:FindMetaData)
			
			(SearchQuery's setPredicate:SearchPredicate)
			(SearchQuery's setSearchScopes:{EnclosingFolderPath})
			SearchQuery's startQuery()
			
			repeat while SearchQuery's isGathering() as boolean
				delay 0.01
			end repeat
			
			SearchQuery's stopQuery()
			
			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
			
			set MetadataItem to (SearchQuery's resultAtIndex:0)
			
			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})
			
			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
			
			if ((LengthInSeconds ≠ missing value) and (class of LengthInSeconds = real)) then
				-- LengthInSeconds is not equal to missing value and is a number
				set TheHours to LengthInSeconds div hours
				set RemainingSeconds to LengthInSeconds - (TheHours * hours)
				set TheMinutes to (RemainingSeconds div minutes)
				set TheSeconds to (RemainingSeconds mod minutes) div 1 -- The div 1 makes it an integer
				set VideoLength to (TheHours as text) & ":" & my Make2Digits(TheMinutes) & ":" & my Make2Digits(TheSeconds)
			else
				set VideoLength to missing value
			end if
			
			set TheResult to my CreateResults(VideoName, PixelHeight, PixelWidth, FileSize, ItemKind, VideoLength, AudioBitRate, VideoBitRate)
			if (not (Successful of TheResult)) then
				return false
			end if
			set WindowLevelOffest to WindowLevelOffest + 1
		end repeat
	end if
end tell

Bill Kopp


#10

Shane,

The one thing I never figured out with the script was how to deal with file names with special characters like back slash (), :alien:, :heart: … Is there some kind of class, function, special syntax, … that converts those characters to something that can be worked? When the characters are in there the code isn’t able to find the file inside folder, and that that’s folder that the file is actually in.

Bill


(Shane Stanley) #11

You shouldn’t have to do anything with them. But you shouldn’t put all that stuff at the end in a Finder tell block – just put stuff that involves the Finder in it.


#12

I modified the script in this post at 8:45am pacific time on March 17, 2017

I changed the way the video time length was converted from seconds to a HH:MM:SS format. It now uses NSDateComponentsFormatter to calculate this value.

I modified the script in this post at 12:04 pacific time on March 16, 2017

Shane pointed out there was useless code my script. So I removed the useless code involving the notification stuff. So this makes my second final change. I do remember Shane telling me not long ago. “Never say never.” I guess he he was right about that part too :slight_smile:


I modified the script in this post at 7:28 pacific time on March 15, 2017

Richard Parker, a friend of mine, told me how to the fix the script so it would work with backslashes (\) in the file name. I added code toward the top of the “repeat with TheFile in FileList” repeat loop. So the script in this post will now work when a backslashes (\) are in the file name. The comments in the script point out where the change were made and what was done to fix it.

The changes consisted of adding the following 4 lines.

		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

I also added 4 line of comments above that to explain why this was needed and why it works.
For anyone interested,


Shane,

I changed the script so Finder tell blocks were only used where needed instead of me being lazy and putting everything in one big Finder tell block. That got rid of all the problems except when there is a \ character in the file name. This causes the search to fail to find itself in it’s own enclosing folder. I have no idea how to get around this. I spent hours and didn’t get anywhere. But the rest of this works.

I added a bunch of comments to this code to make it easier to follow. But this is it for me changing it. It will work as long as there is no \ character in the file name.

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"} -- 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
	script ReturnObj
		property Successful : false
	end script
	
	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 ReturnObj) to true
		return ReturnObj
	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 ReturnObj) to false
		return ReturnObj
	end try
end MakeMiniBBEditWin

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

on CreateResults(VideoName, PixelHeight, PixelWidth, FileSize, ItemKind, VideoLength, 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 script object here in case I ever want to pass something back from this handler other then it succed or failed
	script ReturnObj
		property Successful : false
	end script
	
	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 theFormatter to current application's NSByteCountFormatter's alloc()'s init()
			theFormatter's setCountStyle:(current application's NSByteCountFormatterCountStyleDecimal)
			theFormatter's setIncludesActualByteCount:true
			set ConvertedFileSize to theFormatter'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 (VideoLength ≠ missing value) then
			set TheText to TheText & "Video length: " & VideoLength & 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 ReturnObj) to false
			return ReturnObj
		end if
		
		set (Successful of ReturnObj) to true
		return ReturnObj
	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 ReturnObj) to false
		return ReturnObj
	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
		
		-- This part takes the number of seconds in the viedo and converts it to a string of the for HH:MM:SS
		if ((LengthInSeconds ≠ missing value) and (class of LengthInSeconds = real)) then
			-- LengthInSeconds is not equal to missing value and is a number
			set TheHours to LengthInSeconds div hours
			set RemainingSeconds to LengthInSeconds - (TheHours * hours)
			set TheMinutes to (RemainingSeconds div minutes)
			set TheSeconds to (RemainingSeconds mod minutes) div 1 -- The div 1 makes it an integer
			set VideoLength to (TheHours as text) & ":" & my Make2Digits(TheMinutes) & ":" & my Make2Digits(TheSeconds)
		else
			set VideoLength to missing value
		end if
		
		-- 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, VideoLength, AudioBitRate, VideoBitRate)
		if (not (Successful of TheResult)) then
			return false
		end if
		set WindowLevelOffest to WindowLevelOffest + 1
	end repeat
end if

#13

I changed the text in the script of my previous post to fix the last remaining problem with backslashes.

Bill


(Shane Stanley) #14

Bill,

You’ve still left in the notification center stuff that does nothing. See my earlier message.


#15

I yet again modified my script to remove some useless code from the script. I thought I had gotten rid of it before but code is like weeds. It returns no matter how many time you get rid of it :slight_smile: The newly modified post starts with “I modified the script in this post at 12:04 pacific time on March 16, 2017”

Bill


(Phil Stokes) #16

Sorry, I 've only just got round to looking at this.

–1
although you still need the formatting, using mdls seems like an easier way to go for getting the metadata. This is an old one I have that does the same thing as yours using TextEdit.

choose file with prompt "Choose a file"
set this_file to the POSIX path of (the result as alias)
set command_output to (do shell script "mdls '" & this_file & "'")
tell application "TextEdit"
	activate
	set this_document to make new document with properties {name:this_file, text:command_output}
	set properties of text of this_document to {size:15.0, color:{0, 0, 65535}, font:"Courier"}
	set bounds of window 1 to {120, 50, 612, 750} 
end tell

–2
Second thing was that when I tried the full version of your last edited post, although it completed and opened up BBEdit, it caused SD6 to weirdly hang. The ‘open quickly’ window just froze in mid-animation and nothing could be done but force quit SD6. Might be something local my end (in fact, I’ve been having a few issues with Spotlight myself, see --3 below.)

–3
Nothing to do with AppleScript per se, but I’ve been using NSMetadataQuery myself in one of my cocoa apps. One of the issues that came up recently was that if Spotlight (or mds) decides to re-index the entire disk (which it does from time to time), my app becomes unresponsive.

This led me to wondering if anyone knows a way to reliably tell whether mds is reindexing or not. mdutil has a status option, but all that does is tell you whether indexing is enabled for a given volume, rather than tell you what mds is actually doing.


(Shane Stanley) #17

Except that the result of mdls is a string that needs a whole lot of text parsing to extract and format, whereas NSMetadataQuery returns a dictionary. Have a look at what Bill’s script is showing, and then have a go at extracting the info from an mdls call. It can be done, but it gets ugly.


(Shane Stanley) #18

Bill,

An alternative, mostly just for the sake of it:

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

(Phil Stokes) #19

Putting the string into a list with ‘paragraphs’ makes that easier:

set command_output to paragraphs of (do shell script "mdls '" & this_file & "'")

Have a look at what Bill’s script is showing

I did, but it has the singular disadvantage of causing SD6 to freeze. Here it is again (note the event viewer pane)

I’m not trying to rain on anyone’s parade. Just pointing out that there’s another way to approach this.


(Phil Stokes) #20

Arguable.

Granted, I didn’t cover the edge cases or convert the file sizes, but as a proof of concept, this looks a lot less ugly to my eyes.

set _audio to {}
set _video to {}
set _duration to {}
set _pixelHeight to {}
set _pixelWidth to {}
set _size to {}
set _kind to {}
set _name to {}


choose file with prompt "Choose a file"
set this_file to the POSIX path of (the result as alias)
set command_output to paragraphs of (do shell script "mdls '" & this_file & "'")
repeat with i from 1 to count of command_output
	set this_item to item i of command_output
	set o to 0
	try
		set o to offset of "=" in this_item
	end try
	if o ≠ 0 then
		set kMDType to text 1 thru o of this_item
		if kMDType contains "AudioBitRate" then
			set end of _audio to text (o + 1) thru -1 of this_item
		else if kMDType contains "Duration" then
			set end of _duration to text (o + 1) thru -1 of this_item
		else if kMDType contains "PixelHeight" then
			set end of _pixelHeight to text (o + 1) thru -1 of this_item
		else if kMDType contains "PixelWidth" then
			set end of _pixelWidth to text (o + 1) thru -1 of this_item
		else if kMDType contains "FSSize" then
			set end of _size to text (o + 1) thru -1 of this_item
		else if kMDType contains "ItemKind" then
			set end of _kind to text (o + 1) thru -1 of this_item
		else if kMDType contains "VideoBit" then
			set end of _video to text (o + 1) thru -1 of this_item
		else if kMDType contains "FSName" then
			set end of _name to text (o + 1) thru -1 of this_item
		end if
	end if
end repeat

set audioBitString to ""
set durationString to ""
set pixelHeightString to ""
set pixelWidthString to ""
set sizeString to ""
set kindString to ""
set videoBitString to ""
set videoFrameSizeString to ""
set fileNameString to ""
set WindowLevelOffest to 1

set ret to return & return

if (count of _audio) = 1 then
	set audioBitString to "Audio Bit Rate: " & (item 1 of _audio as text)
end if
if (count of _duration) = 1 then
	set durationString to "Video length:" & (item 1 of _duration as text)
end if
if (count of _pixelHeight) = 1 then
	set pixelHeightString to (item 1 of _pixelHeight as text)
end if
if (count of _pixelWidth) = 1 then
	set pixelWidthString to (item 1 of _pixelWidth as text)
end if
if (count of _size) = 1 then
	set sizeString to "File size: " & (item 1 of _size as text)
end if
if (count of _kind) = 1 then
	set kindString to "Item Kind: " & (item 1 of _kind as text)
end if
if (count of _video) = 1 then
	set videoBitString to "Video Bit Rate: " & (item 1 of _video as text)
end if
if (count of _name) = 1 then
	set fileNameString to "Video name: " & (item 1 of _name as text)
end if

if ((count of _pixelWidth) ≠ 0 and (count of _pixelHeight) ≠ 0) then
	set videoFrameSizeString to "Video frame size =" & _pixelHeight & " X " & _pixelWidth
end if



set DocText to fileNameString & ret & videoFrameSizeString & ret & sizeString & ret & durationString & ret & kindString & ret & videoBitString & ret & audioBitString
if fileNameString's length is not 0 then
	tell application "BBEdit"
		make new document with properties {name:fileNameString, text:DocText, bounds:{50 + (WindowLevelOffest * 40), 60 + (WindowLevelOffest * 40), 700 + (WindowLevelOffest * 40), 100 + (WindowLevelOffest * 40)}} -- {left, top, right, bottom}
	end tell
end if