Superminigallery: a gallery with ruby, rmagick and builder

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).

Superminigallery thumbnail

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

  1. Create a folder in your computer. For example: holidays.
  2. Then you copy there the pictures you want to show to the world.
  3. Open a terminal and cd to that directory. E.g.
    cd ~/Desktop/holidays
  4. Execute the script! E.g.
    ruby ~/code/superminigallery.rb
  5. 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. ruby 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. ruby 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. ruby 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: ruby 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: ruby begin FileUtils.mkdir output_path rescue end We need to create a Magick::Draw object for watermarking the images, and define its parameters: ruby 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. ruby Dir['*.jpg','*.JPG'].sort.each do |f| Read each file into a Magick::Image object: ruby img = Magick::Image.read(f).first And for each version... ruby 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: ruby 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. ruby 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 } } end Resizing the image is as simple as ruby 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 ruby if(k=='big') draw.annotate(version, 0, 0, 0, 0, "(c) soledadpenades.com") end You can replace my (c) soledadpenades.com with your text, of course! And for writing the resulting image to disk: ruby version.write output_img_path Very 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 ruby GC.start as 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: ruby File.open(File.join(output_path, 'index.html'), 'w+') do |file| file.puts xhtml end Here'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 :-)