Batch Image Resizing with Ruby and ImageMagick

by Chadwick Wood
March 10th, 2009

A couple of days ago, I was working on an upcoming website that displays a hundred or so images of an artist's paintings. Each painting on the website will be represented in two sizes: a smaller view, and a "full-size" view. Also, for each painting there is associated information that needs to be displayed (title, size, medium, etc.). I certainly didn't want to do all that work by hand! So I wrote a script...

The Materials at Hand

Here's what I had to work with:

  • High-resolution tif files of the paintings, named like "1 pntng_name.tif", "2 pretty_landscape.tif", etc. The files were in a collection of subdirectories, to sort them by year.
  • An Excel file with all of the information about each painting, including a number for each painting that corresponded to the number at the beginning of the painting's filename.

I could have run each of the images through one of my Photoshop scripts to do the resizing, or I could have used PHP with ImageMagick to do it, but I've been getting further into using Ruby these days (and loving it), so I decided to see how it easy it would be to write a Ruby script that would use ImageMagick to generate the resized images I need, and also grab the information out of the Excel file for each painting, to generate code for the website the images were going to be used on.

First off, maybe there's a Ruby library out there to read Excel files, but I didn't want to mess with that. Simple solution: I opened up the Excel file, did Select All, then copied and pasted into my text editor. The content was pasted as tab-delimited lines, one line per row. Perfect for scripting (I was sure that there weren't any tabs in my actual content itself).

The Script

So, here's the Ruby script. I'll comment the code to help you along, and give a run-down at the end:

require "find"   # to use command-line "find" to go through subdirectories for files
require "open3"   # to run ImageMagick's convert from our script
require "ftools"   # file-handling tools

# 'width x height' for our image export sizes.
# "strip" is the smaller size, which I always want to be resized to 415 pixels high

@strip_dimension = '1000000x415'
# the full-size image, which should be fit in a 900- by 500-pixel bounding box
@full_dimension = '900x500'

# we will pass the directory to search for images in as an argument
@in_dir = ARGV[0]

# the text file that has the paintings info in it (in the same dir as the script itself)
@info_file = 'paintings_info.txt'
# the output file for our website code
@html_file = 'paintings.html'

# open info file and read it into a hash
@file = File.open(@info_file)
@paintings_info = {}
while line = @file.gets
  @arr = line.split("\t")
  # the key to match the info to the filename is the first item in the info (@arr[0])
  @paintings_info[@arr[0]] = @arr
end
@file.close

# open the output file for writing
@file = File.open(@html_file,'w')

# the format of the paintings_info file (with array index)
# 0-no. 1-year  2-name  3-medium  4-org.size  5-frame.size
# 6-frame$  7-location/owner  8-price date.sold

# do a find on the input directory
Find.find(@in_dir) do |path|
  # script will go through everything in the directory and subdirectories.
  # if the script finds a .tif file...
  if !FileTest.directory?(path) && File.fnmatch?('*.tif',path)
    # output the path for debugging
    puts path

    # look at file name to find hash key (everything before the first space in the file name)
    basename = File.basename(path,'.tif')
    key = basename.split(' ')[0]
    info = @paintings_info[key]
    # some string processing to make a nice output filename for the images
    edited_name = basename.gsub(/[^a-zA-Z 0-9]*/,'').gsub(/ +/,'_').gsub(/_$/,'').downcase
    strip_name = edited_name+'.strip.jpg'
    full_name = edited_name+'.jpg'

    # the "website code" I needed to output was actually a call to a custom PHP function.
    # here I'm passing info from the info file into that PHP function call
    @file.puts "stripImage('/images/paintings/#{strip_name}','/images/paintings/#{full_name}',\n\t"+
        "'#{info[2]}
#{info[4]} • "+info[3].capitalize+"');\n\n" # the first ImageMagick call I need, to make the "strip" size of the image @cmd = "convert \"#{path}\" -resize #{@strip_dimension} -quality 90 'edited/#{edited_name}.strip.jpg'" stdin, stdout, stderr = Open3.popen3(@cmd) # output any debug stuff from the call to 'convert' puts l while l = stdout.gets puts l while l = stderr.gets # same thing, for the other size image I need @cmd = "convert \"#{path}\" -resize #{@full_dimension} -quality 90 'edited/#{edited_name}.jpg'" stdin, stdout, stderr = Open3.popen3(@cmd) puts l while l = stdout.gets puts l while l = stderr.gets end end

... ok, that's it! So basically, the script takes a directory as an argument, and goes through that directory and all of its subdirectories, running convert on each .tif it finds, and also writing a snippet of PHP code to an output file, referencing the generated files and the painting information associated with the files.

And of course, if any of this is leaving you scratching your head, just leave a comment and we'll get it all sorted out!