Controlling iTunes with Ruby

In working on Ruby Scripting Leopard, I’ve been helped by searching for AppleScript examples, such as those at Doug’s AppleScripts for iTunes. To pay it forward, I offer an example to some future searcher after the jump. Since WordPress (or something) is turning quotes into “smart” quotes, follow this link if you want plain text.

This should work on stock Leopard.

I run it via launchd, the replacement for cron(1). Lingon is a convenient way to create a launchd configuration file.

#!/usr/bin/env ruby

# Run to create a playlist named "today’s tunes". The playlist is 
# populated with tracks selected because the first character of their
# names begins with a randomly selected letter or digit. For example, 
# my list today begins like this:
#  Galang
#  Gamma Ray
#  Gary Stomp
#  Gear

require osx/cocoa
include OSX
OSX.require_framework ScriptingBridge

PLAYLIST_NAME = "today’s tunes"

def main
  todays_tunes = make_empty_playlist
  tracks = tracks_beginning_with(some_random_character, all_songs)
  tracks.each do | track |
    track.duplicateTo(todays_tunes)
  end
end

def make_empty_playlist
  ensure_no_playlist(PLAYLIST_NAME)
  p = ITunesPlaylist.alloc.initWithProperties({’name => PLAYLIST_NAME})
  # TODO: why does following fail?
  # LIBRARY.userPlaylists.addObject(p)
  LIBRARY.userPlaylists.insertObject_atIndex(p,0)
  p.shuffle = true
  p
end

def tracks_beginning_with(letter, tracks)
  tracks.find_all do | track |
    name = if starts_with_qualifier?(track.name)
             phrase_tail(track.name)
           else
             track.name
           end
    /^#{letter}/i =~ name
  end
end

def ensure_no_playlist(name)
  p = LIBRARY.playlists.objectWithName(name)
  p.delete if p.exists == 1
end

def all_songs
  LIBRARY.playlists.objectWithName(’Library‘).tracks
end

def some_random_character
  possibilities = (’a‘..’z‘).to_a + (’0‘..’9‘).to_a
  possibilities[rand(possibilities.length)]
end

# Works with either a NSCFString or a string.
# TODO: Change tests to indicate that?
def starts_with_qualifier?(name)
  name =~ /^thes/i || name =~ /^ans/i || name =~ /^as/i
end

def phrase_tail(name)
  name.gsub(/^w+s+/, ‘)
end

def bapp(bundle_name)
  SBApplication.applicationWithBundleIdentifier(bundle_name)
end

ITUNES = bapp(’com.apple.iTunes‘)
LIBRARY = ITUNES.sources.objectWithName(’Library‘)

if $0 == __FILE__
  main
end

One Response to “Controlling iTunes with Ruby”

  1. Philip Woeber Says:

    FYI: I have done a lot of ruby scripting on microsoft with Watir for testing Time Warner Telecom webapps. Before I retired from TWTC, I converted three of my fellow testers to Ruby from Perl. Now I do iTunes for me and Excel for my CPA daughter’s business. I want to use rb-appscript for iTunes and Excel. I copied your text version (see it below), saved it as createPlaylist.rb, ran it to create a playlist named “today’s tunes” from textmate (command-R) and got results below. I still have a lot to learn about my MacPro. Am also delving into RubyCocoa. I will keep on hacking. I love Ruby. I am 68 now. I am a refugee from COBOL, FORTRAN and Sperry aka Unisys mainframes. I have an O’Reilly Safari subscription and bought your RubyCocoa book and roughcut. I also have hard copy of Everyday Scripting that I am working through now. Very instructive. I didn’t know how to use “collect”.

    RubyMate
    Ruby
    Theme:
    copy output
    RubyMate r8136 running Ruby r1.8.6 (/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby)
    >>> createPlaylist.rb

    ruby: unknown type name “tdta”.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    /Users/pwoeber/ruby/rb-appscripts/createPlaylist.rb:64:in `starts_with_qualifier?’: ‘NSString#=~’ doesn’t work correctly. Because it returns byte indexes. Please use ‘String#=~’ instead.
    Program exited.

    Here is the script:
    #!/usr/bin/env ruby

    # Run to create a playlist named “today’s tunes”. The playlist is
    # populated with tracks selected because the first character of their
    # names begins with a randomly selected letter or digit. For example,
    # my list today begins like this:
    # Galang
    # Gamma Ray
    # Gary Stomp
    # Gear

    require ‘osx/cocoa’
    include OSX
    OSX.require_framework ‘ScriptingBridge’

    PLAYLIST_NAME = “today’s tunes”

    def main
    todays_tunes = make_empty_playlist
    tracks = tracks_beginning_with(some_random_character, all_songs)
    tracks.each do | track |
    track.duplicateTo(todays_tunes)
    end
    end

    def make_empty_playlist
    ensure_no_playlist(PLAYLIST_NAME)
    p = ITunesPlaylist.alloc.initWithProperties({’name’ => PLAYLIST_NAME})
    # TODO: why does following fail?
    # LIBRARY.userPlaylists.addObject(p)
    LIBRARY.userPlaylists.insertObject_atIndex(p,0)
    p.shuffle = true
    p
    end

    def tracks_beginning_with(letter, tracks)
    tracks.find_all do | track |
    name = if starts_with_qualifier?(track.name)
    phrase_tail(track.name)
    else
    track.name
    end
    /^#{letter}/i =~ name
    end
    end

    def ensure_no_playlist(name)
    p = LIBRARY.playlists.objectWithName(name)
    p.delete if p.exists == 1
    end

    def all_songs
    LIBRARY.playlists.objectWithName(’Library’).tracks
    end

    def some_random_character
    possibilities = (’a’..’z').to_a + (’0′..’9′).to_a
    possibilities[rand(possibilities.length)]
    end

    # Works with either a NSCFString or a string.
    # TODO: Change tests to indicate that?
    def starts_with_qualifier?(name)
    name =~ /^the\s/i || name =~ /^an\s/i || name =~ /^a\s/i
    end

    def phrase_tail(name)
    name.gsub(/^\w+\s+/, ”)
    end

    def bapp(bundle_name)
    SBApplication.applicationWithBundleIdentifier(bundle_name)
    end

    ITUNES = bapp(’com.apple.iTunes’)
    LIBRARY = ITUNES.sources.objectWithName(’Library’)

    if $0 == __FILE__
    main
    end

Leave a Reply

You must be logged in to post a comment.