Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save DonRichards/f7d421d6ac86ee1b2fcf4a62caf303cc to your computer and use it in GitHub Desktop.
Save DonRichards/f7d421d6ac86ee1b2fcf4a62caf303cc to your computer and use it in GitHub Desktop.
Fix Google Photos Takeout Timestamp Issues for Immich

Fix Google Photos Takeout Timestamp Issues for Immich

Problem: Google Photos Takeout exports modify file timestamps during the zip process, breaking chronological sorting in photo management apps like Immich. While Google includes the original timestamps in .supplemental-metadata.json files, most apps ignore these.

Solution: This script restores original timestamps from Google's metadata files to both the filesystem and EXIF data, enabling proper date-based sorting and organization.

WARNING: This script is going to set and reset you machine's date repidly. I'm using it on a raspberry pi/NAS. It will set it back when it's complete after every file.

What it does:

  • Reads .supplemental-metadata.json files from Google Photos Takeout
  • Extracts original photo/video timestamps
  • Updates filesystem timestamps (touch)
  • Updates EXIF metadata (DateTimeOriginal for photos, CreationDate for videos)
  • Supports common formats: JPEG, TIFF, PNG, RAW files (CR2, NEF, ARW, etc.), MP4, MOV, AVI, etc.

Requirements:

  • exiftool - for EXIF metadata manipulation
  • jq - for JSON parsing

Usage:

  1. Extract your Google Photos Takeout zip
  2. Update the cd path in the script to point to your /Takeout/Google Photos folder
  3. Optionally set FOLDER_NAME to start from a specific folder (leave empty to process all)
  4. Run: bash update_dates.sh

Perfect for:

  • Immich users importing Google Photos exports
  • Anyone needing to restore original timestamps after Google Photos Takeout
  • Bulk EXIF timestamp correction

Set your host machine's date back manually

  • sudo date -s "$(curl -s --head http://google.com | grep '^Date:' | sed 's/Date: //g')"

Note: Always backup your photos before running any bulk modification script!

#!/bin/bash
shopt -s nullglob
CURRENT_DATE=$(sudo date -s "$(curl -s --head http://google.com | grep '^Date:' | sed 's/Date: //g')")
echo "Current date: $CURRENT_DATE"
# DESCRIBE WHAT THIS SCRIPT DOES
# This script processes Google Photos Takeout backup to restore original timestamps.
# It reads .supplemental-metadata.json files and applies the original timestamps to:
# 1. File system timestamps (touch)
# 2. EXIF metadata (DateTimeOriginal for photos, CreationDate for videos)
# 3. Handles various file types: JPG, CR2, RAW, MP4, MOV, etc.
# Check if required tools are installed
for tool in exiftool jq; do
if ! command -v "$tool" &> /dev/null; then
echo "$tool could not be found. Please install it."
exit 1
fi
done
echo "Current directory contents:"
cd "/RAID/Pictures/Google Photos Backup/Takeout/Google Photos" || exit 1
ls -alh
# Set your starting folder here
FOLDER_NAME=""
starting_point_found=0
processed_count=0
error_count=0
# Function to detect actual file type
detect_file_type() {
local file="$1"
exiftool -FileType -b "$file" 2>/dev/null
}
# Function to diagnose problematic files
diagnose_file() {
local file="$1"
echo " πŸ” Diagnosing file: $file"
echo " πŸ“‹ File size: $(du -h "$file" | cut -f1)"
echo " πŸ“‹ File permissions: $(ls -la "$file" | awk '{print $1, $3, $4}')"
echo " πŸ“‹ Current EXIF dates:"
exiftool -DateTimeOriginal -CreateDate -ModifyDate -s "$file" 2>/dev/null | sed 's/^/ /' || echo " No EXIF data found"
echo " πŸ“‹ File signature: $(file "$file")"
}
# Function to update metadata based on file type
update_metadata() {
local file="$1"
local timestamp="$2"
local exif_date="$3"
# Detect actual file type, not just extension
local file_type
file_type=$(detect_file_type "$file")
if [[ -z "$file_type" ]]; then
echo " ❌ Could not detect file type for $file"
return 1
fi
echo " πŸ“ File type: $file_type"
case "$file_type" in
"JPEG"|"TIFF"|"PNG"|"CR2"|"NEF"|"ARW"|"DNG"|"RAF"|"ORF"|"PEF"|"RW2")
# Photo files - use DateTimeOriginal
# Set the date to match the timestamp.
sudo date -s "$system_date"
# Handle files with wrong extensions by temporarily renaming them
local original_file="$file"
local new_temp_file=$(mktemp)
cp "$file" "$new_temp_file"
local file_ext="${file##*.}"
local needs_rename=false
# if [[ "$file_type" != "$file_ext" ]]; then
echo " πŸ”„ Extension mismatch: $file_type file with .$file_ext extension"
# Create temporary file with correct extension
local correct_ext=$(echo "$file_type" | tr '[:upper:]' '[:lower:]')
working_file="${file%.*}.temp_${correct_ext}"
cp "$new_temp_file" "$working_file" || exit 1
local needs_rename=true
local new_creation_date=$(stat -c %y "$working_file" | cut -d'.' -f1)
# Convert stat format (2013-01-21 20:00:07) to EXIF format (2013:01:21 20:00:07)
local new_creation_date_exif=$(echo "$new_creation_date" | sed 's/-/:/; s/-/:/')
# Extract just the date portion (YYYY:MM:DD) for comparison, ignoring time
local creation_date_only=$(echo "$new_creation_date_exif" | cut -d' ' -f1)
local exif_date_only=$(echo "$exif_date" | cut -d' ' -f1)
rm -f "$new_temp_file"
if [[ "$creation_date_only" != "$exif_date_only" ]]; then
echo " ❌ Date mismatch (ignoring time): $creation_date_only != $exif_date_only"
echo " ❌ File: $working_file"
echo " ❌ Creation date (full): $new_creation_date_exif"
echo " ❌ EXIF date (full): $exif_date"
echo " ❌ Error out."
exit 1
fi
echo " πŸ“ Created temporary file: $(basename "$working_file")"
# First try with DateTimeOriginal
if exiftool -q -overwrite_original -DateTimeOriginal="$exif_date" "$working_file" 2>/tmp/exif_error.txt; then
echo " βœ… Updated EXIF DateTimeOriginal"
success=true
else
# Try alternative date tags for problematic files
echo " ⚠️ DateTimeOriginal failed, trying CreateDate..."
if exiftool -q -overwrite_original -CreateDate="$exif_date" -ModifyDate="$exif_date" "$working_file" 2>/tmp/exif_error2.txt; then
echo " βœ… Updated EXIF CreateDate/ModifyDate"
success=true
else
success=false
fi
fi
# Handle cleanup and file replacement
if [[ "$needs_rename" == true ]]; then
if [[ "$success" == true ]]; then
# Replace original with updated temp file
mv "$working_file" "$original_file"
echo " πŸ”„ Replaced original file with updated version"
else
# Remove temp file on failure
rm -f "$working_file"
echo " πŸ—‘οΈ Cleaned up temporary file"
fi
fi
# Reset the date to the current date.
sudo date -s "$CURRENT_DATE"
if [[ "$success" == true ]]; then
return 0
else
echo " ❌ Failed to update EXIF metadata"
echo " πŸ” Error details:"
if [[ -f /tmp/exif_error.txt ]]; then
sed 's/^/ /' /tmp/exif_error.txt
fi
if [[ -f /tmp/exif_error2.txt ]]; then
sed 's/^/ /' /tmp/exif_error2.txt
fi
# Run detailed diagnosis
diagnose_file "$original_file"
return 1
fi
;;
"MP4"|"MOV"|"AVI"|"MKV"|"WEBM"|"3GP"|"M4V")
# Video files - use CreationDate and MediaCreateDate
if exiftool -q -overwrite_original -CreationDate="$exif_date" -MediaCreateDate="$exif_date" "$file" 2>/dev/null; then
echo " βœ… Updated video metadata"
return 0
else
echo " ❌ Failed to update video metadata"
return 1
fi
;;
*)
echo " ⚠️ Unsupported file type: $file_type"
return 1
;;
esac
}
find . -type d | sort | while read -r dir; do
dir_basename="$(basename "$dir")"
# Start processing from the specified folder onwards
if [[ "$dir_basename" == "$FOLDER_NAME" || -z "$FOLDER_NAME" ]]; then
starting_point_found=1
fi
if [[ $starting_point_found == 0 ]]; then
continue
fi
echo ""
echo "πŸ“‚ Processing directory: $dir"
cd "$dir" || continue
for json in *.supplemental-metadata.json; do
if [[ ! -f "$json" ]]; then
continue
fi
img="${json%.supplemental-metadata.json}"
if [[ ! -f "$img" ]]; then
echo " ⚠️ Image file $img not found, skipping..."
continue
fi
# Extract timestamp from JSON
ts=$(jq -r '.photoTakenTime.timestamp' "$json" 2>/dev/null)
if [[ ! "$ts" =~ ^[0-9]+$ ]]; then
echo " ❌ Invalid timestamp in $json, skipping..."
((error_count++))
continue
fi
# Convert timestamp to different formats
touch_time=$(date -u -d @"$ts" +%Y%m%d%H%M.%S)
exif_date=$(date -u -d @"$ts" +"%Y:%m:%d %H:%M:%S")
system_date=$(date -u -d @"$ts" +"%Y-%m-%d %H:%M:%S")
readable_date=$(date -u -d @"$ts" +"%Y-%m-%d %H:%M:%S UTC")
echo " πŸ”„ Processing: $img"
echo " πŸ“… Original date: $readable_date"
echo " πŸ• Setting timestamp: $touch_time"
sudo date -s "$system_date"
# Update filesystem timestamp
if touch -t "$touch_time" "$img" 2>/dev/null; then
echo " βœ… Updated filesystem timestamp"
else
echo " ❌ Failed to update filesystem timestamp"
((error_count++))
continue
fi
# Reset the date to the current date.
sudo date -s "$CURRENT_DATE"
# Update metadata based on file type
if update_metadata "$img" "$touch_time" "$exif_date"; then
((processed_count++))
else
((error_count++))
fi
done
cd - > /dev/null
done
echo ""
echo "πŸ“Š Processing complete!"
echo "βœ… Successfully processed: $processed_count files"
echo "❌ Errors encountered: $error_count files"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment