A simple Rails app for finding missing artwork for your iTunes collection

Ok, I’ve been a bit compulsive about having artwork for my movies and music in iTunes since we got our AppleTV. Ok, maybe more than a bit. ;) Even so, this little Rails application should give you some idea of how simple it is. It uses Amazon’s web service (which, it turns out, I misjudged), to look up candidate artwork for each album and displays them to you. You simply click on the desired image (higher resolution is typically better) and it will insert that artwork into iTunes for each track in your album.

I won’t pretend that this is the cleanest implementation but it gets the job done. If any of you watching at home want to play along, you will also need ruby/amazon installed.

FYI: Amazon’s search engine may not resolve all of your album names. You may want to manually search Amazon, in the case of stubborn albums, to determine what Amazon calls the album. However, more often than not, this shouldn’t be a problem if you ripped using iTunes.

Finally, this program could be easily adapted for hunting down artwork for your movie collection as well.

Code follows:

AlbumController.rb:

    require 'win32ole'
    require 'amazon/search'

    class AlbumController < ApplicationController
      def fetch_file_with_url(url, meta)
        response = Net::HTTP.get_response(URI.parse(url))
        title = meta.product_name
        title.gsub! ':', '-'
        title.gsub! '/', '-'
        art_filename = ""
        art_filename << session[:ART_PATH] << title 
        art_filename.gsub! ' ', ''
        art_filename.gsub! '"', ''    
        art_filename.gsub! '\\'', ''
        art_filename.gsub! '(', ''
        art_filename.gsub! ')', ''        
        if art_filename.length > 60
          # iTunes COM API seems to have issues with long file names
          art_filename = art_filename[0,59]
          puts art_filename
        end
        art_filename << ".jpg"    
        File.open( art_filename, 'wb') do |f| 
          f << response.body
          f.flush
        end
        size = File.size(art_filename)
        art_filename
      end

      def reset
        session[:track_counter] = nil
        session[:skip_hash] = {}
        redirect_to '/album/iterate_through_coverless_albums'
      end

      def next
        session[:skip_hash][session[:current_album]] = 1
        session[:track_counter] += 1
        redirect_to '/album/iterate_through_coverless_albums'
      end

      def process_input
        album_candidates = lookup_album session[:current_album], session[:current_artist]
        update_artwork_with params[:image_url], album_candidates[params[:idx].to_i]
        self.next
      end  

      def update_artwork_with(artwork_url, meta)  
        art_filename = fetch_file_with_url artwork_url, meta
        return if art_filename.nil?

        # Change path from Ruby to Winblows
        art_filename.gsub! /\\//, '\\\\'

        app = WIN32OLE.new 'iTunes.Application'
        music = app.LibrarySource.Playlists.ItemByName 'Music'
        tracks_in_album = music.Search session[:current_album], 0
        i = 1
        puts "==> #{art_filename}, #{tracks_in_album.Count}"
        while i <= tracks_in_album.Count do
          track = tracks_in_album.Item i
          puts "#{track.Album} <=> #{session[:current_album]}"
          if track.Album != session[:current_album]
            i += 1
            next
          end
          puts "*#{track.Name}"

          track.AddArtworkFromFile art_filename
          i += 1
        end
      end

      def lookup_album(title, artist)
        #puts title
        begin
          req = Amazon::Search::Request.new session[:AWS_KEY]
          res = req.keyword_search(title + " " + artist, 'music') 
          return res.products.clone
        rescue
          puts "excepton"
          nil
        end
      end

      def iterate_through_coverless_albums
        if session[:track_counter].nil?
          # First time through.  Setup.
          session[:AWS_KEY] = 'XXXXXXXXXXXXXXXXXXXXX'  # Go get your own AWS key :P
          session[:ART_PATH] = 'f:/music/artwork/'
          session[:track_counter] = 1
          session[:skip_hash] = {}
        end

        app = WIN32OLE.new 'iTunes.Application'
        music = app.LibrarySource.Playlists.ItemByName 'Music'
        total_count = music.Tracks.Count

        while session[:track_counter] <= total_count do
          track = music.Tracks.Item session[:track_counter]
          if track.Artwork.Count == 0 and not session[:skip_hash].has_key? track.Album
            @album_candidates = lookup_album track.Album, track.Artist
            if not @album_candidates.nil?
              # We have our next contestant.  Let's break
              #puts "Got candidates: #{@album_candidates.length}"
              @current_album = session[:current_album] = track.Album
              @current_artist = session[:current_artist] = track.Artist
              break
            end
          end
          session[:track_counter] += 1  
        end
      end

      def index
        iterate_through_coverless_albums
        render :action => 'iterate_through_coverless_albums', :controller => 'album'
      end
    end

iteratethroughcoverless_albums.rhtml:

    script type = "text/javascript">
function submit_form(url, idx)
{
art.image_url.value = url;
art.idx.value = idx;
art.submit();
}
</script> <% form_tag '/album/reset' do %>
<%= submit_tag 'Reset' %>
<% end %>

<% form_tag '/album/next' do %>
<%= submit_tag 'Next' %>
<% end %>

<h1><%= @current_artist %>, <%= @current_album %></h1>
<% form_tag '/album/process_input', {:name => "art", :id => "art"} do%>
<%= hidden_field_tag "image_url" %>
<%= hidden_field_tag "idx" %>
<% if not @album_candidates.nil? %>
<table>
<th>Album Name</th>
<th>Small Art</th>
<th>Medium Art</th>
<th>Large Art</th>
<% idx = 0
@album_candidates.each do |album| %>
<tr>
<td><%= album.product_name %></td>
<td><%= image_tag album.image_url_small, :onclick => "submit_form('#{album.image_url_small}', #{idx})" %></td>
<td><%= image_tag album.image_url_medium, :onclick => "submit_form('#{album.image_url_medium}', #{idx})" %></td>
<td><%= image_tag album.image_url_large, :onclick => "submit_form('#{album.image_url_large}', #{idx})" %></td>
</tr>
<% idx += 1
end %>
</table>
<% else %>
Done
<% end %>
<% end %>

<% form_tag '/album/next' do %>
<%= submit_tag 'Next' %>
<% end %>

Posted by evan on Sunday, April 15, 2007

blog comments powered by Disqus