#! /usr/bin/env nix-shell #! nix-shell -i bash -p bash exiftool # Directory to start the search base_dir=~/Pictures/Photos/Camera log_file="metadata_changes.log" temp_file="metadata_changes_temp.log" # Function to extract date and time from filename extract_datetime_from_filename() { filename=$(basename "$1") datetime="" # Patterns to match filenames with date and time if [[ "$filename" =~ ([0-9]{4})-([0-9]{2})-([0-9]{2})_([0-9]{2})_([0-9]{2})_([0-9]{2}) ]]; then datetime="${BASH_REMATCH[1]}:${BASH_REMATCH[2]}:${BASH_REMATCH[3]} ${BASH_REMATCH[4]}:${BASH_REMATCH[5]}:${BASH_REMATCH[6]}" elif [[ "$filename" =~ ([0-9]{4})([0-9]{2})([0-9]{2})_([0-9]{2})([0-9]{2})([0-9]{2}) ]]; then datetime="${BASH_REMATCH[1]}:${BASH_REMATCH[2]}:${BASH_REMATCH[3]} ${BASH_REMATCH[4]}:${BASH_REMATCH[5]}:${BASH_REMATCH[6]}" elif [[ "$filename" =~ ([0-9]{8})_([0-9]{6}) ]]; then datetime="${BASH_REMATCH[1]:0:4}:${BASH_REMATCH[1]:4:2}:${BASH_REMATCH[1]:6:2} ${BASH_REMATCH[2]:0:2}:${BASH_REMATCH[2]:2:2}:${BASH_REMATCH[2]:4:2}" elif [[ "$filename" =~ ([0-9]{8})([0-9]{6}) ]]; then datetime="${BASH_REMATCH[1]:0:4}:${BASH_REMATCH[1]:4:2}:${BASH_REMATCH[1]:6:2} ${BASH_REMATCH[2]:0:2}:${BASH_REMATCH[2]:2:2}:${BASH_REMATCH[2]:4:2}" elif [[ "$filename" =~ ([0-9]{8}) ]]; then datetime="${BASH_REMATCH[1]:0:4}:${BASH_REMATCH[1]:4:2}:${BASH_REMATCH[1]:6:2} 00:00:00" elif [[ "$filename" =~ ([0-9]{4})-([0-9]{2})-([0-9]{2})\ ([0-9]{2})_([0-9]{2})_([0-9]{2}) ]]; then datetime="${BASH_REMATCH[1]}:${BASH_REMATCH[2]}:${BASH_REMATCH[3]} ${BASH_REMATCH[4]}:${BASH_REMATCH[5]}:${BASH_REMATCH[6]}" elif [[ "$filename" =~ ([0-9]{4})-([0-9]{2})-([0-9]{2}) ]]; then datetime="${BASH_REMATCH[1]}:${BASH_REMATCH[2]}:${BASH_REMATCH[3]} 00:00:00" fi # Validate extracted date if echo "$datetime" | grep -qE '^([0-9]{4}):([0-9]{2}):([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})$'; then year=$(echo "$datetime" | cut -d':' -f1) month=$(echo "$datetime" | cut -d':' -f2) day=$(echo "$datetime" | cut -d':' -f3 | cut -d' ' -f1) hour=$(echo "$datetime" | cut -d' ' -f2 | cut -d':' -f1) minute=$(echo "$datetime" | cut -d':' -f2 | cut -d':' -f2) second=$(echo "$datetime" | cut -d':' -f2 | cut -d':' -f3) # Remove leading zeros for validation year=$((10#$year)) month=$((10#$month)) day=$((10#$day)) hour=$((10#$hour)) minute=$((10#$minute)) second=$((10#$second)) if ((year < 2009 || year > 2024)) || ((month < 1 || month > 12)) || ((day < 1 || day > 31)) || ((hour > 23)) || ((minute > 59)) || ((second > 59)); then datetime="" fi else datetime="" fi echo "$datetime" } # Function to log changes log_change() { photo="$1" old_date="$2" new_date="$3" action="$4" touch "$temp_file" { echo "File: $photo" echo " Old Date: $old_date" echo " New Date: $new_date" if [ -n "$action" ]; then echo " Suggested action: $action" fi echo "" } >>"$temp_file" } # Function to handle errors and convert images to JPEG using ImageMagick handle_conversion_error() { photo="$1" file_type=$(file --mime-type -b "$photo") if [[ $file_type == image/* ]]; then echo "Converting $photo to jpg using ImageMagick." mogrify -format jpg "$photo" else echo "File is a video or unsupported type: $photo. Skipping conversion." fi } # Loop through each year directory for year_dir in "$base_dir"/*/; do year=$(basename "$year_dir") # Exclude VFS directory if [ "$year" == "VFS" ]; then continue fi # Loop through each month directory for month_dir in "$year_dir"*/; do month=$(basename "$month_dir") # Loop through each photo in the month directory for photo in "$month_dir"*; do if [ -f "$photo" ]; then # Extract the date from the photo metadata photo_date=$(exiftool -DateTimeOriginal -d "%Y:%m:%d %H:%M:%S" "$photo" | awk -F': ' '{print $2}') if [ -z "$photo_date" ]; then # Scenario 2: No date metadata or invalid date filename_datetime=$(extract_datetime_from_filename "$photo") if [ -n "$filename_datetime" ]; then new_date="$filename_datetime" log_change "$photo" "No date metadata" "$new_date" echo "Found: $photo" echo " Extracted date from filename: $new_date" else new_date="$year:$month:01 00:00:00" log_change "$photo" "No date metadata" "$new_date" echo "Found: $photo" echo " Defaulting to directory date: $new_date" fi else photo_year=$(echo "$photo_date" | cut -d':' -f1) photo_month=$(echo "$photo_date" | cut -d':' -f2) if [ "$photo_year" != "$year" ] || [ "$photo_month" != "$month" ]; then # Scenario 1: Date mismatch filename_datetime=$(extract_datetime_from_filename "$photo") if [ -n "$filename_datetime" ]; then filename_year=$(echo "$filename_datetime" | cut -d':' -f1) filename_month=$(echo "$filename_datetime" | cut -d':' -f2) if [ "$photo_year" == "$filename_year" ] && [ "$photo_month" == "$filename_month" ]; then echo "Mismatch found: $photo" echo " Directory date: $year/$month" echo " Photo date: $photo_date" echo " Filename date matches photo date, file is in the wrong directory." action="Move to $filename_year/$filename_month" log_change "$photo" "$photo_date" "$filename_datetime" "$action" else new_date="$filename_datetime" log_change "$photo" "$photo_date" "$new_date" echo "Mismatch found: $photo" echo " Directory date: $year/$month" echo " Photo date: $photo_date" echo " Filename date: $filename_datetime" echo " Suggested action: Update photo metadata to match filename date." fi else new_date="$year:$month:01 00:00:00" log_change "$photo" "$photo_date" "$new_date" echo "Mismatch found: $photo" echo " Directory date: $year/$month" echo " Photo date: $photo_date" echo " No valid date found in filename. Defaulting to directory date." fi fi fi fi done done # Ask for confirmation before proceeding to the next year read -rp "Finished processing year $year. Do you want to save the changes and continue to the next year? (y/n): " response if [[ "$response" != "y" ]]; then echo "Stopping script." rm -f "$temp_file" touch "$temp_file" exit 0 else # Apply changes and save log while IFS= read -r line; do if [[ "$line" == File:* ]]; then photo=$(echo "$line" | cut -d' ' -f2-) elif [[ "$line" == " New Date:"* ]]; then new_date=$(echo "$line" | cut -d' ' -f3-) if ! exiftool -overwrite_original -DateTimeOriginal="$new_date" "$photo"; then handle_conversion_error "$photo" fi elif [[ "$line" == " Suggested action: Move to"* ]]; then target_directory=$(echo "$line" | cut -d' ' -f4-) target_path="$base_dir/$target_directory" echo "$target_path" # mkdir -p "$target_path" && mv "$photo" "$target_path/" fi done <"$temp_file" cat "$temp_file" >>"$log_file" rm -f "$temp_file" touch "$temp_file" fi done