Skip to content

Instantly share code, notes, and snippets.

@jvhaarst
Last active December 12, 2022 22:02
Show Gist options
  • Save jvhaarst/2343281 to your computer and use it in GitHub Desktop.
Save jvhaarst/2343281 to your computer and use it in GitHub Desktop.
Bash script to move images, based on exif data and file timestamp
#!/bin/bash
# Reads EXIF creation date from all .JPG files in the
# current directory and moves them carefully under
#
# $BASEDIR/YYYY/YYYY-MM/YYYY-MM-DD/
#
# ...where 'carefully' means that it does not overwrite
# differing files if they already exist and will not delete
# the original file if copying fails for some reason.
#
# It DOES overwrite identical files in the destination directory
# with the ones in current, however.
#
# This script was originally written and put into
# Public Domain by Jarno Elonen <elonen@iki.fi> in June 2003.
# Feel free to do whatever you like with it.
# Defaults
TOOLS=(exiftool jq) # Also change settings below if changing this, the output should be in the format YYYY:MM:DD
DEFAULTDIR='/Users/jvhaarst/Pictures/van camera/van camera'
MAXDEPTH=-maxdepth 1
#MAXDEPTH=''
# activate debugging from here
#set -o xtrace
#set -o verbose
# Improve error handling
set -o errexit
set -o pipefail
# Check whether needed programs are installed
for TOOL in ${TOOLS[*]}
do
hash $TOOL 2>/dev/null || { echo >&2 "I require $TOOL but it's not installed. Aborting."; exit 1; }
done
# Enable handling of filenames with spaces:
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
# Use BASEDIR from commandline, or default if none given
BASEDIR=${1:-$DEFAULTDIR}
for FILE in $(find $(pwd -P) $MAXDEPTH -not -wholename "*._*" -iname "*.JPG" -or -iname "*.JPEG" -or -iname "*.CRW" -or -iname "*.THM" -or -iname "*.RW2" -or -iname '*.ARW' -or -iname "*AVI" -or -iname "*MOV" -or -iname "*MP4" -or -iname "*MTS" -or -iname "*PNG")
do
INPUT=${FILE}
DATE=$(exiftool -quiet -tab -dateformat "%Y:%m:%d" -json -DateTimeOriginal "${INPUT}" | jq --raw-output '.[].DateTimeOriginal')
if [ "$DATE" == "null" ] # If exif extraction with DateTimeOriginal failed
then
DATE=$(exiftool -quiet -tab -dateformat "%Y:%m:%d" -json -MediaCreateDate "${INPUT}" | jq --raw-output '.[].MediaCreateDate')
fi
if [ -z "$DATE" ] || [ "$DATE" == "null" ] # If exif extraction failed
then
DATE=$(stat -f "%Sm" -t %F "${INPUT}" | awk '{print $1}'| sed 's/-/:/g')
fi
if [ ! -z "$DATE" ]; # Doublecheck
then
YEAR=$(echo $DATE | sed -E "s/([0-9]*):([0-9]*):([0-9]*)/\\1/")
MONTH=$(echo $DATE | sed -E "s/([0-9]*):([0-9]*):([0-9]*)/\\2/")
DAY=$(echo $DATE | sed -E "s/([0-9]*):([0-9]*):([0-9]*)/\\3/")
if [ "$YEAR" -gt 0 ] & [ "$MONTH" -gt 0 ] & [ "$DAY" -gt 0 ]
then
OUTPUT_DIRECTORY=${BASEDIR}/${YEAR}_${MONTH}_${DAY}
mkdir -pv ${OUTPUT_DIRECTORY}
OUTPUT=${OUTPUT_DIRECTORY}/$(basename ${INPUT})
if [ -e "$OUTPUT" ] && ! cmp -s "$INPUT" "$OUTPUT"
then
echo "WARNING: '$OUTPUT' exists already and is different from '$INPUT'."
else
echo "Moving '$INPUT' to $OUTPUT"
rsync -ah --progress "$INPUT" "$OUTPUT"
if ! cmp -s "$INPUT" "$OUTPUT"
then
echo "WARNING: copying failed somehow, will not delete original '$INPUT'"
else
rm -f "$INPUT"
fi
fi
else
echo "WARNING: '$INPUT' doesn't contain date."
fi
else
echo "WARNING: '$INPUT' doesn't contain date."
fi
done
# restore $IFS
IFS=$SAVEIFS
@ldong
Copy link

ldong commented Dec 27, 2015

Nice, thank you. This script uses exiftool as dependency, so brew install exiftool for mac people.

@sko-lv
Copy link

sko-lv commented Jun 29, 2016

Is 69 line correct or should be:
mkdir -pv ${OUTPUT_DIRECTORY} ?

@jvhaarst
Copy link
Author

Yeah, fixed now.

@dforrer
Copy link

dforrer commented Mar 5, 2017

If $INPUT-Path equals $OUTPUT-Path you're just deleting the files.
Meaning if you run this script twice you delete all files which are already sorted.

@rampol
Copy link

rampol commented Jun 25, 2017

Thanks for this script! I needed to modify lines 48 and 51 adding .val to make it work. Not sure why, probably my exiftool version (brew in osx el capitan)
DATE=$(exiftool -quiet -tab -dateformat "%Y:%m:%d" -json -DateTimeOriginal "${INPUT}" | jq --raw-output '.[].DateTimeOriginal.val')

@ncherro
Copy link

ncherro commented Dec 6, 2017

Thanks for this 👍

@atescula
Copy link

Thanks for script ! How to also rename files based on some EXIF parameters like Nikon ShutterCount, Canon filenumber, etc. Thanks a lot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment