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


iteratethroughcoverlessalbums.rhtml:
    script type = "text/javascript">
function submit
form(url, idx)
{
art.image_url.value = url;
art.idx.value = idx;
art.submit();
}
</script>

&lt;% form_tag '/album/reset' do %&gt;<br />
&lt;%= submit_tag 'Reset' %&gt;<br />
&lt;% end %&gt;<br />


<% formtag ‘/album/next’ do %>
<%= submit
tag ‘Next’ %>
<% end %>

<h1><%= @currentartist %>, <%= @currentalbum %></h1>
<% formtag ‘/album/processinput’, {:name => “art”, :id => “art”} do%>
<%= hiddenfieldtag “imageurl” %>
<%= hidden
fieldtag “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
@albumcandidates.each do |album| %>
<tr>
<td><%= album.product
name %></td>
<td><%= imagetag album.imageurlsmall, :onclick => “submitform(’#{album.imageurlsmall}’, #{idx})” %></td>
<td><%= imagetag album.imageurlmedium, :onclick => “submitform(’#{album.imageurlmedium}’, #{idx})” %></td>
<td><%= imagetag album.imageurllarge, :onclick => “submitform(’#{album.imageurllarge}’, #{idx})” %></td>
</tr>
<% idx += 1
end %>
</table>
<% else %>
Done
<% end %>
<% end %>

<% formtag ‘/album/next’ do %>
<%= submit
tag ‘Next’ %>
<% end %>

1 comment so far ↓

#1 anonymous on 07.09.08 at 5:20 pm

this is cool. thanks for the idea.

Leave a Comment