aboutsummaryrefslogtreecommitdiff
path: root/pictures/import.sh
blob: 3ef850fe873c3e55d32f3e295d2de948dc37effb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
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)