Great_Wave_off_Kanagawa2If you are like me, you take a gazillion pictures and they are all over the place.

Recently, I decided to the consolidate all my data from all my hard drives onto one massive NAS Drive (Synology 412+).

Synology DOES offer a tool that let’s you sort all your images using “Smart Folders” or something, but that didn’t appeal to me.

I wanted a ruby script to that I could drop into a sea of pictures and I would get all the images sorted by month and year into named folders.

I am sure there are tools that do this, but I wanted to write my own.

I’m stubborn that way.

Step 1: Install exifr reader gem.

One of the great things about ruby is that there usually is a “gem for that”.


Heinrichs-iMac:~ hbeck$ sudo gem install exifr

WARNING: Improper use of the sudo command could lead to data loss
or the deletion of important system files. Please double-check your
typing when using sudo. Type "man sudo" for more information.

To proceed, enter your password, or type Ctrl-C to abort.

Password: StevePontEsMuyMacho
Successfully installed exifr-1.1.3
1 gem installed
Installing ri documentation for exifr-1.1.3...
Installing RDoc documentation for exifr-1.1.3...
Heinrichs-iMac:~ hbeck$ 

Step 2: Write some code!

We are going to require

  • rubygems since we are using a gem.
  • exifr, to read the exif data.
  • date, to read the date.
  • fileutils, to move the files.

#! /usr/bin/env ruby

require 'rubygems'
require 'exifr'
require 'date'
require 'fileutils'


# I want to just drop this in a folder of images. This gets the working directory
wd = Dir.getwd

# files is an array of file name strings in the working folder.
files = Dir.entries("#{wd}")

#I'm going to go thru each file and read it's date.
files.each do |file|  
        # the meat of what I want to do will go here....
end

Step 3: There’s trouble right here in Directory City

One of the pain in the keister issues in MacOS is that hidden files called “.” or “..” will be picked up in the directory scan. But since they do not have exif data we need to sniff for them and NOT run the exifr method on them. Also, the ruby file will be in the directory so I need to exclude that as well.


if file.start_with?(".") || file.end_with?(".rb") 
    puts "(hand wave) this is not the file you are looking for..."
  else
       # the meat of what I want to do will go here.
  end 

Time to get some shooting data!


dateShot = EXIFR::JPEG.new("#{file}").date_time

returns a date that looks like this: Sat Jun 22 22:09:22 -0400 2013

I know it’s a date class since I double checked thusly:


puts dateShot.class

and got ‘Date’.
If ever you are unsure what a variable actually is ‘variable.class‘ is your best friend.

Step 4: There’s MORE trouble right here in Directory City

The idea is to make directories with Month_Year, but what if there is ALREADY a directory in the folder? What if I dump a sea of images into a directory, run my magic ruby script, and then find MORE images after I have sorted all the images into directories? I can’t run the exifr method on a directory, so I need to sniff for that as well.


if file.start_with?(".") || file.end_with?(".rb")
    puts "(hand wave) this is not the file you are looking for..."
  elsif file.include? "."
     # I know that all my jpeg files will have a DOT in the name and my directories do NOT have dots in their name.
     # So I will put the methods here....
  else
    puts "it's a directory"
  end 

Step 4: How about a date, baby?

I want the date formatted like this: Mon_Year. The Date class offers a method to format our date pretty much any way we like.


     dateShot = EXIFR::JPEG.new("#{file}").date_time
     folderName = dateShot.strftime("%b_%Y")

This will return Jun_2013.

I want to make the directory, but of course, if the directory ALREADY EXISTS, I want to leave it alone.


 if File.exists?("#{folderName}") # I thought Dir.exists would work, but it doesn't. If you know WHY, please let me know.
          puts "the directory exists"
     else
          Dir.mkdir("#{folderName}")
          puts "make the directory"
     end

The directory has been created, let’s move the files in there…


FileUtils.move file, folderName

Yes, it really is that easy.

Step 5: It’s ALWAYS SOMETHING ALICE!! 

Ok, you are thrilled that everything is working fine and images are getting sorted out but then exifr finds that SOME images have no shooting data.

It happens. Maybe you downloaded the image from a website that stripped out the exif data.

When this occurs, the exifr method returns nil.

We need to catch that, so let’s make a NO_Date directory and drop the undated images in there.


   if dateShot.nil?
         folderName = 'No_Date'
         puts "there is no date"
 else
         folderName = dateShot.strftime("%b_%Y")
 end

Step 6: Let’s test it! 

I suggest you start with a small folder of images, drop the ruby file in there, and see your results. I wrote this on my MacOS, but it should work on any OS. If you have Windows, it goes without saying that you need to install Ruby to make this work (but I said it anyway).

Step 7: What happens if I have a NEF/RAW image? 

Good question! I’ll investigate this another time. I have a lot of images to clean up!

Here is the final script. Enjoy.


	#! /usr/bin/env ruby

	require 'rubygems'
	require 'exifr'
	require 'date'
	require 'fileutils'

	# I want to just drop this in a folder of images. 
	wd = Dir.getwd

	# files is a list of a files in the working folder
	files = Dir.entries("#{wd}")

	#I'm going to go thru each file and read it's date.
	files.each do |file|  

	  if file.start_with?(".") || file.end_with?(".rb") 
		#ignore ruby scrip and hidden files.
	    puts "(hand wave) this is not the file you are looking for..." 
	  elsif file.include? "."
	     dateShot = EXIFR::JPEG.new("#{file}").date_time

	     if dateShot.nil?
	       folderName = 'No_Date'
	       puts "there is no date"
	     else
	       folderName = dateShot.strftime("%b_%Y")
	     end

	     if File.exists?("#{folderName}") 
	     	puts "the directory exists"
	     else
	     	Dir.mkdir("#{folderName}")
	     end

	     FileUtils.move file, folderName

	  else
	    puts "it's a directory"
	  end

Final thoughts