Skip to content

Instantly share code, notes, and snippets.

@mproffitt
Last active October 15, 2021 14:33
Show Gist options
  • Save mproffitt/d720a1a513cd3155316783a023f0edf2 to your computer and use it in GitHub Desktop.
Save mproffitt/d720a1a513cd3155316783a023f0edf2 to your computer and use it in GitHub Desktop.
dtcp Darktable image management command
#!/bin/bash
# dtcp.bash
#
# darktable copy - workflow and image management script
#
# @author Martin Proffitt
#
# Script flags
#
# --install Install the script to /usr/local/bin, setup dependencies and systemd services
# --input Monitor the input directory for new files
# --output Monitor the output directory for fresh exports
# --resize Resize a given image file
# --resize-all Resize all image files under a directory (recursive)
#
# Dependencies
#
# - imagemagick
# - inotify-tools
# - exiftools
#
# Image location definitions
#
# For this particular workflow, we rely on a directory structure under
# the Pictures directory on linux.
#
# New images should be loaded into the "input" folder which is monitored by
# inotifywait. From here, all images will be transferred to the workbench
# folder, organised by image type, then date. This gives flexibility in
# finding individual daily filom-rolls within darktable.
#
# Exports from Darktable should be placed into the finals/darktable folder
# as this too is monitored by inotifywait for resizing and moving using imagemagick.
#
# Resized images will be placed into Pictures/final/resized organised by date then type
# as this makes it simpler to find all images of a particular type exported for a given
# days film roll.
PICTURES_DIR="${HOME}/Pictures"
INPUT_DIR="${PICTURES_DIR}/input"
WORKBENCH="${PICTURES_DIR}/workbench"
FINALS="${PICTURES_DIR}/final"
DARKTABLE="${FINALS}/darktable"
RESIZED="${FINALS}/resized"
# Create the necessary directories
DIRS=(
"${PICTURES_DIR}"
"${INPUT_DIR}"
"${WORKBENCH}"
"${FINALS}"
"${DARKTABLE}"
"${RESIZED}"
)
for directory in ${DIRS[@]}; do
[ ! -d ${directory} ] && mkdir -p ${directory}
done
# List of sizes to be exported by default
# Add or remove any sizes as necessary.
# Additional resizing needs to be added within the resize function
# as this list is for basic resizing only
SIZES=(
320
640
1024
1920
"original"
)
##
# Move files from the input folder to the workbench sorted by type > date
#
# @param filename String
#
function input()
{
cd ${PICTURES_DIR}
local filename="$(basename $1)"
local ext="${filename##*.}"
# I tend to locate cr2 and dng together as they are both raw extension types
[ "${ext}" == 'dng' ] && ext=raw
[ "${ext}" == 'cr2' ] && ext=raw
if [ "$(cut -d_ -f1 <<<${filename})" == 'IMG' ] ; then
mv "input/${filename}" "input/$(cut -d_ -f2- <<<${filename})"
file="$(cut -d_ -f2- <<<${filename})"
fi
# Take the date from the filename instead of using todays date
# that allows us to find files by day of shoot
date=$(date -d $(cut -d_ -f1 <<< ${filename}) +%Y-%m-%d)
# Create the daily filmroll folder if it doesn't exist
[ ! -d "${WORKBENCH}/${ext}/${date}" ] && mkdir -p "${WORKBENCH}/${ext}/${date}"
# Don't overwrite images - get rid of duplicates instead
if [ -f "${WORKBENCH}/${ext}/${date}/${filename}" ] ; then
rm "${INPUT_DIR}/${filename}"
else
mv "${INPUT_DIR}/${filename}" "${WORKBENCH}/${ext}/${date}/${filename}"
fi
}
##
# Resize function
#
# @param filename String
#
# Resizes the given image based on the predefined list of sizes given above.
#
function resize()
{
local filename="$(basename $1)"
local ext="${filename##*.}"
date=$(date -d $(cut -d_ -f1 <<< ${filename}) +%Y-%m-%d)
local path="${RESIZED}/${date}/${ext}"
# Loop over all sizes defined at the global level
for size in ${SIZES[@]}; do
[ ! -d "${path}/${size}" ] && mkdir -p "${path}/${size}"
# create the original directory (line above) but don't copy the file there
# yet as we may still want to create resizes against it
if [ "${size}" != 'original' ] ; then
mogrify -path ${path}/${size} -resize ${size} ${DARKTABLE}/${filename}
fi
done
# Any additional sizing should be done here before the move command.
# finally move the original image to the days "original" folder
# note, this is the original export, not the raw.
mv ${DARKTABLE}/${filename} ${path}/original/${filename}
}
##
# Install the script and dependencies
#
# This script will install to /usr/local/bin
# and set up service files in /usr/lib/systemd/user
#
function install()
{
local scriptpath=$(dirname $1)
local script=$(basename $1)
# We require root priviledges to install this script
sudo mv ${scriptpath}/${script} /usr/local/bin/${script}
# debian and redhat dependency install
if [ -f /etc/lsb-release ] ; then
sudo apt install -y imagemagick inotify-tools libimage-exiftool-perl
elif [ -f /etc/redhat-release ] ; then
sudo dnf install -y imagemagick inotify-tools perl-Image-ExifTool.noarch
fi
for action in input output; do
service=dtcp-${action}-${USER}.service
cat <<EOF | sudo tee /usr/lib/systemd/user/${service}
[Service]
ExecStart=/usr/local/bin/${script} --${action}
Restart=always
RestartSec=1
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=dtcp-${action}-${USER}
UMask=0077
[Install]
WantedBy=multi-user.target
EOF
if systemctl --user is-enabled --quiet ${service}; then
echo "${service} previously enabled, reloading and restarting"
systemctl --user daemon-reload
systemctl --user restart ${service}
else
echo "enabling and starting ${service}"
systemctl --user enable ${service}
systemctl --user start ${service}
fi
systemctl --user status ${service}
done
}
##
# Rename the file based on date-time
#
# If the file does not matche the format YYYYMMDD_HHMMSS then we rewrite the filename based on
# either the EXIF datestamp or the file modified time, whichever is available.
#
function image_rename()
{
if ! file "$@" | grep -qoP '^.+: \w+ image'; then
echo "${@} is not an image file"
return 1
fi
local dirname="$(dirname "$@")"
local filename="$(basename "$@")"
local extension="${filename##*.}"
local newextension="${extension,,}"
filename="${filename%.*}"
[ "${newextension}" == 'jpeg' ] && newextension=jpg
datearray=(
$(sed 's/[_\ ]/\ /g' <<< "${filename}")
)
let i=0
local contains_date=false
until ${contains_date}; do
date -d "${datearray[$i]}" &>/dev/null
[ $? -eq 0 ] && contains_date=true || contains_date=false
if [ $i -ge $((${#datearray[@]} - 1)) ]; then
break
fi
let i=$(($i+1))
done
local date=''
if ${contains_date}; then
let i=$(($i - 1))
date=$(sed 's/\ /_/g' <<< "${datearray[@]:${i}}")
else
date=$(exiftool "$@" | grep 'Date\/Time Original' | cut -d: -f2- | awk '{gsub(/^ /,"");gsub(/ /,"_");gsub(":","");print}')
if [ -z "${date}" ]; then
# fall back to stat as a last resort
date=$(stat -c %y "$@" | awk -F\. '{gsub(/ /,"_",$1); gsub(/[:-]/, "",$1); print $1;}')
fi
fi
#if [ "${filename}.${extension}" != "${date}.${newextension}" ]; then
mv "${dirname}/${filename}.${extension}" "${DARKTABLE}/${date}.${newextension}"
#fi
}
##
# Rename all image files found under a directory using the image_rename function above
#
function directory_rename()
{
while read -d '' filename; do
image_rename "$filename"
done < <(find . -type f -print0)
}
##
# Bind function for the export directory
#
# Calls inotifywait with -m to continually monitor for new files
#
function inotify_output()
{
inotifywait -m ${DARKTABLE} -e create -e moved_to |
while read d action filename; do
# wait for file to be closed then sleep for .1 seconds as darktable moves this file.
if [ "${action}" != 'MOVED_TO' ] ; then
[ -f "${DARKTABLE}/${filename}" ] && inotifywait -e close_write ${DARKTABLE}/${filename}
sleep .1
fi
# We'll have another inotifywait command as a temporary file is moved so if the file
# is deleted, skip.
[ ! -f "${DARKTABLE}/${filename}" ] && continue
resize ${filename}
done
}
##
# Bind function for the input directory
#
# Calls inotifywait with -m to continually monitor for new files
#
function inotify_input()
{
inotifywait -m ${INPUT_DIR} -e create -e moved_to |
while read d a filename; do
[ -f "${INPUT_DIR}/${filename}" ] && inotifywait -e close_write ${INPUT_DIR}/${filename}
input ${filename}
done
}
##
# print usage information
function usage()
{
echo "Usage: dtcp.bash --[install|input|output|rename|rename-all]"
echo " --install - install script and dependencies"
echo " --input - start an inotifywait daemon monitoring the input folder"
echo " --output - start an inotifywait daemon monitoring the output folder"
echo " --rename <filename> - rename a single image file"
echo " --rename-all - renames all image files found under a directory"
}
##
# execute the script given the correct input arguments
action=$1
case ${action} in
"--install")
install $0
;;
"--input")
inotify_input
;;
"--output")
inotify_output
;;
"--rename")
image_rename "$2"
;;
"--rename-all")
directory_rename
;;
*)
usage
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment