Thursday, December 14, 2017

#1 2007-03-19 05:00:59 am

Kevin Bradley
Administrator
From:: Independence, MO
Registered: 2006-03-13
Posts: 548
Website

Advanced iTunes Scripting with Libraries

Pre-Fab Scripting

In my previous columns in this series I've talked about ways of improving your scripts by using "pre-fab" pieces that you can use in more than one script.  A good example of this was the sorting of your list of artists in iTunes so that you can present the user with a more friendly-looking list than if it were unsorted.  This time we'll be building a more complicated script using priority queues from my Nite Flite library.

The Problem

Like a lot of folks from the baby boom generation, I grew up on "album rock".  Some albums were made to be listened to as a cohesive whole.  A good example of this would be Pink Floyd's "Dark Side of the Moon" or Electric Light Orchestra's "Time".  So I like to listen to entire albums sometimes, instead of just a random selection of tracks.  And while you can certainly use iTunes to hunt up an entire album, the problem becomes more tricky when you're trying to automate the filling of an iPod whose size isn't large enough to swallow your entire iTunes library.

To further complicate matters, I like to rotate my music on the basis of "least recently played."  iTunes handles this natively for tracks, but can't pull entire albums this way.  For that, we need Applescript!

So, the problem before us is this:  Select several albums that have not been played recently to fill up an iPod playlist of a given size.  We also want to filter out unpopular albums (i.e. - the average rating of all tracks is below 3.5 stars) and any genres that we don't want (country, TV Shows, etc.).  A tall order, no?

First, let's do a thumbnail sketch of the work ahead (See my article on Structured Programming:

--get album list from iTunes
--remove duplicates
--Let's see if the album meets our standards
--Get the average rating of the album
--strip out undesirable tracks
--reorder albums by last played date
--set up playlist


Now that we have a roadmap, things don't seem to difficult.  We'll also want the priority queue library, so we'll load it first:

Editor's Note: If you don't have the Script Library from Kevin's excellent "Nite Flite 1.2" box of tools in your ~/Library/Scripts/ folder, you can download it from ScriptBuilders.net by clicking on this download link before proceeding. In the unzipped Nite Flite folder, you'll find examples and a folder named "Script Library". Put that in (or copy it to) your Scripts folder and you are ready to roll. Note also that in the complete script is assembled at the end of the article; there is no need to download the pieces one by one as you read along. Adam Bell


Applescript:


--load the library
set scriptPath to (path to scripts folder from user domain) & "Script Library:"
set priorityLib to load script ((scriptPath as text) & "priorityLib.scpt") as alias

And we'll create a new priority queue:

Applescript:


--create a newpriQueue
copy priorityLib's newPriQueue to albumsPriQueue
set priorityLib's newPQItem to {priority:"", cargo:""}

And we need to get some user input, namely, how large a playlist to create.  I've included definitions for kilobytes, megabytes, and gigabytes mostly for convenience:

Applescript:


--definitions
set kb to 1024
set mb to 1000 * kb
set gb to 1000 * mb
display dialog "How many gigabytes should I gather?" default answer "1.0"
set inputGB to text returned of the result
set NanoThreshold to (inputGB as real) * gb

And we have some definitions that we'll need further on:

Applescript:


--more definitions
set albumRatingMin to 3.5 * 20 --iTunes uses 0 to 100 internally, not 1-5 stars, so each star is 20
set prohibitedGenres to {"Country", "video", "TV Shows", "Podcast", "Holiday", "Christmas", "Comedy"}
set allAlbumsList to {}

With all that housekeeping out of the way, now we can get down to business!  We'll start by getting a list of albums from iTunes.

Applescript:


--get list
tell application "iTunes"
   set albumList to album of file tracks of library playlist 1 whose podcast is false
   set tcount to count albumList
end tell

Since iTunes will return us a bunch of repeated album names, we'll remove any duplicates.

Applescript:


--remove duplicates
repeat with i from 1 to tcount
   tell my albumList's item i to if it is not in my allAlbumsList and it ≠ "" then set end of my allAlbumsList to it
end repeat
display dialog "Processing " & (count of allAlbumsList) & " Albums" giving up after 5

Now we need to take the winnowed-down list and ask iTunes to fetch the tracks for each album so we can figure out the average rating for the album.  If it's below 3.5 stars, we don't want to use it.

Applescript:


--Let's see if the album meets our standards
tell application "iTunes"
   repeat with anAlbum in allAlbumsList
       set albumTracks to (every file track of playlist 1 whose album is anAlbum)
       
       --Get the average rating of the album
       set numTracks to count albumTracks
       set albumRating to 0
       repeat with aTrack in the albumTracks
           set albumRating to albumRating + (rating of aTrack)
       end repeat
       set albumRating to albumRating / numTracks
--this much will not compile; continued below

We didn't mention it earlier, but we also don't want solitary tracks, so we'll ask for only albums with more than one track.  And we'll now jump into actually choosing tracks and enqueueing them based on the oldest "last played" date of all tracks in the album.  If any track doesn't have a "last played" date, then we will assign the date it was added as its "last played" date. 

Applescript:


--script continued from above
if albumRating > albumRatingMin and numTracks > 1 then
               
               set lastPlayed to (current date)
               set prohibited to false
               
               repeat with aTrack in albumTracks
                   --strip out undesirable tracks
                   if genre of aTrack is in prohibitedGenres then set prohibited to true
                   if played date of aTrack is missing value then
                       set lastPlayed to date added of aTrack
                   else if (played date of aTrack) < lastPlayed then
                       set lastPlayed to (played date of aTrack)
                   end if
               end repeat

               --reorder albums by last played date        
               if not prohibited then
                   -- if we've made it this far, add the album to the queue
                   set albumsPriQueue to priorityLib's priEnqueue(albumsPriQueue, anAlbum, lastPlayed)
               end if
           end if
       end repeat
       --still not complete; more to come

We're now able to remove items from the queue and build our playlist!  If (God forbid!) we don't wind up with ANY albums in our queue, we need to display a dialog to the user, otherwise, we'll de-queue the albums one at a time and add them to the playlist until we reach our quota or we run out of albums.  We also want to check to see if the playlist exists, and if it does, clear it before we begin filling it.  If it doesn't exist yet, let's create it:

Applescript:


--continuing
   if (priorityLib's priQueueIsEmpty(albumsPriQueue)) then
       display dialog "Empty Queue."
   else
       --set up playlist
       if exists user playlist "iPod Albums" then
           delete every file track of user playlist "iPod Albums"
       else
           make new user playlist with properties {name:"iPod Albums"}
       end if
       repeat while ((size of user playlist "iPod Albums") ≤ NanoThreshold) and (not (priorityLib's priQueueIsEmpty(albumsPriQueue)))
           set albumsPriQueue to priorityLib's priDequeue(albumsPriQueue)
           set theResult to priorityLib's priQueueResult
           set theAlbum to cargo of theResult
           display dialog "Adding album " & theAlbum giving up after 1
           duplicate (every file track of library playlist 1 whose album is theAlbum) to user playlist "iPod Albums"
       end repeat
   end if
end tell --done!

"And the rest", as they say, "is history."  This script can take a while to run, if you have a lot of albums in your library.  If you want to spiff up this script, you can add more user interaction and perhaps find places to cut corners and speed it up.  This is a "brute force" method, but it gets the job done.  I'll leave you with the finished script, and (hopefully) the urge to polish this a bit for your own uses.

Applescript:

--load the library
set scriptPath to (path to scripts folder from user domain) & "Script Library:"
set priorityLib to load script ((scriptPath as text) & "priorityLib.scpt") as alias

--create a newpriQueue
copy priorityLib's newPriQueue to albumsPriQueue
set priorityLib's newPQItem to {priority:"", cargo:""}

set kb to 1024
set mb to 1000 * kb
set gb to 1000 * mb
display dialog "How many gigabytes should I gather?" default answer "1.0"
set inputGB to text returned of the result
set NanoThreshold to (inputGB as real) * gb

set albumRatingMin to 3.5 * 20 --iTunes uses 0 to 100 internally, not 1-5 stars, so each star is 20
set prohibitedGenres to {"Country", "video", "TV Shows", "Podcast", "Holiday", "Christmas", "Comedy"}
set allAlbumsList to {}

tell application "iTunes"
   set albumList to album of file tracks of library playlist 1 whose podcast is false
   set tcount to count albumList
end tell

--remove duplicates
repeat with i from 1 to tcount
   tell my albumList's item i to if it is not in my allAlbumsList and it ≠ "" then set end of my allAlbumsList to it
end repeat
display dialog "Processing " & (count of allAlbumsList) & " Albums" giving up after 5

--Let's see if the album meets our standards
tell application "iTunes"
   repeat with anAlbum in allAlbumsList
       set albumTracks to (every file track of playlist 1 whose album is anAlbum)
       
       --Get the average rating of the album
       set numTracks to count albumTracks
       set albumRating to 0
       repeat with aTrack in the albumTracks
           set albumRating to albumRating + (rating of aTrack)
       end repeat
       set albumRating to albumRating / numTracks
       
       if albumRating > albumRatingMin and numTracks > 1 then
           
           set lastPlayed to (current date)
           set prohibited to false
           
           repeat with aTrack in albumTracks
               --strip out undesirable tracks
               if genre of aTrack is in prohibitedGenres then set prohibited to true
               if played date of aTrack is missing value then
                   set lastPlayed to date added of aTrack
               else if (played date of aTrack) < lastPlayed then
                   set lastPlayed to (played date of aTrack)
               end if
           end repeat
           
           --reorder albums by last played date        
           if not prohibited then
               -- if we've made it this far, add the album to the queue
               set albumsPriQueue to priorityLib's priEnqueue(albumsPriQueue, anAlbum, lastPlayed)
           end if
       end if
   end repeat
   
   if (priorityLib's priQueueIsEmpty(albumsPriQueue)) then
       display dialog "Empty Queue."
   else
       --set up playlist
       if exists user playlist "iPod Albums" then
           delete every file track of user playlist "iPod Albums"
       else
           make new user playlist with properties {name:"iPod Albums"}
       end if
       repeat while ((size of user playlist "iPod Albums") ≤ NanoThreshold) and (not (priorityLib's priQueueIsEmpty(albumsPriQueue)))
           set albumsPriQueue to priorityLib's priDequeue(albumsPriQueue)
           set theResult to priorityLib's priQueueResult
           set theAlbum to cargo of theResult
           display dialog "Adding album " & theAlbum giving up after 1
           duplicate (every file track of library playlist 1 whose album is theAlbum) to user playlist "iPod Albums"
       end repeat
   end if
end tell


Nitewing '98
--
I distrust morning people, largely because I suspect them of getting together early one day while the rest of us were asleep and setting up the rules of civilization.


Filed under: iTunes, Libraries

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)