diff options
Diffstat (limited to 'pictures')
| -rwxr-xr-x | pictures/import.sh | 125 | 
1 files changed, 125 insertions, 0 deletions
| diff --git a/pictures/import.sh b/pictures/import.sh new file mode 100755 index 0000000..3ef850f --- /dev/null +++ b/pictures/import.sh @@ -0,0 +1,125 @@ +#!/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) | 
