Imagine you have been in a very nice place for holidays. You took a lot of pictures and want to show them to your family and friends, but you don’t feel like using services like flickr or programs like iPhoto. You just want to put them in your own server and give the url to your friends.
What can you do? Well, you could do like me and create a little script to generate an HTML file, with thumbnails and even watermarked images (just in case some creepy individual decides to use your stuff without asking first).

Requirements
This script requires a couple of gems to be installed: RMagick and builder (but if you’ve done some stuff with Rails you might already have them). RMagick is used for dealing with the images and builder is used for generating the XHTML. This is because I didn’t want to write any html by hand, with their less than and greater than signs, attributes, etc.
Using it
- Create a folder in your computer. For example: holidays.
- Then you copy there the pictures you want to show to the world.
- Open a terminal and cd to that directory. E.g.
cd ~/Desktop/holidays
- Execute the script! E.g.
ruby ~/code/superminigallery.rb
- Wait until it finishes
When it finishes you’ll find there’s an output folder in the holidays folder. That’s where the index.html file, as well as all the thumbnails and watermarked images are. Simply upload the contents of this folder to your host and let everybody know about it!
Ok, but show me the code
The first lines act like a configuration area. You can change the output folder name, so that it is called superoutput, gallery, whatever you like (as long as it is a valid path name).
You may change the sizes of the generated pictures; these sizes are defined in the versions variable. Each pair means [width, height]. For example, the thumbnails are 300 pixels wide and 150 pixels high.
output_path = 'output'
versions = {
'thumbnail' => [300,150],
'big' => [1024,768]
}You can also configure which EXIF tags need to be retrieved. Since their names are too obscure for non-technical savvy people I decided to create this hash for storing the key (Exif tag) and the nice name to show with the value. So instead of showing DateTimeOriginal, it will simply output Taken.
exif_fields = {
'Taken' => 'DateTimeOriginal',
'Camera' => 'Model',
'Exposure' => 'ExposureTime',
'Shutter Speed' => 'ShutterSpeedValue'
}There are way more tags you could show, but they can be confusing for normal people and only entertain geeks, so it’s better to keep them down to a minimum.
Declare the builder object, and initialize it with the XHTML header.
x = Builder::XmlMarkup.new(:target=>xhtml, :indent=>1)
x.instruct!
x.declare! :DOCTYPE, :html, :PUBLIC, "-//W3C//DTD XHTML 1.0 Strict//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"
x.html( "xmlns" => "http://www.w3.org/1999/xhtml" ) {Now it would be amazing to have some styling in the page so that it doesn’t look so ugly. We can put an style tag inside the head, and use the text! method for adding literal text to the builder object:
x.style( "type"=>"text/css" ) { x.text! "
body{
font-family:georgia,serif
}
h1,h2 {
margin-top: 0;
}
...
" }(the … means there’s more code but I have reduced it for clarity purposes)
Now, we need to create the output directory. I haven’t bothered with outputting error messages if the directory already exists or anything. It will always try to create it:
begin
FileUtils.mkdir output_path
rescue
endWe need to create a Magick::Draw object for watermarking the images, and define its parameters:
draw = Magick::Draw.new
draw.gravity = Magick::CenterGravity
draw.pointsize = 64
draw.font_family = "Helvetica"
draw.font_weight = Magick::BoldWeight
draw.stroke = 'none'
draw.fill = "#ffffff99"Basically we are saying: use Helvetica bold 64pt, painting it with white (ffffff) and some transparency (99 for alpha channel)
. If you don’t have Helvetica installed in your system, replace it with your favourite font.
(But since 2007 is Helvetica’s 50th anniversary, you should do everything possible to use Helvetica!)
Now we open the current directory (where the script was executed) and find all files with jpg and JPG extensions, and sort them. That’s because sometimes the images don’t get listed in alphabetical order, and us humans like to see things in sequential order. Specially because they usually are numbered incrementally, and older numbers mean older images, so IMG001 should appear before IMG100.
Dir['*.jpg','*.JPG'].sort.each do |f|Read each file into a Magick::Image object:
img = Magick::Image.read(f).firstAnd for each version…
versions.each do |k,v|… create the version filename by appending the version name to the filename, like big_IMG_1234.jpg, and the output filename, by prepending the output path to the version filename:
version_file = k + '_' + f
output_img_path = File.join(output_path, version_file)If the version is ‘thumbnail’, we’ll add the image metadata to the builder object. Note how you don’t need to open or close tags, but just include things in blocks or parenthesis to get the mark up done.
if(k=='thumbnail')
x.div('class'=>'picture') {
x.h2(f)
x.a('href'=> version_file.sub('thumbnail_', 'big_')) {
x.img('src'=>version_file)
}
x.dl {
x.dt('Dimensions')
x.dd(img.columns.to_s + ' x ' + img.rows.to_s)
exif_fields.each do |title, field|
key = "Exif:#{field}"
if img[key]!=nil
x.dt(title)
x.dd(img[key])
end
end
}
}
endResizing the image is as simple as
version = img.crop_resized(v[0], v[1])crop_resized returns another Image object which we store in the version variable.
Now, if we are dealing with the ‘big’ version, we’ll add the watermark that we prepared at the beginning. That is done with
if(k=='big')
draw.annotate(version, 0, 0, 0, 0, "(c) soledadpenades.com")
endYou can replace my (c) soledadpenades.com with your text, of course!
And for writing the resulting image to disk:
version.write output_img_pathVery very important: do not forget to call the Garbage Collector. For some reason which I still haven’t been able to elucidate, the RMagick gem leaks memory furiously. So if you forget to do a
GC.startas I did with the first version of the script, your computer will mostly hung if you make it generate a lot of thumbnails. If you look at the current processes with top or a similar tool, you’ll find a ruby process eating more and more memory with each picture it processes.
And finally, we just need to output the generated XHTML to index.html:
File.open(File.join(output_path, 'index.html'), 'w+') do |file|
file.puts xhtml
endHere’s the result and here’s the source code. With only 120 lines of code (excluding the license text :D), it’s way easy to modify to suit your tastes.
Don’t tell anyone but…
I must confess I got the inspiration for this from herotyc’s jGallery (edit: link pointed to a dead site so I removed it). But he used a bash script and I thought there should be a way of doing the same with ruby :-)

herotyc
Hey, great stuff! Good job.
I’ll take away your watermark idea and put into my jGallery, hehe. I did my script for the same way you did yours(learning of course), I was looking for a very easy way to have photos in my webpage. I tried some php galleries like Coppermine, but they were so slow, and uploading en entire gallery was painful.
Just two questions:
How do you deal with rotated photos? Photos taken with the camera rotated 90º
Were you forced to use the MIT licence? If not, why did you choose it?
The photos are beautiful, ‘Menorca’ I guess.
Cheers!
sole
Yep, adding pictures to those galleries, one by one, is horribly slow!
About the questions:
1) My camera has already rotated the pictures. Since I’m using crop_resized, RMagick will take care of adjusting the width or height as necessary before cropping. I think some older versions of RMagick do not have that method, so you have to manually add a piece of code with something for detecting if you need to resize vertically or horizontally.
2) I chose the MIT because
- it’s short and easy to read
- you can learn with the code and use it if you wish
- if you use it in more products, more people can learn from your products because it “propagates”.
And yes the pics are from Menorca :-) (“Minorca” as they say here hehe)
David Bock
I didn’t know RMagick could read EXIF info so easily! Nice! Thanks for that snippet.
sole
You’re welcome! Hope it’s useful…
walter
Thank you for a nice script. I’m trying it out but it keeps duplicating the same image twice on one row, then moving on to the next image. The output shows each image being processed four times instead of twice. Any thoughts?
I’m also looking up RMagick on how to move the watermark around.
Cheers
sole
Hi Walter!
I don’t really know why could it fail. What system are you using?
I am thinking that maybe in Windows the *.jpg and *.JPG files could return the same results twice but I haven’t tested it in Windows.
Other than that once it enters the loop it just tries to get the first image of each file (it seems that JPG files can store more than one image). So I can’t think of any other error source…
Have you tried changing the gravity parameter for moving the text to a different place?
Let me know if the windows thing works :-)
walter
Yes after fiddling around with it for a while it was indeed the Dir['*JPG','*.jpg'] line causing the error in Windows 2000 but you beat me to it ;)
So then just a note here for any other newb. What was happening was that the Dir was reading the jpg file then doing it again for JPG for the same file name.
in the script rewritting the line to just one Dir['*.jpg'].sort.each do |f| did the job.
Thanks for the gravity parameter note! I’ll take a look at that.
Cheers
Walter
walter
i’m going over rmagick now, what’s happening is that the thumbnail is okay but the larger image get’s cropped and expanded to the version hash parameter. Thus working correctly but I’ll need another method. Makes sense for the thumbnail, but any way to leave the big image as-is rather than specifying a size in version hash?
thanks
walter
walter
If it helps, I should point out that my ‘big’ images are already a uniform 600×400, but even when I specify 1024×768 in the version hash, the image still gets cropped. Oddly the thumbnail is more representative of the total image.
I’ll try moving around the line version = img.crop_resized(v[0], v[1]) and I’ll let you know if it works
walter
sole
Maybe that’s happening because of the aspect ratio which is different. 1024×768 is roughly 4:3 while 600×400 is 3:2. Maybe that’s why rmagick is trying to adjust things and being too intelligent.
Take a look at the crop_resized docs and see if you can play a bit with Gravity again.
Other solution is to work out your own method, combining resize and crop. I did it once for a host which had an old rmagick gem installation and hadn’t the crop_resized method in it. It’s not too complicated, you just need to know very well what you want to do.
Good luck :-)
walter
So…I went over the Gravity and image sizes again. It wasn’t the aspect ratio but that was the key to fixing it. After playing around with it, I started over, going over each variable setting. It turns out for the big images to be viewed properly I had to set it to 400×600 not 600×400. Yeesh.
Yep, I fried a good brain cell, but I learned a lot. Ah well, life as a nuby. Thanks for your help! Great script.
Cheers
Walter
sole
Glad to hear you finally found your way around it :-)
I guess frying cells is the only way for learning for real. Once you do that you never forget what made you fry the cell.
Thanks for using it too!
Manuel
Soledad,
Gracias por compartir este script.
Hay alguna manera de leer captions con RMagick?
He buscado en internet y no encuentro la manera de hacerlo.
Se me ocurre que seria excelente, en lugar de colocar los datos de la camara, el tamaño y esas cosas, colocar el caption o comment de la foto seria genial.
Saludos y gracias nuevamente
sole
Pero los captions donde estan guardados? En los jpg? O en la base de datos del programa que sea?
Manuel
Hola Soledad,
tengo entendido que el caption esta en los jpg.
pero no estoy seguro. Al abrirlos en gimp los reconoce en um campo llamado comment y en photoshop los lee en el campo caption.
sole
Huy, pues no tenia ni idea. Que informacion hay ahi? Lo guarda la camara? O es algo que guardan los editores?
Manuel
Es algo que guardan los editores, no la camara, en la documentacion de RMagick esta el atributo caption, pero hasta donde entiendo – y no entiendo mucho, para ser franco – uno lo puede modificar, pero no veo la manera de leerlo… Pero de todas formas millones de gracias por compartir tu creacion con el mundo :-)
madgoblin
Ummm tiene pinta de estar en los datos de EXIF… O al menos hay uno que se llama “user comments”
http://www.robogeo.com/home/exifiptcfields.asp
http://www.exif.org/
sole
@Manuel: eso ya me cuadra mas… Y no se, habria que probarlo.
@madgoblin: ya estamos todos!