Search code examples
objective-capplescriptitunes

How do I convert this Applescript code for talking to iTunes to (Objective)C to increase performance


I have some applescript code for building a representation of an iTunes library on OSX, trouble is for large libraries it is too large. I expect it to be significantly quicker if written in (Objective)C but I dont know how to do this. I know its a bug ask but would anyone be willing to rewrite this as (Objective)C.

set thePath to (POSIX file "/tmp/songkong_itunes_model.txt")
set fileref to open for access (thePath) with write permission
tell application "iTunes"
    set eof fileref to 0
    set mainLibrary to library playlist 1
    repeat with nexttrack in (get every track of mainLibrary)
        if (class of nexttrack is file track) then
            try
                set trackname to name of nexttrack
                set loc to location of nexttrack
                set locpath to POSIX path of loc
                set persistid to persistent ID of nexttrack
                set nextline to trackname & "::" & locpath & "::" & persistid
                tell current application to write nextline & "\n" as «class utf8» to fileref
            end try
        end if
    end repeat
end tell
close access fileref
return ""

Solution

  • "I expect it to be significantly quicker if written in (Objective)C"

    No, it's slow primarily because you're retrieving the data in the most inefficient way possible, one value at a time. The same algorithm will be dog slow in any language, since every single get involves building and sending an Apple event to the iTunes process, which has to interpret the event, look up the value being referred to, and send it back to your process in a second reply event.

    A secondary problem is that AppleScript is notoriously inefficient at iterating across large lists - due to some shoddy implementation, the time it takes to look up a list item increases in direct relation to the list's length, so the time taken to iterate the entire list increases quadratically (i.e. O(n*n) efficiency in Big-O notation). However, there's a standard kludge for getting around that problem, so it shouldn't be a deal breaker.

    You have several options:

    1. If targetting 10.9+, you could use the new iTunesLibrary.framework to retrieve the data.

    2. If you need to support 10.8 or earlier, you could parse the iTunes Music Library.xml file in the user's iTunes music folder.

    3. Rewrite your AS code to minimise the number of Apple events being sent and optimize the iteration process.

    1 and 2 you can figure out yourself. For 3, here's how you retrieve all your data using just 3(!) get events:

    tell application "iTunes"
        tell every file track of library playlist 1
            set tracknames to its name
            set locs to its location
            set persistids to its persistent ID
        end tell
    end tell
    

    That'll give you 3 separate lists which you can iterate over in parallel:

    set thePath to (POSIX file "/Users/has/songkong_itunes_model3.txt")
    set fileref to open for access (thePath) with write permission
    set eof fileref to 0
    
    repeat with i from 1 to length of tracknames
        set nextline to item i of tracknames ¬
            & "::" & POSIX path of item i of locs ¬
            & "::" & item i of persistids
        tell current application to write nextline & "
    " as «class utf8» to fileref
    end repeat
    
    close access fileref
    

    If the repeat loop is also sucking up performance, you can trick AppleScript into performing the lookup in a slightly different way that doesn't suffer the same inefficiency. Haven't actually tested this (don't use iTunes) but I think this should do it:

    tell application "iTunes"
        tell every file track of library playlist 1
            script performancekludge
                property tracknames : its name
                property locs : its location
                property persistids : its persistent ID
            end script
        end tell
    end tell
    
    set thePath to (POSIX file "/tmp/songkong_itunes_model.txt")
    set fileref to open for access (thePath) with write permission
    set eof fileref to 0
    
    tell performancekludge
        repeat with i from 1 to length of its tracknames
            set nextline to item i of its tracknames ¬
                & "::" & POSIX path of item i of its locs ¬
                & "::" & item i of its persistids
            write nextline & linefeed as «class utf8» to fileref
        end repeat
    end tell
    
    close access fileref
    

    (Too lazy to explain the details, but there's a chapter on tweaking performance in the book if you want to read further.)