iTunes object specifier (specified by a test) – relevance of order of operands in boolean conjunction?

This isn’t Script Debugger specific, but maybe someone can help me out anyway. Does anyone know why the ordering of the operands in this example would influence the result?! In the second case, the requirement that the work tag not be empty is ignored. What am I missing here?

use application "iTunes"

count every track whose composer of it ≠ "" and work of it ≠ ""
(*2182*)
count every track whose work of it ≠ "" and composer of it ≠ ""
(*13793*)

If it helps… the same behavior can be observed using SwiftAutomation:

import SwiftAutomation
import MacOSGlues

print(try (ITunes().tracks[ITUIts.composer != "" && ITUIts.work != ""].count(each:ITU.item)))
print(try (ITunes().tracks[ITUIts.work != "" && ITUIts.composer != ""].count(each:ITU.item)))

There are some rather different looking things in this code. When sending commands to iTunes you should use a tell application. iTunes works a lot with track, playlist and source to identify a unique object. For example just to identify a track I would normally do something like track 1 of playlist 2 of source 1 which would identify the first track of music in my library. iTunes structures can vary but this is what it does on my iMac.

tell application "iTunes"
	name of playlist 2  -- Returns Music
	name of playlist 1  -- Returns Library
	name of it -- Returns "iTunes"

	track 1 of playlist 2 of source 1 -- Returns file track id 50308 of user playlist id 49640 of source id 66

	composer of every track of playlist 2 of source 1 -- Returns the name of every composer in the library
	work of every track of playlist 2 of source 1   -- Returns a huge list of empty strings on my iMac
end tell

The “it” in the script is pointing to iTunes as a whole, and not to any of the useful objects in iTunes. I don’t have anything in my library that has “work” set to anything so I have nothing but empty strings for work.

I’m not sure what you were trying to do but the following returns a boolean value not a number:

	(count of (composer of every track of playlist 2 of source 1)) ≠ (count of (work of every track of playlist 2 of source 1))

If you weren’t trying to do that comparison what is written makes no sense. If you tried to compare the count of the first part with the attributes of the second part it will never return anything but false.

It would be best to say what you wanted to do. This script has a lot of errors in it. It would be constructive to think of references to things as being done with tracks, playlists and sources.

Bill

I’m trying to get the number of tracks where both a composer as well as a a work has been set (to something other than the empty string). The second line in my posted script actually achieves this, correctly returning 2182, the third, one on the other hand, does not, even though I’ve simply switched the operands in the boolean conjunction around. The output for each of the two statements is given in parentheses.

The “use” statement was added in OS X 10.9, the ‘it’ identifier you can safely ignore, as it was simply added by Script Debugger where I copied it from the ‘source’ view. The script that I posted produces the exact same Apple event as this one:

tell application "iTunes"
    count (every track whose composer ≠ "" and work ≠ "")
    count (every track whose work ≠ "" and composer ≠ "")
end tell  

where the two statements do in fact return different values for me (2182 vs. 13793), yet should not.

Or look at this single line instead:

 tell application "iTunes" to (the work of (every track whose work ≠ "" and composer ≠ "")) contains ""

This should not return true, yet for me it does, as iTunes ignores the requirement that the work tag not be empty. Try it out for yourself, it will probably return ‘true’ for you as well, even though you don’t have the work tag set anywhere. On the other hand the following, where the order of the operands is simply switched around, correctly returns ‘false’.

tell application "iTunes" to (the work of (every track whose composer ≠ "" and work ≠ "")) contains ""

The question is whether this is a general issue, or whether it is idiosyncratic behavior of iTunes specifically. At this point everything’s pointing towards a bug in iTunes’ implementation of Apple event handling from my perspective.

Another example:

tell application "iTunes" to get (count of (every track whose work = "No such work exists" and name ≠ ""))

Again, the first operand is completely ignored here. The returned count is simply of every track whose name is not empty. You should be able to reproduce this, regardless of whether you use the work tag for any tracks, or not.

I’d agree with that.

Interesting.

tell application "iTunes"
   set compWork to count (every track whose composer of it ≠ "" and work of it ≠ "")
   --> 3
   set workComp to count (every track whose work of it ≠ "" and composer of it ≠ "")
   --> 1384   
   set justComp to count (every track whose composer of it ≠ "")
   --> 1384   
   set justWork to count (every track whose work of it ≠ "")
   --> 3
   set compList to (id of every track whose composer of it ≠ "")
   set workList to (id of every track whose work of it ≠ "")
   set bothCount to 0
	repeat with thisWork in workList
		if (thisWork as item) is in compList then set bothCount to bothCount + 1
	end repeat
   bothCount
   --> 3
   set composersOfNoWork to {}
   set workCompList to (every track whose work of it ≠ "")
   repeat with thisWork in workCompList
      set the end of composersOfNoWork to composer of thisWork
   end repeat
end tell

For me I think tell blocks are a better way to go because it is very explicit as to what object is being worked with. Use is not so specific. Over time I have learned to put parentheses around everything so I can keep surprises down to minimum.

Now that I understand what you are trying to do. I can say iTunes messed up. To be sure of things like this I make things very explicit. Telling playlist “music” of source “library” is telling a very specific object in iTunes. But reversing the order should not make a difference. In the test I did it looks like it is just returning the count from the second part of the compare. There is no interpretation of anything where that would be right.

tell application "iTunes"
	tell playlist "music" of source "library"
		count (every track whose (composer ≠ "")) -- returns 1228
		count (every track whose (work ≠ "")) -- returns 0
		count (every track whose (composer ≠ "") and (work ≠ "")) -- returns 0
		count (every track whose (work ≠ "") and (composer ≠ "")) -- returns 1228
	end tell
end tell

iTunes evolves a lot and therefore I don’t trust what’s going on with it unless I am very specific with it.

Bill

Ed,

I think your script is very interesting. You first did it by reading the attributes then actually used a loop and checked it. That’s one step father then I would have thought to go. Unfortunately since I have zero items where work is not the empty string so the line

set workList to (id of every track whose work ≠ "") 

got an error since there are no tracks to get ids from. I was curious what the script ended up returning. Can you post the results of the script.

Bill

Unfortunately I can’t on this machine. I’m also getting 0 for works ≠ “”

If I recall when I ran the script at home the result (composersOfNoWork) was a short list of podcast creators.

Ed,

What I was curious about was if it also returned 3 items.

Bill

Well, technically, the last returned result of the script is the last result from the repeat loop. But the variable being set in the repeat loop does end up with three items.

The three “works” that were found were podcasts and the three items were the producers’ names.

Whenever AppleScript baffles me, I my favorite reference, which is Matt Neuburg’s book AppleScript: The Definitive Guide, 2nd Edition. Starting on page 201, he shows several examples of filtering data using ONE boolean whose/where test. On page 203, he mentions the possibility of combining TWO or more such tests, such as those in letterman’s example. Here’s what Neuburg says about it:

If the target application is willing, you may even be able to combine multiple boolean tests… The [multiple] boolean test, where it works, is a very powerful specifier; with a single Apple event you’re getting the target application to do a lot of searching for you. Unfortunately, you never know whether it will work; only experimenting will tell you.

In letterman’s two examples, the first boolean test succeeds, while the second is ignored. That (sort of) makes sense, if iTunes’ implementation is indeed one that does NOT support multiple tests. So the bug is not that iTunes fails to support multiple boolean tests, but rather that it compiles and executes unsupported code without throwing an error.

Stan C.

Thanks everyone for the discussion, so far! I for one will try to write up a bug report one of these days. Hope I’m not the only one.

It looks like, at the very least, grouping, movement, movement number, movement count, album artist, comment, description, kind each have the same limitation, in that they are ignored when they occur as the first operand in certain boolean conjunctions. I think it’s up to the iTunes team to figure this one out?

A few more examples:

tell application "iTunes"
count every track whose work = "nonsense" and artist ≠ ""
	--> 22999
count every track whose work = "nonsense" and grouping ≠ ""
	--> 0
count every track whose grouping = "nonsense" and composer ≠ ""
	--> 13793
count every track whose grouping = "nonsense" and album artist ≠ ""
	--> 0
count every track whose movement = "nonsense" and name ≠ ""
	--> 23001
count every track whose movement = "nonsense" and grouping ≠ ""
	--> 0
count every track whose movement number = -900 and name ≠ ""
	--> 23001
count every track whose movement number = -900 and grouping ≠ ""
	--> 0
count every track whose movement count = -900 and name ≠ ""
	--> 23001
count every track whose movement count = -900 and grouping ≠ ""
	--> 0
end tell

I guess one has to be very careful with the order in which properties are checked. I don’t think Bob Dylan was quite this prolific. :unamused:

tell application "iTunes"
count every track whose artist = "Bob Dylan" and album artist = "Bob Dylan" and name ≠ ""
	--> 23001
end tell

vs.

tell application "iTunes"
count every track whose name ≠ "" and artist = "Bob Dylan" and album artist = "Bob Dylan"
	--> 733
end tell

Not quite. All other things being equal, compound whose clauses are provided free with Cocoa Scripting. For example, you can use a compound whose test like this:

tell application "Script Debugger"
	name of documents whose event log visible is false and compiled is true
end tell

SD includes code for the event log visible and compiled properties, but the whose clauses are all done automatically by Cocoa Scripting. So the failure in iTunes can be called a bug.

(A developer might want to implement their own tests for reasons of efficiency where collections can be very large. In that case, they may also be responsible for handling compound whose clauses, and Cocoa Scripting can get a bit tricky. I suspect that’s the case here.)

Hey Folks,

It would seem that this notation works properly:

tell application "iTunes"
   set trackList to tracks where its artist is "U2" and its name is "The Troubles" and its sort artist is ""
   count of trackList
end tell

--> 1

-Chris

It’s not about the notation but the order of the properties that you’re checking, that is artist -> name -> sort artist in your example appears to be a safe ordering when checking these properties. But if you instead begin with checking the sort artist tag, it will be not be taken into account at all. Just something to be aware of currently:

tell application "iTunes"
set trackList to tracks where its sort artist = "nonsense" and its artist is "U2" and its name is "The Troubles"
count of trackList
end tell
--> 1

By the way, the movement/work tags are obviously fairly new to iTunes, but as for the rest of the examples I’ve posted where these tags are not involved, the incorrect behavior does not appear to be present on an earlier version of iTunes (9, tested on OS X Tiger). So, this appears to be a regression. Unfortunately that fairly outdated version of iTunes is the only one I can compare the current one to right now, so I wouldn’t be able to say which version exactly introduced this issue.

That’s interesting. There is another approach: the iTunes framework. So something like this works:

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

set {iTunesLib, theError} to current application's ITLibrary's libraryWithAPIVersion:"1.0" |error|:(reference)
set thePred to current application's NSPredicate's predicateWithFormat:"(artist.name BEGINSWITH[cd] %@) AND (title BEGINSWITH[cd] %@)" argumentArray:{"a", "b"}
set theTracks to iTunesLib's allMediaItems()
set x to ((theTracks's filteredArrayUsingPredicate:thePred)'s valueForKey:"title") as list

But…

There are two serious downsides:

*There are security issues that make it very hard to use. The documentation says it has to be run from a code-signed application, but it won’t run from SD. It does run from Script Editor, which probably has extra privileges, and I can’t seem to get to run from a code-signed applet. It may well require a sandboxed app these days.

  • Only a limited range of properties are exposed directly. It wouldn’t surprise me if the ones exposed directly behave correctly when scripting iTunes, and the trouble begins when you choose one of the others.

If those limitations didn’t rule it out, its use of an NSPredicate means at least the filtering works correctly.

Hey Shane,

Hmm… It works fine for me with both SD 6.0.3 (6A191) and SD 5.0.12 (207).

It also runs from both FastScripts and Keyboard Maestro.

macOS 10.12.2

-Chris

Ah, OK. I now realise I was using an unsigned debug version of SD.