#!/bin/bash set -Eeu -o pipefail # Imports photos from a given location(s) to PHOTO_DIR, organized by calendar # year and with a filename matching the date the photo/video was taken. # # The date is determined by the following, in order of precedence: # 1. exif data from the image/video # 2. regex matching for a date-like string inside the filename # 3. the modification time of the souce file DATE_FORMAT='%Y-%m-%d-%H%M%S' DRY_RUN=0 usage() { echo 'usage: import.sh -cp|-mv PHOTO_DIR PATH...' 1>&2 exit 1 } if (( $# < 3 )); then usage fi if [ "$1" == -cp ]; then MOVE_COMMAND='cp --preserve=timestamps'; shift elif [ "$1" == -mv ]; then MOVE_COMMAND=mv; shift else usage fi PHOTO_DIR=$1; shift date_from_exif() { local date=$(exiftool -ee -quiet -tab -dateformat "$DATE_FORMAT" -json -DateTimeOriginal "$1" | jq --raw-output '.[].DateTimeOriginal.val') if [ "$date" == 'null' ]; then date=$(exiftool -ee -quiet -tab -dateformat "$DATE_FORMAT" -json -MediaCreateDate "$1" | jq --raw-output '.[].MediaCreateDate.val') fi if [[ "$date" =~ 20[0-9]{2}-[0-1][0-9]-[0-3][0-9]-[0-2][0-9][0-5][0-9][0-5][0-9] ]]; then echo "$date" fi } date_from_filename() { if [[ "$1" =~ (20[0-9]{2})[_-]([0-1][0-9])[_-]([0-3][0-9])[_-]([0-2][0-9])[_-]([0-5][0-9])[_-]([0-5][0-9]) ]]; then echo "${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}-${BASH_REMATCH[4]}${BASH_REMATCH[5]}${BASH_REMATCH[6]}" elif [[ "$1" =~ (20[0-9]{2})[_-]([0-1][0-9])[_-]([0-3][0-9])[_-]([0-2][0-9][0-5][0-9][0-5][0-9]) ]]; then echo "${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}-${BASH_REMATCH[4]}" elif [[ "$1" =~ (20[0-9]{2})([0-1][0-9])([0-9]{2})[_-]([0-2][0-9][0-5][0-9][0-5][0-9]) ]]; then echo "${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}-${BASH_REMATCH[4]}" elif [[ "$1" =~ (20[0-9]{2})([0-1][0-9])([0-9]{2})([0-2][0-9][0-5][0-9][0-5][0-9]) ]]; then echo "${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}-${BASH_REMATCH[4]}" elif [[ "$1" =~ (20[0-9]{2})[_-]([0-1][0-9])[_-]([0-3][0-9]) ]]; then echo "${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}-000000" elif [[ "$1" =~ (20[0-9]{2})([0-1][0-9])([0-3][0-9]) ]]; then echo "${BASH_REMATCH[1]}-${BASH_REMATCH[2]}-${BASH_REMATCH[3]}-000000" fi } date_from_mtime() { date -r "$1" "+$DATE_FORMAT" } # Generates a "safe" destination filename. If "foo.jpg" already exists, then this function # will return "foo_2.jpg" (or "foo_3.jpg" if "foo_2.jpg" exists...etc) safe_filename() { if (( $# > 1 )); then local src=$1 dst=$2 if [ -d "$dst" ]; then dst="${dst}/$(basename "$src")" fi else local dst=$1 fi local ext=${dst##*.} local odst=$dst local i=2 while [ -f "$dst" ]; do dst="${odst%.*}_${i}.${ext}" ((i++)) done echo "$dst" } safe_mv() { local src=$1 dst=$2 dst=$(safe_filename "$src" "$dst") if ((DRY_RUN)); then echo "[dry-run] $MOVE_COMMAND $src -> $dst" else $MOVE_COMMAND --no-clobber --verbose "$src" "$dst" fi } while IFS='' read -r -d '' file; do date=$(date_from_exif "$file") if [ -z "$date" ]; then date=$(date_from_filename "$file") fi if [ -z "$date" ]; then date=$(date_from_mtime "$file") fi year=${date:0:4} ext=${file##*.} ext=${ext,,} target="${PHOTO_DIR}/${year}/${date}.${ext}" mkdir -p "${PHOTO_DIR}/${year}" safe_mv "$file" "$target" done < <(find "$@" -type f \( -iname '*.jpg' -o -iname '*.png' -o -iname '*.mp4' \) -print0)