AppleScript for Beginners V - Testing & Shorthand

Let’s quickly review the script from our last visit together; the fourth in this series.


tell application "iTunes"
   set tracks_To_Play to (every track of playlist 1 whose played count = 0)
   repeat with play_This in tracks_To_Play
       if play_This's rating = 0 then
           play play_This
       end if
       delay ((play_This's duration) - 8)
       stop
       set {play_This's played count, play_This's played date} to {1, (current date)}
   end repeat
end tell

We start it off by making a list (tracks_To_Play) of every track in the library that has never been played, by selecting those which the played count property is 0. We then repeat through this list of tracks, and every time we find one with a rating of 0, we tell iTunes to play it. After the test is completed, the script delays for the duration of the track (minus 8 seconds), then the track stops playing, and the track’s played count and played date properties are altered to reflect being played once today.

I will tell you right now that this is a pretty sloppy and poorly written script, with some pretty glaring errors, some of which you may have noted already if you tried running the thing. For instance, regardless of whether or not the track is played, it will still delay and set both the played count and played date properties of whichever track is in the loop. Looking at it now, a few weeks after I wrote it, it is perfectly obvious that I am neither a professional scripter, nor an experienced technical writer. Oh well, I suppose it had to happen sometime.

We may return to this piece of junk later on, but I have a better idea. Let’s start afresh with something new in order learn the fun and excitement of using if/then/else clauses to test items in a list. For instance, say you have a playlist you made for your dad (and entitled “Dad” as well, you creative genius), and you are wondering which track is the shortest. You can’t just tell iTunes to find the shortest track, you have to loop through them all, testing each one to see which is the shortest:


tell application "iTunes"
	set dad_tracks to every track of playlist "Dad"
	set shortest_one to item 1 of dad_tracks
	repeat with a_track in dad_tracks
		if (a_track's duration) < (shortest_one's duration) then
			set shortest_one to a_track
		end if
	end repeat
	play shortest_one
end tell

The list of tracks is dad_tracks and we set the variable shortest_one to the first track of this list. It does not matter which track we choose, since we have to loop through the entire list anyway, and every time we find one of shorter duration that the current shortest_one, we are going to replace it. You can see this in the repeat loop; as we cycle through each track (yes, including the first one that we chose to occupy the shortest_one variable), we compare the durations of the two tracks, and if the track in the loop (a_track) has a shorter duration than the track in the shortest_one variable, the next line is executed, which then replaces the shortest_one with the a_track. At the end of the script, the shortest one is played.

The if/then testing abilities of AppleScript are very useful, for all sorts of things, and you can test for more than one thing at a time, on the same line. For instance, let’s say we wanted to find the shortest track of Dad’s playlist that had never actually been played. We could re-write the if line like this:


if (a_Track's duration) < (shortest_one's duration) and (a_Track's played count) = 0 then

Now, the track in the loop has to pass two tests in order to occupy the variable shortest_one; not only must the duration be less than the current shortest_one, it must also have a played count of zero (0). I have not checked the AppleScript Language Guide (I suppose I probably should), but I do not believe there are any limits on how many tests can be placed on a single line. Try this weirdness out:


if (a_Track's duration) < (shortest_one's duration) and (a_Track's played count) = 0 and (a_Track's artist) contains "a" then

Now, we are simply testing to see if the artist of the track in question contains the letter a. You get the idea.

Nifty. What if we also wanted to find the longest track at the same time Sure, we could build an entire second loop to go through the list again using a different variable and test the track durations for the largest, but that would be way too much work. We use AppleScript to lessen our workload, and fortunately, AppleScript gives us a very useful term that we can use inside the very same if/then loop, called else. Take a look at this variation of the original script:


tell application "iTunes"
	set dad_tracks to every track of playlist "Dad"
	set shortest_one to item 1 of dad_tracks
	set longest_one to item 1 of dad_tracks
	repeat with a_Track in dad_tracks
		if (a_Track's duration) < (shortest_one's duration) then
			set shortest_one to a_Track
		else if (a_Track's duration) > (longest_one's duration) then
			set longest_one to a_Track
		end if
	end repeat
	play longest_one
end tell

Notice that we also set the longest_one variable to the first track of dad_tracks, and using the else if modification, any time a_track does not pass the first test (meaning that a_Track is NOT less duration than the current shortest_one), a second test is performed to see if perhaps a_Track is longer duration than the current longest_one. In this fashion, each track in the list dad_tracks is compared against either the shortest or the longest tracks that currently are held in the variables shortest_one & longest_one. When the script finishes, both tracks will be known, although this time, we have instructed iTunes to play the longest_one. (If you play around with it, you can probably figure out how to play both of them.)

We need not use the else if form if we do not wish to, we can just say say else. In that case, whenever the previous test(s) fail, whatever the script is told to do after the plain old else is executed. Check out this final variation:


tell application "iTunes"
	set dad_tracks to every track of playlist "Dad"
	set shortest_one to item 1 of dad_tracks
	set longest_one to item 1 of dad_tracks
	repeat with a_Track in dad_tracks
		if (a_Track's duration) < (shortest_one's duration) then
			set shortest_one to a_Track
		else if (a_Track's duration) > (longest_one's duration) then
			set longest_one to a_Track
		else
			play a_Track
			delay 4
		end if
	end repeat
	play longest_one
end tell

In this form, if a_Track is neither shorter than the current shortest_one nor longer than the current longest_one, iTunes is directed to play the track, but only for 4 seconds. This version is actually kind of fun, I have it running as I write this, and am enjoying listening to a few seconds of every track that it cycles through.

Of course, the if/then/else testing does not need to be done in a loop format. Anytime you need to check for something, you can use it. Try this on for size:


if (current date)'s date string contains Saturday then
	say "Today is Saturday"
else
	say "I don't know what the day is, but it's not Saturday, my friend."
end if

Isn’t it fun to make your computer talk to you In fact, it is so much fun, I had to modify it a little bit:


if (current date)'s date string contains Saturday then
	say "Today is Saturday"
else
	say ("I  know you are testing for Saturday, but today is" & ((current date)'s weekday as string))
end if

Sorry, I got a little carried away. I just love this stuff. Since we are approaching the bottom of the window, I would like to mention some cool shorthand features of AppleScript that you may not be aware of. Although not HUGE time savers, they are good to know nonetheless. For instance, when you want to write a tell application block, you only need to write this:


tell app "itunes"
--Make a script
end

When you compile it, it is immediately converted thusly:


tell application "iTunes"
	--Make a script
end tell

So, you need not completely type [b]application[/b], nor do you need to capitalize [b]iTunes[/b], and as long as you just type end at the end of the script, Script Editor automatically knows to make it an end tell. In fact, as long as you keep your ends straight, Script Editor will convert all of them to their correct forms, regardless of whether they are end returns, end ifs, end tells, or any other kind of ends, like this:


tell app "iphoto"
set all_albums to the name of every album
repeat with a_Name in all_albums
if (count ((a_Name as string)'s every character)) > 10 then
say ("Album " & a_Name & " contains " & (count ((a_Name as string)'s every character)) & " letters.")
else
say "Here are the letters of " & a_Name
repeat with a_Letter in (a_Name as string)'s every character
say a_Letter
delay 0.5
end 
end 
end 
end

Isn’t that cool If you just pay attention to the number of end statements you need, you just keep listing them along, and the compiling takes care of the details for you. In fact, even if you forget one, the error you often get is Expected “end” but found “end tell”. With that, you just shrug, throw in another end, let Script Editor figure out what kind of end it is, and continue on your merry way.

All right, I am going to try one more time to leave you with a script that hints at our next topic. I will try a little harder this time to come up with something less crummy than last time. Don’t worry if it looks a little busy, we should be able to cover it all in our next discussion. Here goes:


tell application "iTunes"
	set all_playlist_names to the name of every playlist
	set your_choice to item 1 of (choose from list all_playlist_names with prompt "Pick a playlist, any playlist...")
	set the_guess to text returned of (display dialog "Guess how many tracks are in playlist " & your_choice default answer "")
	if (the_guess as number) = (count (every track of playlist your_choice)) then
		display dialog "WOW!  You are a genius!  Enjoy this track." giving up after 4
		play track (random number from 1 to (count (every track of playlist your_choice))) of playlist your_choice
	else
		display dialog "Wrong!!" & return & "There were " & (count (every track of playlist your_choice)) & " tracks.  You were off by " & ((count (every track of playlist your_choice)) - the_guess)
	end if
end tell