#!/usr/bin/env bash set -Eeuo pipefail IFS=$'\n\t'
usage() { cat <<'EOF' Usage: rename-photos.sh [--dry-run] FOLDER rename-photos.sh --help
Rename photos using their EXIF capture date.
Options: -n, --dry-run Show planned changes without renaming files -h, --help Show this help message
Output format: YYYYMMDD_HHMMSS.ext
Duplicates: Files with the same timestamp receive a numeric suffix: YYYYMMDD_HHMMSS_001.ext
Supported extensions: jpg, jpeg, heic, heif, tif, tiff, png, webp
Requirements: exiftool EOF }
die() { printf 'Error: %s\n' "$*" >&2 exit 1 }
on_error() {
local exit_code=$?
printf 'Error: command failed at line %s (exit code %s)\n'
"${BASH_LINENO[0]:-unknown}" "$exit_code" >&2
exit "$exit_code"
}
trap on_error ERR
dry_run=false folder=''
while (($# > 0)); do case "$1" in -n|--dry-run) dry_run=true shift ;; -h|--help) usage exit 0 ;; --) shift (($# == 1)) || die 'Exactly one folder must be specified.' folder=$1 shift ;; -*) die "Unknown option: $1" ;; *) [[ -z $folder ]] || die 'Exactly one folder must be specified.' folder=$1 shift ;; esac done
[[ -n $folder ]] || { usage >&2 exit 2 }
command -v exiftool >/dev/null 2>&1 || die 'exiftool is required but was not found in PATH.'
[[ -d $folder ]] || die "Folder does not exist: $folder" [[ -r $folder ]] || die "Folder is not readable: $folder"
if [[ $dry_run == false ]]; then [[ -w $folder ]] || die "Folder is not writable: $folder" fi
Convert the folder to an absolute path for predictable operations.
folder=$(cd -- "$folder" && pwd -P)
declare -a files=()
while IFS= read -r -d '' file; do
files+=("$file")
done < <(
find "$folder" -maxdepth 1 -type f
( -iname '.jpg' -o -iname '.jpeg'
-o -iname '.heic' -o -iname '.heif'
-o -iname '.tif' -o -iname '.tiff'
-o -iname '*.png' -o -iname