10

What I want to do is reorganize files in my Camera Roll folder. Using the creation date I want to Put them inside folders according to Year/Month Format using their creation date.

In this answer, they explain how to make folders and organize them: https://stackoverflow.com/a/1314394/4980886

#!/bin/bash
find $PHOTO_DIR -regextype posix-extended -type d -regex '.*/[0-9]{4}/[0-9]{2}/[0-9]{2}$' |
while read dir; do
    newdir="$(echo $dir | sed 's@/\([0-9]\{4\}\)/\([0-9]\{2\}\)/\([0-9]\{2\}\)$@/\1-\2-\3@')"
    mv "$dir" "$newdir"
    rmdir "$(dirname $dir)"
    rmdir "$(dirname $(dirname $dir))"
done

But it doesn't address how to get the creation date, maybe I should get the metadata from EXIF data. How to do either?

Community
  • 1
  • 1
prab4th
  • 231
  • 1
  • 2
  • 11

4 Answers4

14

According to exiftool man page 'Renaming examples'

exiftool '-Directory<DateTimeOriginal' -d %Y/%m/%d "$dir"

and if only copying is required, there is an option:

exiftool -o . '-Directory<DateTimeOriginal' -d %Y/%m/%d "$dir"

has the same effect as above except files are copied instead of moved.

mivk
  • 13,452
  • 5
  • 76
  • 69
mdn
  • 363
  • 3
  • 10
  • 1
    Note that the directories are created in your current dir, not the directory given as argument to be processed. – mivk Mar 13 '19 at 16:24
  • Yeah, its weird, I tried to replace "-o ." with "-o " and it ignores and just creates the files in the current directory. So then I have to rsync them over after that. Still, great tool! – crowmagnumb Jun 01 '22 at 20:01
3

i have written a bash script to copy files straight off my iphone/ipad and copy them into folders on a target drive based on image creation date. i use a program called exiftool found at http://www.sno.phy.queensu.ca/~phil/exiftool/

for a given image, i extract the creation datetime into an array using

DATEBITS=( $(exiftool -CreateDate -FileModifyDate -DateTimeOriginal "$IMGFILE" | awk -F: '{ print $2 ":" $3 ":" $4 ":" $5 ":" $6 }' | sed 's/+[0-9]*//' | sort | grep -v 1970: | cut -d: -f1-6 | tr ':' ' ' | head -1) )

where $IMGFILE is the path to the image file. you can then get access to the year, month, day etc using

YR=${DATEBITS[0]}
MTH=${DATEBITS[1]}
DAY=${DATEBITS[2]}
HR=${DATEBITS[3]}
MIN=${DATEBITS[4]}
SEC=${DATEBITS[5]}

it is then trivial to create the directory you need to stash the image file in

bhu Boue vidya
  • 379
  • 6
  • 16
  • Still stuck? Here's the rest of it. Obviously modify the target according to wants. This will take all files in the current directory and sort them into `year/month/file` structure: `for file in *; do if [ ! -d "$file" ]; then DATEBITS=( $(exiftool -CreateDate -FileModifyDate -DateTimeOriginal "$file" | awk -F: '{ print $2 ":" $3 ":" $4 ":" $5 ":" $6 }' | sed 's/+[0-9]*//' | sort | grep -v 1970: | cut -d: -f1-6 | tr ':' ' ' | head -1) ); YR=${DATEBITS[0]}; MTH=${DATEBITS[1]}; DAY=${DATEBITS[2]}; HR=${DATEBITS[3]}; MIN=${DATEBITS[4]}; SEC=${DATEBITS[5]}; mkdir $YR/$MTH && mv $file $_; fi; done` – Barry Sep 17 '20 at 20:38
2

I've been working on sorting 14,000 photos from 1997 on. It's a bear because not all are digital cameras, especially the early ones. I used the 'CreateDate' tag in the image files.

I wrote the following BASH script to perform the sorting based on EXIF data, if possible. It looks for the EXIF 'CreateDate' tag first. If found, it uses it. If not, it looks for the 1st 8 characters of the file name to see if they are a VALID date (ANDROID, etc). If neither tests positive, it looks for the EXIF 'FileModifyDate' tag, which can be inaccurate. If the EXIF 'CreateDate' is found, the date prefix (YYYMMDD-) will be added to the file name (if it doesn't already exist) and then it will be moved into it's appropriate directory. If all three tests fail, the file is left alone for user intervention.

If you're lucky, and the camera supported it, and the photographer enabled it, the date may be imprinted on the image for you. I had to sort MANY photos this way, so be sure to look at the photos that are not processed and those that use the EXIF 'FileModifyDate' method of sorting.

Also, if you find you have some of those KODAK PCD picture files, you can use ImageMajick's "convert" like so:

convert $OrigFileName[$SUFX] -colorspace RGB "$(basename $OrigFileName .pcd).jpg"

However, ImageMajick doesn't always copy all the tags to the new image file but you can use EXIFTOOL to transfer the tags to your new image file like so:

exiftool -tagsFromFile $OrigFileName $(basename $OrigFileName .pcd).jpg

Code:

#! /bin/bash
# This script is used to sort photos. It uses the EXIFTOOL to
#  1st attempt to extract the photo's "CreateDate". If it is
#  invalid, then the file name is checked to see if it begins
#  with "YYYYMMDD", which is also checked for validity. If the
#  prior two checks fail, then the photo's "FileModifyDate" is
#  used but can be inaccurate.
# If a valid creation date is found and the file name is NOT
#  date-encoded, then the file is renamed with a "YYYYMMDD-"
#  prefix.
#=======================================================================
#   Revision index:
#   2019-0704:  KSV - Created and tested.
#=======================================================================

DEBUG=0     # Debugging
DRYRUN=0    # Do everything but move the files
NOTE=""     # Notes

# ANSI COLORS
CRE="$(echo -e '\r\033[K')"
RED="$(echo -e '\033[1;31m')"
GRN="$(echo -e '\033[1;32m')"
YEL="$(echo -e '\033[1;33m')"
BLU="$(echo -e '\033[1;34m')"
MAG="$(echo -e '\033[1;35m')"
CYN="$(echo -e '\033[1;36m')"
WHT="$(echo -e '\033[1;37m')"
NML="$(echo -e '\033[0;39m')"

#=======================================================================
# Functions
#=======================================================================
# Enter with: YEAR, MONTH and DAY
# Returns: 0=invalid date, 1=valid date
# EX: IsValidDate $YEAR $MONTH $DAY
IsValidDate() {
  #echo "Parm: Y=$1,${#1} M=$2,${#2} D=$3,${#3}" >/dev/stderr
  if ([ "$1" -ge "1950" ] && [ "$1" -le "2050" ]) || \
  ([ "$2" -ge "01" ] && [ "$2" -le "12" ]) || \
  ([ "$3" -ge "01" ] && [ "$3" -le "31" ]) ; then
    echo "1"    # valid date
  else
    echo "0"    # invalid date
  fi
}

# Dump debugging info
# EX: $(DumpDebug $FN $FILE $EXT $FN_WD $DATE $YEAR $MONTH $DAY "$NOTE")
DumpDebug() {
  #echo "1=${#FN}, 2=${#FILE}, 3=${#EXT}, 4=${#FN_WD}, 5=${#DATE}, 6=${#YEAR}, 7=${#MONTH}, 8=${#DAY}, 9=${#NOTE}" >/dev/stderr
  echo "================================"
  echo "FN        = $1"
  echo "FILE      = $2"
  echo "EXT       = $3"
  echo "FN_WD     = $4"
  echo "DATE      = $5"
  echo "YEAR      = $6"
  echo "MONTH     = $7"
  echo "DAY       = $8"
  echo "ValidDate = $(IsValidDate $6 $7 $8)"
  echo "NOTE      = $9"
  echo "================================"
}

#=======================================================================
# Script starts here
#=======================================================================
# Use exiftool to find video and photos
#exiftool -filename *.[JjGg][PpIi][GgFf] *.[Jj][Pp][Ee][Gg] *.[Mm][PpOo][Gg4Vv] 2>/dev/null | awk {'print $4'} | \
find . -maxdepth 1 -type f -iname "*.[JjGg][PpIi][GgFf]" -or \
-iname "*.[Jj][Pp][Ee][Gg]" -or \
-iname "*.[Mm][PpOo][Gg4Vv]" | \
while read FN ; do
  FN=$(basename $FN)                                # strip the leading "./"
  if [ -e $FN ] && [ ${#FN} != 0 ] ; then           # be sure the file exists!
    EXT=${FN##*.}                                   # extract the extension
    FILE=${FN%.*}                                   # extract the base file name

    # First attempt to see if there is a valid date prefix in the file name.
    YEAR=$(echo ${FN:0:4} | egrep -E ^[0-9]+$ )     # insure digits only
    MONTH=$(echo ${FN:4:2} | egrep -E ^[0-9]+$ )    # insure digits only
    DAY=$(echo ${FN:6:2} | egrep -E ^[0-9]+$ )      # insure digits only
    DATE="$YEAR:$MONTH:$DAY"                        # create a DATE string
    # Check the filename's derived date from for validity (not NULL strings)
    #  and that the date falls within the proper range
    if ([ ! -z "${YEAR}" ] && [ ! -z "${MONTH}" ] && [ ! -z "${DAY}" ]) && \
    [ $(IsValidDate $YEAR $MONTH $DAY) == 1 ]  ; then
      if [ $DEBUG == 1 ] ; then echo "ValidDate: $(IsValidDate $YEAR $MONTH $DAY)" ; fi
      FN_WD=0               # date prefix exists, do not append the derived date to the filename.
    else
      FN_WD=1               # append the derived date prefix to the filename.
    fi

    # Next, attempt to find an EXIF CreateDate from the file, if it exists.
    DATE=$(exiftool -s -f -CreateDate $FN | awk '{print $3}')
    # Perform sanity check on correctly extracted EXIF CreateDate
    if [ "${DATE}" != "-" ] && [ "${DATE}" != "0000:00:00" ] ; then
      # Good date extracted, so extract the year, month and day
      # of month from the EXIF info
      echo "A valid ${WHT}CreateDate${NML} was found, using it."
      YEAR=${DATE:0:4}
      MONTH=${DATE:5:2}
      DAY=${DATE:8:2}
      NOTE="(by CreateDate)"

    else
      # EXIF CreateDate invalid or not found, so attempt to derive the
      # date from the file name.
      YEAR=$(echo ${FN:0:4} | egrep -E ^[0-9]+$ )       # insure digits only
      MONTH=$(echo ${FN:4:2} | egrep -E ^[0-9]+$ )  # insure digits only
      DAY=$(echo ${FN:6:2} | egrep -E ^[0-9]+$ )        # insure digits only
      DATE="$YEAR:$MONTH:$DAY"                      # create a DATE string

      # check the extracted date from filename for validity (not NULL strings)
      #  and that the date falls within the proper range
      #if [ -z "${YEAR}" ] || [ -z "${MONTH}" ] || [ -z "${DAY}" ] ; then
      if ([ ! -z "${YEAR}" ] && [ ! -z "${MONTH}" ] && [ ! -z "${DAY}" ]) && \
      [ $(IsValidDate $YEAR $MONTH $DAY) == 1 ]  ; then
        echo "A valid ${WHT}FileNameDate${NML} was found, using it."
        NOTE="(by file name)"

      else
        # EXIF CreateDate and FileNameDate extraction failed, so attempt
        # to extract the EXIF FileModifyDate from the file, if it exists.
        DATE=$(exiftool -s -f -FileModifyDate $FN | awk '{print $3}')
        # Perform sanity check on correctly extracted EXIF FileModifyDate
        if [ "${DATE}" != "-" ] && [ "${DATE}" != "0000:00:00" ] ; then
          # Good FileModifyDate found, extract the year, month and
          # day of month from the EXIF info
          echo "A valid EXIF CreateDate and FileNameDate were not found!"
          echo " The innacurate ${WHT}FileModifyDate${NML} will be used."
          YEAR=${DATE:0:4}
          MONTH=${DATE:5:2}
          DAY=${DATE:8:2}
          NOTE="(!inaccurate! by FileModifyDate)"
          FN_WD=0               # date prefix exists, do not append the derived date to the filename.
        else
          echo "Invalid date retrieved!"
          if [ $DEBUG == 1 ] ; then
            echo "Length = ${#YEAR}-${#MONTH}-${#DAY}"
          fi
          echo "Skipping File: $FN..."
          echo
        fi
      fi
    fi

    # Modify the filename if a valid EXIF CreateDate or FileNameDate was found.
    if [ $FN_WD == 0 ] ; then
      FILE=${FILE}.${EXT}
    else
      FILE=${YEAR}${MONTH}${DAY}-${FILE}.${EXT}
    fi

    # Debug output
    if [ $DEBUG == 1 ] ; then DumpDebug $FN $FILE $EXT $FN_WD $DATE $YEAR $MONTH $DAY "$NOTE" ; fi

    # We have a date, hopefully a good one, move the file
    if [ $DRYRUN == 0 ] ; then
      # create the directory structure. Pipe errors to NULL
      mkdir -p $YEAR/$MONTH/$DAY >/dev/null 2>&1
      # move the file to the appropriate directory
      echo " -> Moving $FN to $YEAR/$MONTH/$DAY/$FILE $NOTE"
      mv $FN $YEAR/$MONTH/$DAY/$FILE
      echo
    else
      echo "Dryrun: Moving $FN to $YEAR/$MONTH/$DAY/$FILE"
      echo
    fi
    # Clear the variables
    FN=""; FILE=""; EXT=""; FN_WD=""; DATE=""; YEAR=""; MONTH=""; DAY=""; NOTE=""
  else
    echo
    echo "File $FN not found!"
    echo
  fi
done
Rafael Tavares
  • 5,678
  • 4
  • 32
  • 48
1

have been banging my head around the same or similar problem.
Exiftool is a powerful thing, however it has some limitations and it doesn't really solve file duplicate problem.

This is what I use in my script

# EXIFSubSecCreateDateParser extracts EXIF metadata: the year, month, day, hour, minute, second, subsecond,
# and generates date and note
EXIFSubSecCreateDateParser() {
  # Define a variable and pass the arguments
  EXIF_OUTPUT="${1}"
  # Substitute dots with a common colon delimiter
  EXIF_OUTPUT_SUBSTITUTE="${EXIF_OUTPUT//./:}"
  # Define delimiter
  DELIMITER=":"
  # Concatenate the delimiter with the main string
  DELIMITED_EXIF_OUTPUT="${EXIF_OUTPUT_SUBSTITUTE}${DELIMITER}"
  # Split the text based on the delimiter
  EXIF_OUTPUT_ARRAY=()
  while [[ "${DELIMITED_EXIF_OUTPUT}" ]]; do
    EXIF_OUTPUT_ARRAY+=( "${DELIMITED_EXIF_OUTPUT%%${DELIMITER}*}" )
    DELIMITED_EXIF_OUTPUT="${DELIMITED_EXIF_OUTPUT#*${DELIMITER}}"
  done
  # Assign the array values to the corresponding variables
  YEAR="${EXIF_OUTPUT_ARRAY[0]}"
  MONTH="${EXIF_OUTPUT_ARRAY[1]}"
  DAY="${EXIF_OUTPUT_ARRAY[2]}"
  HOUR="${EXIF_OUTPUT_ARRAY[3]}"
  MINUTE="${EXIF_OUTPUT_ARRAY[4]}"
  SECOND="${EXIF_OUTPUT_ARRAY[5]}"
  SUBSECOND="${EXIF_OUTPUT_ARRAY[6]}"
  DATE="${YEAR}:${MONTH}:${DAY}"
}

# Attempting to extract EXIF metadata from the file to an array.
EXIF_EXTRACT=( $(exiftool -s -f -SubSecCreateDate -CreateDate -Model "${WIPSortedFileAbsolutePath}" | awk '{print $3":"$4}') )

# Assigning the array values to variables
EXIF_SubSecCreateDate_OUTPUT="${EXIF_EXTRACT[0]}"  
EXIF_CreateDate_OUTPUT="${EXIF_EXTRACT[1]}"  
EXIF_Model_OUTPUT="${EXIF_EXTRACT[2]}"  

# Setting Target Directory structure with TargetDirectoryStructure
DestinationStructure="${YEAR}/${MONTH}/${DAY}"

# Setting Filename format
FormatedFileName="${MODEL}-${YEAR}${MONTH}${DAY}-${HOUR}${MINUTE}${SECOND}-${SUBSECOND}.${NormalisedFileExtension}"

# Moving the file to the Desination File Path
mv "${File}" "${DestinationPath}/${DestinationStructure}/${FormatedFileName}"

I found some answers but piecemeal solutions did not go far enough. I ended up writing my own thing with a kick-start based off Johnny Quest's script.

Anyway, have a look at https://github.com/ivang-coder/Neatly-Sorted
It might give you some ideas for your endeavor.

Rafael Tavares
  • 5,678
  • 4
  • 32
  • 48