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
My name is Evan Light and, yes, I am a nerd. I'm also a professional software developer who, after spending one too many years contracting to the federal government, escaped into the far more enjoyable commercial world. Having spent several years using C and even more using Java (the latter very nearly caused me to give up programming entirely), I consider myself fortunate to have discovered Ruby and to use it as part of my daily work.