Generic display of Spotlight Metadata for file(s)

Have a nice barbeque in a garden!

I find it aggravating! That I can’t get the original date strings right through the coercion to a record. Just saying! :slight_smile:

I know I could take the text, and “massage” it and run it through a run script in the debugger, to find where the shoe is pressurizing, and then reverse engineer it, but… :smiley:

It is just good enough as it is!

Talk about aggravation, I actually use this (Portuguese Date formats):

on getdate(t)
	--	    2012-08-09 00:00:00 +0100
	return t
	set d to every word of t
....

:stuck_out_tongue:

Don’t ask why, the date separator is “/”, not “-”
On the other hand text dates like “5 de Janeiro de 2012” all end up being December :mad:

:slight_smile:

Glad it is just not me, I still have ideas :slight_smile: Ideas I think to be fruitless ahh… but they are untested!.

And the way you have handled it, is perfectly ok, don’t misunderstand me, it is just that I want the original strings, for no rational reason really.

As a solution I think your way to handle it is the best, because now it should be fairly easy to turn the dates into date objects for further maninpulation, and it is very readable to.

It is just that I want to know what is going wrong really, and how do I make this into a string that the run script tackles in that nested context! :slight_smile:

As for your date string, that is the downside of localization. Like the kDMIContentType: On my machine a script has a kMDItemKind of prosedyre or Compiled OSA Script (data-fork), so it is a lot of stuff that must be handled, should one write something that works in every context!

But alas kDMIContentType is com.apple.applescript.script I don’t think you can do something similiar dates thouhg, but has to check for which localization you currently use. before forming the datestring.
You can get that info out of system preferences I believe.

date strings are not quoted because they are actually not strings but string descriptions of date objects
and they do contain only ASCII compatible numeric and ±: characters

Helo Stefan!

And the records, isn’t particular happy about such a string really at least not when being constructed during a run script, then you need quoting, but how? That is If you are to construct the whole record in one go, like above and retain the original date strings? There is no problem, when constructing the record bits and pieces at a time, but it is kind of slow, running script 5 times or so per record! :slight_smile:

Can we assume, then, that an unquoted string with more then two words is a date? (numbers can have only a max of two words)

In this thread I learned (did I ?) about the convention of quoting strings with spaces, leaving a one word output unquoted. Since this kind of output can be piped to other commands there must be rules. I am aware that 3rd party importers might produce whatever output (like “0”, quotes and all, for a boolean) but are there rules? TIA

I don’t think so. There might be other unquoted objects.

Quotation is not limited to space characters. All special characters of the shell must be escaped
(quotation is actually escaping multiple consecutive characters), so strings containing special characters are quoted automatically by mdls. As dates are not treated as strings they are not quoted also because spaces characters don’t matter for output (for input they are parameter delimiters)

After carefully cleaning the keyboard here is a less soiled script. Some olive seeds might still be in it, chew carefully!

property knownItems : {kmditemaudiochannelcount:missing value, kMDItemAudioEncodingApplication:missing value, kmditemaudiosamplerate:missing value, kmditemaudiotracknumber:missing value, kmditemauthors:{missing value}, kMDItemComment:missing value, kMDItemComposer:missing value, kMDItemCopyright:missing value, kMDItemFinderComment:missing value, kMDItemIsGeneralMIDISequence:missing value, kMDItemKeySignature:missing value, kMDItemKeywords:{missing value}, kMDItemLyricist:missing value, kmditemmusicalgenre:missing value, kMDItemOriginalFormat:missing value, kMDItemOriginalSource:missing value, kMDItemProducer:missing value, kMDItemRecordingDate:missing value, kmditemrecordingyear:missing value, kMDItemTempo:missing value, kMDItemTimeSignature:missing value, kmditemtitle:missing value, org_xiph_flac_compilation:missing value, org_xiph_flac_discnumber:missing value}

-- added comment:
-- knownItems are just an example, compile the ones you are interested to avoid "try get kmdItemBof of info..."
-- the returned values of metadataList() never are missing value, making the detection of absent metadata easy.

property info : {}


on open of theFiles
	set info to {}
	repeat with f in theFiles
		set fp to quoted form of POSIX path of f
		copy (metadataList(fp) & knownItems) to end of info
-- if you don't care about known items use
-- copy metadataList(fp) to end of info
	end repeat
end open


on metadataList(fp)
	set {astid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, {"="}}
	
	try
		set prs to "{"
		set rs to (do shell script "mdls " & fp)
		set islist to false
		set isdate to false
		(*
		eg:
		kMDItemUsedDates               = (
	    2012-08-09 00:00:00 +0100
		)
*)
		set parags to every paragraph of rs
		repeat with pt in parags
			set p to contents of pt
			if islist then
				if text 1 of p is ")" then
					set islist to false
					--set x to x & "},"
					set prs to prs & x & "}"
				else
					set p to text 4 thru -1 of p
					if isdate(p) then
						set x to x &  getdate(p) 
						if last character of p = "," then
							set x to x & ","
						end if
						--getdate will swallow the comma
						
					else
						if p contains quote then
							set x to x & p
						else
							-- is it a number?
							try
								p*1
								set x to x & p
							on error
								if last character of p = "," then
									set x to x & quote & (word 1 of p) & "\","
								else
									set x to x & quote & word 1 of p & quote
								end if
							end try
						end if
					end if
				end if
			else
				if prs = "{" then
					set x to ""
				else
					set x to ","
				end if
				
				set pil to text items of p
				set {x, p} to {x & item 1 of pil & ":", rest of pil as text}
				
				if p starts with " (" then
					set x to x & "{"
					set islist to true
				else
					if isdate(p) then
						set x to x &  getdate(p)
					else
						if p contains quote then
							set x to x & p
						else
							try
								p*1
								set x to x & p
							on error
								set x to x & quote & word 1 of p & quote
							end try
						end if
					end if
					set prs to prs & x --& ","
				end if
				--end if
				
			end if
		end repeat
		--set prs to text 1 thru -2 of prs & "}"
		set prs to prs & "}"
		set prs to (run script prs)
		
		set AppleScript's text item delimiters to astid
		return prs
	on error e
		set AppleScript's text item delimiters to astid
		return "Error: " & e & "¢ " & prs
	end try
	
end metadataList

on isdate(t)
	count of t
	if t contains quote then return false
	if t does not contain "-" then return false
	return (count of t) = 26
end isdate

on getdate(t)
	--	    2012-08-09 00:00:00 +0100
	--return t
	try
		set d to every word of t
		set r to date "1 de Dezembro de 1904 0:00:00"
		set year of r to item 1 of d
		set month of r to item 2 of d
		set day of r to item 3 of d
		set time of r to ((item 4 of d) * hours + (item 5 of d) * minutes + (item 6 of d))
		--	set tz to 1 * (item 8 of d)
		--	if item 7 of d = "-" then set tz to -tz
		-- set tz to hours * (tz div 100) + minutes * (tz mod 100)
		--	set r to r + (time to GMT) - tz
		return ("date \"" & short date string of r & " " & time string of r & "\"")
	on error
		return t
	end try
	-- portuguese for example flips with date as text
end getdate

Hehehe.

I really can’t stop trying this, though I think the run script solution to be one that works.

Anyhow, I feel that the solution as it is isn’t correct, when the +0100 for time to GMT is not part of the data, so I think it should be represented some way. maybe like a regular time string. The problem is how to represent negative amounts of time, one option being represnting negative times as passed midday, or as hours before midnight.

that is

-0100 may be 1300 or it may be 2300.

As the getdate handler is coded at the moment, it doesn’t handle negative times to GMT, for a user in France, your code will break!

In the same time, I urge you to add a time string, and a convention for sending with the time to GMT, maybe in the understanding that the time was having that difference to GMT, as to avoid double calculations, but still giving the user the opportunity to figure out the UTC time.

I leave you with those ideas

Edit I see you posted a new version while I was posting, interesting! :slight_smile:

I really have to stop this for now, it was fun, and I wish you all luck with this.
Edit

I also want to say that I think you are a brilliant coder, and that it was fun participating with you in this! :slight_smile:

As it is, and this makes your code no less, but I think I’ll stick with the list version for generic usage, albeit it uses maybe 50% longer to complete, because it is robust, and I am confident it will handle problems and configurations of meta data we havnen’t encountered yet. (Until I at least know and am sure that I deal with a static set of attributes). The rationale is also, that a record, is supposed to have a static set of attributes, and not vary as they do with spotlight meta data. Therefore I find it better to use the list version. At least until I have a static set of properties to deal with.

Good luck with your endeavours!

I must confess I didn’t go through your scripts carefully, as this is integrated in a larger project, so I don’t understand the “run script solution”. This one uses run script, except only once per file, as once per line was terribly slow.

Ouch! I didn’t find that case yet, so I assumed a very rigid format.

Nope, it’s +1300. Can be weirder like +0745, but the sign is there.

As it is, commented, it doesn’t do anything, I gave up on that 5 pizzas ago, but I tried it at the time by messing with the Date & Time panel and seemed to work fine, even the Nepal timezone (that’s a cute one!).
For me it works fine just returning the string!

As is it’s supposed to return the local time, corrected (after uncommenting) for the unknown circumstances when mdls returns a different timezone than the current.

Anyway this made some olive seeds sprout! Corrections it is then.

this is a handler to convert a Cocoa date description string into an AppleScript date object
independent of international date format settings but be able to consider the local time zone


set ASDate to convertCocoaDateDescriptionToASDate from "2012-08-11 17:33:07 +0200" without consideringLocalTimeZone

on convertCocoaDateDescriptionToASDate from dateDescription given consideringLocalTimeZone:consideringLocalTimeZone
	set {TID, text item delimiters} to {text item delimiters, space}
	set {dateString, timeString, timeZone} to text items of dateDescription
	set currentDate to date timeString
	set text item delimiters to "-"
	set {yr, mn, dy} to text items of dateString
	set text item delimiters to TID
	tell timeZone to set GMTDifference to (((text 1 thru 3) as integer) * 3600) + (((text -2 thru -1) as integer) * 60)
	tell currentDate to set {day, year, its month, day} to {1, yr, mn, dy}
	if consideringLocalTimeZone then
		return currentDate - GMTDifference + (time to GMT)
	else
		return currentDate - GMTDifference
	end if
end convertCocoaDateDescriptionToASDate

Hello!

When it comes to producing things with this, when I need the record, then I’ll actually go for the latest version of yours that detects it is a date, and stuff in a run script statement just there, if I am cautious about getting the UTC dates.

The most important thing, is that it works for you, and your project. I want something generic and general. So, as I stated above. When I need this, in record format, then I’ll actually stuff in the run script handler right there where you detect the dates. You have skinned of ALOT of those run script statments, and I think I will live happilly with those, when I need it. And I will, until I hear complaints! :smiley: And should I ever hear complaints, I’ll tell them to upgrade their hardware! Or I’ll code it in Objective-C!

When there are no consideration of timezones, I could of course live fine with your solution. But with all those files, coming from here and there, and gotten by mounted volumes and such, you really never know!

Now that is a great handler as alwas Stefan! :slight_smile: Snagged!

That one had me puzzled for a while (before the edit)!

Freshly cooked, then!! Fine cook you are, Sir!

Hello!

I have a removed a bug, which kicked in, in the script in post #12, when Finders frontmost finder window is a spotlight window! :slight_smile:

Here’s another take, which seems pretty reliable so far.

The problem with using ‘time to GMT’ to transpose dates to the computer’s time zone is that it refers to the date on which the script’s run, not necessarily the date(s) being processed. I’ve used a hack I cobbled together a few years ago to get round this. I think I’ve seen a better method recently, but I can’t remember what it is. :rolleyes:

The dates in the ‘kMDItemUsedDates’ list always seem to have “midnight” times, which probably means they just represent dates in the original zone rather than precise, transposable date/times. (cf. all-day events in iCal.) That being so, it’s probably better not to transpose them. At the moment, though, the script transposes all dates. It looks easy to exclude the ‘kMDItemUsedDates’ ones from the transpositions and I may amend the script later on.

Snow Leopard or later is required for the use of multiple text item delimiters.

Edit: I have now modified the script to leave ‘kMDItemUsedDates’ dates with their original “midnight” times. They’re identified from the fact that they’re in quotes in the original data, whereas other dates aren’t. The “sed” commands have been modified to leave these quotes in and also to deal with an occasional comma problem I noticed in other lists.
Edit 2: I’ve read a bit more about “sed” this afternoon and now have a sequence which doesnt leave extra commas at the ends of “lists” in the text, which saves having to tidy them up with AppleScript. Script modified accordingly.
Edit 3: The “sed” code now handles all the pre-compilation editing except for the date conversions.
Edit 4: The transposition of date/times to the current computer’s time zone has been made much simpler with an adaption of DJ Bazzie Wazzie’s second script in post #37 below.
Edit 5: Time-zone transpositions cut altogether as it turns out they’re unnecessary. Double quotes within double-quoted text (eventually to compile as double-quotes within strings) catered for.
Edit 6: “sed” script slightly modified in the light of further education, made more robust, and commented.

on metaDataRecord for fp
	-- Ensure that we have a quoted POSIX path to the file.
	if (fp's class is text) then
		if (fp does not start with "'/") then
			if (fp does not start with "/") then set fp to POSIX path of fp
			set fp to quoted form of fp
		end if
	else
		set fp to quoted form of POSIX path of (fp as alias)
	end if
	
	-- Get the metadata text and edit it almost to compilability, marking date entries with unlikely tags.   ;)
	set rs to (do shell script ("mdls " & fp & " | sed -E '{
	s| *= |:| ;			# Colon instead of spaces and equals sign.
	s|\"|\\\\\"|g ;		# All double-quotes in each line escaped as if nested .
	s|\\\\\"|\"| ;		# . then the first .
	s|\\\\\"(,?)$|\"\\1| ;	# . and last returned to normal.
	s|^( +)([^\",]+)(,?)$|\\1\"\\2\"\\3| ;		# Indented text double-quoted if not already.
	s|\"?[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} [+-][0-9]{4}\"?|<McUsr>&</McUsr>| ;	# Dates tagged.
	s|\\($|{| ;			# Parentheses to .
	s|^\\)|}| ;			# . braces.
	s|^( .*\\|.*{)$|& ¬| ;	# Continuation chr at end of lines beginning with space or ending with left brace.
	$ ! s|[^¬]$|&, ¬| ;	# Comma & continuation at end of all other lines except the last.
	1 s|^|{| ;			# Left brace appended at beginning of first line.
	$ s|$|}| ;			# Right brace appended to end of last line.
	}'"))
	
	if ((count rs) > 0) then
		-- Replace the ISO dates with AppleScript dates transposed to the computer's time zone (if relevant) and coerced to text as per the local preferences. Requires AS 2.1 (Snow Leopard) or later for the multiple TIDs.
		set astid to AppleScript's text item delimiters
		set AppleScript's text item delimiters to {"<McUsr>", "</McUsr>"}
		set rs to rs's text items
		repeat with i from 2 to (count rs) by 2
			set dateString to item i of rs
			set item i of rs to "date \"" & getASDate(dateString) & "\""
		end repeat
		set AppleScript's text item delimiters to ""
		set rs to rs as text
		set AppleScript's text item delimiters to astid
		
		-- Return the "compiled" record.
		return (run script rs)
	else
		return {}
	end if
end metaDataRecord

-- Return an AppleScript date from a given ISO date/time with separators, ignoring the time-zone displacement.
on getASDate(ISODate)
	tell (current date)
		set {day, {year, its month, day, its hours, its minutes, its seconds}} to {1, words 1 thru 6 of ISODate}
		return it
	end tell
end getASDate

metaDataRecord for (choose file)

Metadatarecord it is then! :slight_smile:

I actually hoped you came up with a way to bypass the problems with coercing the “iso date” string to a string. :o

I don’t go to such extents at the Tolkien foundation, when it comes to copyright :wink: Yes: Someone actually were stopped, pushing a badge with the name “Tolkien” on it!

Good to see you back, and it is fast and brilliant! No question about that. I still find it aggravating, that I can’t trick the coercion of the datestring into the record, with just one run script statement. It has become a mania! :slight_smile:

(I wonder, if I made it into record form, the pretext, and the “iso/cocoa date strings” and then passed it over to the run script finally. But alas, I am just leaving it be.)

I think we, the participants, all have spent more than a fair amount of effort in this, and I want to quote Mark Aldritt

So I pat my back and move on, after having updated the demo script in post #12 with your code!!

As promised:

set theFile to POSIX path of (choose file)

set x to text 1 thru -2 of (do shell script "mdls " & quoted form of theFile & " | awk -F' = ' '{
if ($1 == \")\"){
	printf \"},\" 
} else if ($2 == \"(\"){
	printf  \"%s : {\", $1
} else if ($1 ~ /^ /){
	var=sub(/^ */, \"\", $1);
	if ($var ~ /^\"/){
		printf \"%s\", $var;
	} else {
		if ($var ~ /,$/){
			printf(\"\\042%s\\042,\", substr($var, 0, length($var) -1)) ;
		} else {
			printf(\"\\042%s\\042\", $var) ;
		}
	}
} else if ($2 ~ /^[0-9]{1,}$/){
	printf \"%s:%s,\", $1, $2 
} else if ($2 ~ /^\\042/){
	printf \"%s:%s,\", $1, $2 
} else {
	printf(\"%s : \\042%s\\042,\", $1, $2)
}}'")
return run script "{" & x & "}"

EDIT: I leave the ISO dates inside and won’t coerce them to AS dates yet. If you have like 16 dates and want only to know the visibility of a couple of files you get like 100s unnecessary do shell scripts. Therefore I use the following code on demand.

ISODateToASDate("2011-07-25 13:48:50 +0200")

on ISODateToASDate(__str)
	set d to current date
	set s to do shell script "date -jnu -f '%Y-%m-%d %H:%M:%S %z' " & quoted form of __str & " '+%Y %m %d %H %M %S'"
	tell d to set {its year, it's month, it's day, it's hours, it's minutes, it's seconds} to every word of s
	return d
end ISODateToASDate

p.s. remove the ‘%z’ and you can use this handler for SQL date fields as well.

It looks very nice! :slight_smile:

Hi, DJ.

This just returns a GMT-equivalent AS date, which can be more efficiently done with vanilla code. However, without the -n and -u options, the shell script’s a very convenient way to transpose to the current computer’s time zone! I’m going to modifiy my script above accordingly. Thanks!

I’m not sure when the -j and -f options were introduced. They’re there in Leopard, but I don’t think they were in Tiger. I’ll have a look later when I give my G5 its weekly restart.

You’re right! Here is the manual page for Tiger and no option -j or -f is described there.

if you don’t want to convert to GMT it can be done simpler (just ignore the timezone) and you won’t need date at all.

ISODateToASDate("2009-05-24 13:45:13 +0200")

on ISODateToASDate(str)
	set d to current date
	set {d's year, d's month, d's day, d's hours, d's minutes, d's seconds} to words 1 thru 6 of str
	return d
end ISODateToASDate

EDIT: Just for the curious ones,

Well the difference between the code above and previous post (using date -jnu) is that the first example convert the time back to GMT while the example in this post will only use local time. You only need GMT code when global time is important. For instance: I have a server and the file is modified locally (in Netherlands; CET) and two hours later someone edits a similar file in the US. When you compare the dates using the handler in this post, results that the file (newer) saved in the US will be considered as an older, earlier, saved version. When you use the handler using the date -jnu options the results will be correct because both dates will be coerced to GMT times and therefore the file from the US will be considered last changed.