Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change the order of a song within an iTunes playlist by AppleScript

How can I move a song that's in a playlist to a different position within the list with an AppleScript command?

I already have the song and the playlist, and the one is already in the other; I need only to change the position.

My goal is to sort the tracks that the user has selected; ideally, I'd want to move the first-by-order track to the position of the first-in-selection track, and order all the other selected tracks immediately and sequentially after the first-by-order one.

like image 643
Peter Hosey Avatar asked Oct 30 '10 01:10

Peter Hosey


2 Answers

The move command does appear to be pretty buggy. In my experimenting, it appears that no matter what location you give it to move a track to, it moves it to the end of the playlist. This should still be manageable though, if a bit inefficient. The syntax for this looks like:

tell application "iTunes"
move first track of playlist "foo" to end of playlist "foo"
end tell

There are several other constructions you're supposed to be able to use instead of "end of", including things like 'beginning of playlist "foo"', 'after track 5 of playlist "foo"', and 'before track 5 of playlist "foo"', but none of those appear to work as expected. But, if you basically get your tracks in a list sorted the way you want, you should be able to just iterate the list, tell iTunes to move each track in succession to the end of the playlist, and you'd end up with the sorted order after it's all done.

like image 72
Brian Webster Avatar answered Sep 22 '22 01:09

Brian Webster


Here is a solution, but it's indirect, because it imports an XML file.

The script creates an XML file, such as export a playlist from iTunes. When the script has finished creating the XML file, it imports the file in iTunes, iTunes creates another Smart Playlist with the same name, the script switch to the new playlist and delete the original. It work also on non-contiguous selection.

set b to false
tell application "iTunes"
    set selTracks to selection
    if (count selTracks) < 2 then return my displayAlert("Select 2 or more tracks")
    set selPlaylist to container of item 1 of selTracks
    try
        tell selPlaylist to set b to special kind is none and smart is true
    end try
    if not b then return my displayAlert("Not a smart playlist")
    set {oldFindexing, fixed indexing} to {fixed indexing, false}
    set firstIndex to index of item 1 of selTracks
    set fixed indexing to oldFindexing

    --**** do something with these selections  **********
    set sortedTracks to reverse of selTracks -- ***** This example reverses the order. ********
end tell
my moveSelectedTracks(sortedTracks, selPlaylist, firstIndex)

on moveSelectedTracks(selT, selP, n)
    script o
        property tDataIDs : {}
        property sTracks : selT
        property tArgs2 : {}
    end script
    set L to {}
    set tc to count o's sTracks
    tell application "iTunes"
        set o's tDataIDs to database ID of tracks of selP -- get id of the tracks in the playlist
        set theID to persistent ID of selP
        repeat with i from 1 to tc -- get id of the each sorted track
            set item i of o's sTracks to "<key>" & (get database ID of (item i of o's sTracks)) & "<"
        end repeat
    end tell

    set tc to count o's tDataIDs

    --- make arguments
    repeat with i from 1 to tc
        if i = n then set o's tArgs2 to o's tArgs2 & o's sTracks
        set t to "<key>" & item i of o's tDataIDs & "<"
        if t is not in o's sTracks then set end of o's tArgs2 to t
    end repeat

    set {oTID, text item delimiters} to {text item delimiters, linefeed}
    set o's tArgs2 to o's tArgs2 as text --convert a list to text (one argument per line)
    set text item delimiters to oTID

    set xmlLib to my get_iTunes_Library_xml() -- get path of "iTunes Library.xml"
    set tFile to (path to temporary items as string) & "__xzaTemp_Playlist321__"
    set tempF to quoted form of POSIX path of tFile

    try --- write arguments to a temporary file
        set openfile to open for access file (tFile & ".txt") with write permission
        set eof of openfile to 0
        write (o's tArgs2) to openfile starting at eof
        close access openfile
    on error err
        try
            close access file tFile
        end try
        return my displayAlert("Error when writing to a temporary file.\\n" & err)
    end try

    -- ** create the XML file, grep write the beginning of the xml File
    do shell script "/usr/bin/grep -m1 -B40 '   <dict>' " & xmlLib & " > " & (tempF & ".xml")

    (* append to the xmlFile:
    grep read each argument and search track info in "iTunes Library.xml", perl clean up the output 
    grep search the info of the smart playlist and write it
    sed change all arguments to array of dicts (this will be the order of each track in the playlist)  
    echo write the end of the xml File.
    *)
    do shell script "(tmp=" & tempF & ".txt; /usr/bin/grep -A62 -F -f \"$tmp\" " & xmlLib & " |/usr/bin/perl -pe 'undef $/; s|</dict> ((?:(?!</dict>).)*)\\n--|</dict>|sgx; s:</dict>(?!.*</dict>).*|</dict>\\s*</dict>\\s*<key>Playlists</key>.*:</dict>:sx;'; echo '</dict>\\n<key>Playlists</key><array>' ; /usr/bin/grep -m1 -A42 -B3 '>Playlist Persistent ID</key><string>" & theID & "<' " & xmlLib & " | /usr/bin/grep -m1 -B40 '<array>'; /usr/bin/sed 's:$:/integer></dict>:; s:^<key>:<dict><key>Track ID</key><integer>:' \"$tmp\" ; echo '</array></dict></array></dict></plist>') >> " & (tempF & ".xml")

    set tFolder to ""
    set b to false
    tell application "iTunes"
        set {tName, songRepeat} to {name, song repeat} of selP
        add ((tFile & ".xml") as alias) -- import the XML file as Smart Playlist
        try
            set tFolder to parent of selP -- if the smart playlist is in a folder playlist
        end try
        set selP2 to last user playlist whose name is tName and its smart is true -- get the new smart playlist

        if (persistent ID of selP2) is not theID then -- else no importation 
            if tFolder is not "" then move selP2 to tFolder -- move to the folder playlist
            reveal (track n of selP2) -- select the same row in the imported playlist
            try
                tell current track to set {dataID, b} to {database ID, its container = selP}
            end try
            if b then -- the current track is in the smart playlist
                set {tState, tPos} to {player state, player position}
                play (first track of selP2 whose database ID = dataID) -- play the same track
                set player position to (tPos + 0.4) --  same position
                if tState = paused then
                    pause
                else if tState is stopped then
                    stop
                end if
                set song repeat of selP2 to songRepeat -- this line doesn't work on iTunes 11
            end if
            delete selP -- delete the smart playlist (the original)
        end if
    end tell
    do shell script "/bin/rm -f " & tempF & "{.txt,.xml} > /dev/null 2>&1 &" -- delete the temp files
end moveSelectedTracks

on get_iTunes_Library_xml()
    do shell script "/usr/bin/defaults read com.apple.iApps iTunesRecentDatabases |/usr/bin/sed -En 's:^ *\"(.*)\"$:\\1:p' |/usr/bin/perl -MURI -e 'print URI->new(<>)->file;'"
    return quoted form of the result
end get_iTunes_Library_xml

on displayAlert(t)
    activate
    display alert t
end displayAlert

This script run on (Mac OS X 10.4 ... 10.7) , (iTunes 7.5 ... 10.6.3).

The script doesn't work on older versions, I don't know about newer versions.


I know :

Mountain Lion use FreeBSD's grep instead of GNU's grep, FreeBSD's grep is extremely slow on Mountain Lion (30 to 100 times slower according to what I read), so this script will also be slow.

Tunes 11 breaks the AppleScript command to song repeat a playlist. The value of song repeat can still be read with get, it just can’t be set, so comments the line in the script.

like image 31
jackjr300 Avatar answered Sep 20 '22 01:09

jackjr300