aboutsummaryrefslogtreecommitdiff
path: root/pictures/import.sh
diff options
context:
space:
mode:
Diffstat (limited to 'pictures/import.sh')
-rwxr-xr-xpictures/import.sh125
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)