I am not trying to sort the playlist in the iTunes window. I need to work through the main Music playlist, but ordered by artist and then album properties of each track. I’ve got the script working doing a bunch of stuff, with the list simply as it comes, but want to do more that would work better if the playlist was sorted as I describe.
Yes I could save a file and use shell sort and or uniq commands, but then have to link the output back to the actual track in iTunes in order to do what else I need. It’s just long winded and messy.
Can anyone help here? Is there any way to work track by track through a playlist ordered by specified criteria?
Yes indeed, but that requires manual intervention. I wanted this to need no additional messing about. Just export ALL the music data, i.e. base it on the Music special playlist (excluding videos). Even if I were to create the new playlist, I couldn’t sort it programatically. It would require the user to sort it correctly which is no good.
This is mostly untested as I don’t use iTunes myself and don’t have any tracks in it! But it should be pretty close, judging from the dictionary. Hopefully it’ll return a list of iTunes references to the tracks in your “Music” playlist, ordered as per your query:
getOrderedTracks("Music")
on getOrderedTracks(playlistName)
-- Comparison customiser for a customisable sort.
-- We'll be sorting a list of lists, each sublist containing an artist name, a track id, and an album name. With this customiser, sublist a is "greater" than sublist b if its artist is lexically greater than b's or their artists are the same and a's album is lexically greater than b's.
script onArtistThenAlbum
on isGreater(a, b)
set artistA to beginning of a
set artistB to beginning of b
return ((artistA > artistB) or ((artistA = artistB) and (end of a > end of b)))
end isGreater
end script
try
-- Get parallel lists of the artists, ids, and albums of all the tracks in the specified playlist.
tell application "iTunes" to ¬
set {theArtists, theIDs, theAlba} to {artist, id, album} of tracks of playlist playlistName
set trackCount to (count theIDs)
on error number -1728
-- Can't get the required data for some reason, eg. the playlist's empty or doesn't exist.
set trackCount to 0
end try
set sortedTracks to {}
if (trackCount > 0) then
-- Mix the three lists down to a single list of three-item 'track' sublists.
repeat with i from 1 to trackCount
set end of sortedTracks to {item i of theArtists, item i of theIDs, item i of theAlba}
end repeat
-- Sort the result (on artist, subsorting on album).
CustomShellSort(sortedTracks, 1, -1, {comparer:onArtistThenAlbum})
-- Replace each sublist with a reference to the relevant track in iTunes.
repeat with i from 1 to (trackCount)
set thisID to item 2 of item i of sortedTracks
tell application "iTunes" to ¬
set item i of sortedTracks to track id thisID of playlist playlistName
end repeat
end if
return sortedTracks
end getOrderedTracks
-- Customisable Shell sort.
-- Shell sort algorithm: Donald Shell, 1959. AppleScript implementation Nigel Garvey, 2010.
on CustomShellSort(theList, l, r, customiser)
script o
property comparer : me
property slave : me
property lst : theList
on shsrt(l, r)
set step to (r - l + 1) div 2
repeat while (step > 0)
slave's setStep(step)
repeat with j from (l + step) to r
set v to item j of o's lst
repeat with i from (j - step) to l by -step
tell item i of o's lst
if (comparer's isGreater(it, v)) then
set item (i + step) of o's lst to it
else
set i to i + step
exit repeat
end if
end tell
end repeat
set item i of o's lst to v
slave's rotate(i, j)
end repeat
set step to (step / 2.2) as integer
end repeat
end shsrt
on isGreater(a, b)
(a > b)
end isGreater
on rotate(a, b)
end rotate
on setStep(a)
end setStep
end script
set listLen to (count theList)
if (listLen > 1) then
if (l < 0) then set l to listLen + l + 1
if (r < 0) then set r to listLen + r + 1
if (l > r) then set {l, r} to {r, l}
if (customiser's class is record) then set {comparer:o's comparer, slave:o's slave} to (customiser & {comparer:o, slave:o})
o's shsrt(l, r)
end if
return -- nothing.
end CustomShellSort
Hmm, thanks, but kinda long winded. However, it got me thinking and here’s an alternative using satimage osax:-
tell application "iTunes"
{artist, album, id} of tracks of splMusic
set {theArts, theAlbs, theIDs} to sortlist the result with respect to {1, 2}
repeat with thisID in theIDs
tell track id thisID of splMusic
log (get {artist, album, name})
end tell
end repeat
end tell
It’s all in the first 2 lines really. The repeat loop is just an example of how to then use the tracks is the chosen sort order.
Long rather than long-winded, since the sort is set up and executed using vanilla code, there’s an error trap in case the playlist is empty, and the process is wrapped in a handler for convenience of use. But if you’re au fait with Satimage OSAX (which I didn’t assume as you’re new to the forum), that’s fine. Less code in the script itself and probably slightly faster. I’m glad the overall approach solved your problem.
The above line is great as a fast method of loading up 2 lists simultaneously, but for my purpose I realised I really need:-
tell application "iTunes"
set (theSort,theIDs} to {location, id} of tracks of splMusic
...
end tell
Easy enough to change, but since ‘location’ is a file alias, satimage complains and so cannot sort by that. So what I really need could be imagined as:-
tell application "iTunes"
set (theSort,theIDs} to {location as text, id} of tracks of splMusic
...
end tell
But that won’t work. Well, no error, but the result is not what is expected. It creates theSort list as a before, a list of aliases and then converts the list to text.
So far, the only option I can thing of is to re-iterate through theSort list and change each item for what I want, i.e. as text for sorting and also in fact to extract a portion of the string. But I want to avoid multiple iterations through lists of many thousands of items (i.e. iTunes tracks) as it will take a significant time to do so.
Can anyone suggest a neat and clever way to load up 2 ‘parallel’ lists but able to convert the file alias to text along the way?
Something like this should do it if the locations are returned as aliases:
tell application "iTunes" to set {theSort, theIDs} to {location, id} of tracks of splMusic
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to linefeed
set theSort to paragraphs of (theSort as text)
set AppleScript's text item delimiters to astid
sortlist {theSort, theIDs} with respect to 1 -- Requires Satimage OSAX.
It’s the location that’s the problem as that is a file alias and satimage cannot sort a list of aliases. So it needs to be converted to text in some way first, prior to sorting and I’m trying to find a way to avoid having to iterate through the whole list just for that.
Ah, ok. I’m not (yet) familiar with ObjC integration into AppleScript. I obviously need to look into it further.
However, would there be a similar simple one-liner that would do what you suggest, but convert the aliases into only parts of the POSIX path? It’s in iTunes and I want only the Artist and Album portions of the path. So splitting the string on the “/” it’s "text items -3 thru -2 I need, just that.
Currently I’m extracting that from the “location as text” later, as I work through the list of tracks, but it would be more efficient if there was a single line that would produce the exact Artist/Album list that I need. It is that section of the location that I need and cannot really be created just from the artist and album properties.
Could that be done within the framework you suggest?
About the best you could get is something like this:
set subPaths to ((current application's NSArray's arrayWithArray:fileList)'s valueForKeyPath:"path.stringByDeletingLastPathComponent")
set theAlbums to (subPaths's valueForKey:"lastPathComponent") as list
set theArtists to (subPaths's valueForKeyPath:"stringByDeletingLastPathComponent.lastPathComponent") as list
I’m not clear exactly what you want here, but Shane’s POSIX path suggestion could be used like this:
use scripting additions
use framework "Foundation"
tell application "iTunes" to set {theLocations, theIDs} to {location, id} of tracks of splMusic
set thePaths to ((current application's NSArray's arrayWithArray:theLocations)'s valueForKey:"path") as list -- POSIX paths.
-- Sort the paths, sorting the IDs in parallel.
set {sortedPaths, sortedIDs} to (sortlist {thePaths, theIDs} with respect to 1 comparison 1) -- Requires Satimage OSAX.
-- Get a parallel list of "artist/album" strings.
set artistAlbumStrings to (change "^.+/([^/]+/[^/]+)/[^/]+/?$" into "\\1" in sortedPaths with regexp) -- Ditto.
tell application "iTunes"
-- Use the sorted ID list here.
end tell
The Satimage stuff could probably be done with ASObjC too ” or indeed the ASObjC stuff with Satimage:
set thePaths to (absoluteURL theLocations as unix path) -- Satimage OSAX.
Thanks for all your help guys. I’ve got something that I think should be OK. Works great now, but only testing on a few dozen tracks. Ultimately it has to work through 25,000 or more tracks, so I want to avoid too much repetitive copying of, or iteration through what will be quite large lists.
Thanks again. If it all falls over, I may be back.