Thursday, 5 January 2012

Organize your photos with a script

I have a lot of photographs and they where distributed over several external disks and computers, so I needed a way to organize them, I've searched the web and found a script that used the exif data to organize the images into folders by year, month, day. So I've picked that up and modified a bit to better fit my neads, I ended up with the script that you can check after the break.
I also use this script to move the photos from my camera to my PC.

Note: I don't remember the link from where I got the original script but I will update this post as soon as I find it. The original script can be found here:

This script organizes the following file types:

  • jpg/jpeg
  • nef
  • png
  • bmp
  • avi
  • mov
  • mpg
  • mp4
It recursively moves the files from the source dir (first argument) into a structure of folders on the destiny dir (second argument), it looks for duplicated files and in case of finding two different files with the same name it keeps the bigger file.
In the end it cleans the source directory by removing the folders that where left empty.

And here you have the script:

# Script to sort Photos and movies from digital cameras into folders by date
# When possible it uses the exif information on the photo to get the original date
# otherwise it uses the last modification date
toUpper() {
    echo $1 | tr  "[:lower:]" "[:upper:]"
toLower() {
    echo $1 | tr "[:upper:]" "[:lower:]"
    echo $1 | sed 's/ /\\ /g'
if [ "$#" != "2" ]; then
    echo "Usage: $0 <source> <destination>"
    exit 1
which stat > /dev/null
# make sure stat command is installed
if [ $? -eq 1 ]
    echo "stat command not found!"
    exit 2
which identify > /dev/null
# make sure identify command is installed
if [ $? -eq 1 ]
    echo "identify command not found!"
    exit 3
# using a tmp file you can have spaces in the file path
find "$cfgSource" -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.nef" -o -iname "*.png" -o -iname "*.bmp" -o -iname "*.avi" -o -iname "*.mov" -o -iname "*.mpg" -o -iname "*.mp4" > images.tmp
#for f in $(find "$cfgSource" -iname "*.jpg" -o -iname "*.nef")
cat ./images.tmp | while read f;
        #f=$(escapeSpaces "${f}")
        # Make sure the file we've been given by find actually exists.
        if [ -f "${f}" ]; then
                echo " "
                FILETYPE=$(toUpper ${f#*.})
                if [ "$FILETYPE" = "JPG" -o "$FILETYPE" = "NEF" ]; then
                    timestamp="$(identify -format '%[exif:DateTimeOriginal]' ${f})"
                #If identify fails to read the date from exif
                # or file is not an image, use stat to get last modification date
                if [ "${timestamp}" = "" ]; then
                    timestamp=$(stat -c %y "$f")
                # Looks like there are three possible timestamp formats:
                #       2008-05-05T14:46:47.16+01:00
                #       2009:02:28 12:57:50
                #       2011-05-25 19:24:26.000000000 +0100
                # Thankfully, cut will handle all of these formats.
                y=$(echo $timestamp | cut -c 1-4)
                m=$(echo $timestamp | cut -c 6-7)
                d=$(echo $timestamp | cut -c 9-10)
                destFile=$cfgDestRoot/$y/$m/$d/$(basename "${f}")
                # If the directory doesn't exist recursively create it.
                if [ ! -d "$cfgDestRoot/$y/$m/$d" ]; then
                        mkdir -p "$cfgDestRoot/$y/$m/$d"
                # Move the file.
                if [ -f "${destFile}" ]; then
                        # Existing file found.
                        echo "Existing file found: ${destFile}"
                        echo "Source: ${f}"
                        # Is it the same file? If so, delete the file we're processing.
                        md5src=$(md5sum "${f}")
                        md5src=${md5src% *}
                        md5dst=$(md5sum "${destFile}")
                        md5dst=${md5dst% *}
                        if [ $md5src = $md5dst ]; then
                                echo "Duplicate found, discarding identical file"
                                rm "$f"
                                # Is this file larger than the existing one?
                                sizeSrc=$(stat -c%s "$f")
                                sizeDst=$(stat -c%s "$destFile")
                                echo "Duplicate Found, keeping the larger file."
                                if [ $sizeSrc -gt $sizeDst ]; then
                                        mv "$f" "$destFile"
                                        rm "$f"
                        mv "$f" "$destFile"
                        echo "Moved $f to $destFile"
            echo "${f}"
            echo "File not found!"
        echo "== =="
#Now delete empty folders from source
find "$cfgSource" -iname "*.DS_Store" -exec rm -v {} +
find "$cfgSource" -iname "*.thm" -exec rm -v {} +
find "$cfgSource" -iname "*.IND" -exec rm -v {} +
find "$cfgSource" -iname "Thumbs.db" -exec rm -v {} +
find "$cfgSource" -type d -empty -delete
#find "$cfgSource" -depth -type d -empty -exec rmdir -v {} +
#Delete temporary file
rm images.tmp

Possibly Related Posts

1 comment:

  1. Thank you so much for sharing. Looks like it works great. Running on Windows with cygwin, after creating the script with a file editor, I had to remember to run dos2unix so the newlines didn't print an error message.