iTunes' playlist sorting

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?

Make a new playlist.
Add all your tracks to it
Sort it according to your needs.
Tracks will be delivered to your script in that order.

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.

Thanks anyway.

Hi.

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. :slight_smile:

Yes, thanks again.

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.

If you want to convert theIDs to a list of POSIX paths, and you’re running 10.11, add this to the start of your script:

use scripting additions
use framework "Foundation"

And then use this (outside the iTunes tell block):

set theIDs to ((current application's NSArray's arrayWithArray:theIDs)'s valueForKey:"path") as list

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.

Hmm, I like that. Hadn’t thought of that way. I’ll give it a try. Thanks.

That snippet will convert a list of aliases into a list of POSIX paths in 10.11. The variable name is irrelevant.

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. :expressionless: