Skip to content

Instantly share code, notes, and snippets.

@Mstaaravin
Last active March 18, 2025 13:55
Show Gist options
  • Save Mstaaravin/6823b6c00f47e06ce71461da9c4dfa77 to your computer and use it in GitHub Desktop.
Save Mstaaravin/6823b6c00f47e06ce71461da9c4dfa77 to your computer and use it in GitHub Desktop.
A bash script that copies media files from exported Takeout Google Photos to a new location while setting their modification dates based on filenames or directory structure.
#!/bin/bash
# Script to copy specific media files (IMG*.jpg, VID*.mp4, Screenshot*.png) to a target directory
# and set their modification times based on various date patterns while avoiding duplicates
# Check if destination directory is provided
if [ $# -ne 1 ]; then
echo "Usage: $0 <target_directory>"
exit 1
fi
# Base directory to search from
BASE_DIR="~/Takeout/Google Photos"
TARGET_DIR="$1"
# Create target directory if it doesn't exist
if [ ! -d "$TARGET_DIR" ]; then
mkdir -p "$TARGET_DIR"
echo "Created target directory: $TARGET_DIR"
fi
# Function to process a file and extract date from its name or parent directory
process_file() {
local file="$1"
local dir="$(dirname "$file")"
local dir_name="$(basename "$dir")"
local filename="$(basename "$file")"
local timestamp=""
local extracted_date=""
echo "Processing file: $filename"
# Try to extract date from directory name first
if [[ "$dir_name" =~ ([0-9]{4})-([0-9]{2})-([0-9]{2}) ]]; then
# Directory has YYYY-MM-DD pattern
year=${BASH_REMATCH[1]}
month=${BASH_REMATCH[2]}
day=${BASH_REMATCH[3]}
extracted_date="${year}${month}${day}"
echo " Found date in directory name: $year-$month-$day"
fi
# Try to extract date/time from filename based on various patterns
# Pattern: Screenshot at YYYY-MM-DD HH-MM-SS.png
if [[ "$filename" =~ Screenshot[[:space:]]at[[:space:]]([0-9]{4})-([0-9]{2})-([0-9]{2})[[:space:]]([0-9]{2})-([0-9]{2})-([0-9]{2}) ]]; then
year=${BASH_REMATCH[1]}
month=${BASH_REMATCH[2]}
day=${BASH_REMATCH[3]}
hour=${BASH_REMATCH[4]}
minute=${BASH_REMATCH[5]}
second=${BASH_REMATCH[6]}
timestamp="${year}${month}${day}${hour}${minute}.${second}"
echo " Found timestamp in Screenshot filename: $year-$month-$day $hour:$minute:$second"
# Pattern: Screenshot from YYYY-MM-DD HH-MM-SS.png
elif [[ "$filename" =~ Screenshot[[:space:]]from[[:space:]]([0-9]{4})-([0-9]{2})-([0-9]{2})[[:space:]]([0-9]{2})-([0-9]{2})-([0-9]{2}) ]]; then
year=${BASH_REMATCH[1]}
month=${BASH_REMATCH[2]}
day=${BASH_REMATCH[3]}
hour=${BASH_REMATCH[4]}
minute=${BASH_REMATCH[5]}
second=${BASH_REMATCH[6]}
timestamp="${year}${month}${day}${hour}${minute}.${second}"
echo " Found timestamp in Screenshot filename: $year-$month-$day $hour:$minute:$second"
# Pattern: Screenshot_YYYYMMDD-HHMMSS.png
elif [[ "$filename" =~ Screenshot_([0-9]{8})-([0-9]{6}) ]]; then
date_part=${BASH_REMATCH[1]}
time_part=${BASH_REMATCH[2]}
year=${date_part:0:4}
month=${date_part:4:2}
day=${date_part:6:2}
hour=${time_part:0:2}
minute=${time_part:2:2}
second=${time_part:4:2}
timestamp="${year}${month}${day}${hour}${minute}.${second}"
echo " Found timestamp in Screenshot filename: $year-$month-$day $hour:$minute:$second"
# Pattern: YYYYMMDD_HHMMSS (common in camera photos)
elif [[ "$filename" =~ ([0-9]{8})_([0-9]{6}) ]]; then
date_part=${BASH_REMATCH[1]}
time_part=${BASH_REMATCH[2]}
year=${date_part:0:4}
month=${date_part:4:2}
day=${date_part:6:2}
hour=${time_part:0:2}
minute=${time_part:2:2}
second=${time_part:4:2}
timestamp="${year}${month}${day}${hour}${minute}.${second}"
echo " Found timestamp in filename: $year-$month-$day $hour:$minute:$second"
# Pattern: IMG_YYYYMMDD_HHMMSS or VID_YYYYMMDD_HHMMSS
elif [[ "$filename" =~ (IMG|VID)_([0-9]{8})_([0-9]{6}) ]]; then
date_part=${BASH_REMATCH[2]}
time_part=${BASH_REMATCH[3]}
year=${date_part:0:4}
month=${date_part:4:2}
day=${date_part:6:2}
hour=${time_part:0:2}
minute=${time_part:2:2}
second=${time_part:4:2}
timestamp="${year}${month}${day}${hour}${minute}.${second}"
echo " Found timestamp in filename: $year-$month-$day $hour:$minute:$second"
# Pattern: IMG_YYYYMMDD_HHMMSS (without underscore)
elif [[ "$filename" =~ IMG_([0-9]{8})([0-9]{6}) ]]; then
date_part=${BASH_REMATCH[1]}
time_part=${BASH_REMATCH[2]}
year=${date_part:0:4}
month=${date_part:4:2}
day=${date_part:6:2}
hour=${time_part:0:2}
minute=${time_part:2:2}
second=${time_part:4:2}
timestamp="${year}${month}${day}${hour}${minute}.${second}"
echo " Found timestamp in filename: $year-$month-$day $hour:$minute:$second"
# Check for VID_XXXXXXXX_HHMMSS_XXX.mp4 pattern (where X is any digit)
elif [[ "$filename" =~ VID_[0-9]+_([0-9]{2})([0-9]{2})([0-9]{2})_ ]]; then
# If we found the time pattern but no date yet, use the directory date
if [[ ! -z "$extracted_date" ]]; then
hour=${BASH_REMATCH[1]}
minute=${BASH_REMATCH[2]}
second=${BASH_REMATCH[3]}
timestamp="${extracted_date}${hour}${minute}.${second}"
echo " Found time in filename, using directory date: $hour:$minute:$second"
fi
fi
# Special case for directories with month-year pattern
if [ -z "$timestamp" ]; then
# Generic pattern for any directory with MM-YYYY format (e.g., "Mendoza 12-2017", "Vacation 05-2018")
if [[ "$dir_name" =~ ([0-9]{1,2})-([0-9]{4})$ ]]; then
# Pattern like "Something MM-YYYY"
month=${BASH_REMATCH[1]}
year=${BASH_REMATCH[2]}
month=$(printf "%02d" $month) # Pad to 2 digits if needed
# Try to extract date from filename if possible
if [[ "$filename" =~ ([0-9]{8}) ]]; then
date_str=${BASH_REMATCH[1]}
year=${date_str:0:4}
month=${date_str:4:2}
day=${date_str:6:2}
echo " Found date in filename: $year-$month-$day"
else
# Default to 15th of the month
day="15"
echo " Found month-year in directory name: $month-$year, using day 15"
fi
# Try to extract time from filename if possible
if [[ "$filename" =~ _([0-9]{6}) ]]; then
time_part=${BASH_REMATCH[1]}
hour=${time_part:0:2}
minute=${time_part:2:2}
second=${time_part:4:2}
else
# Default to noon
hour="12"
minute="00"
second="00"
fi
timestamp="${year}${month}${day}${hour}${minute}.${second}"
fi
fi
# If we didn't find a timestamp, use directory date with noon time
if [ -z "$timestamp" ] && [ ! -z "$extracted_date" ]; then
timestamp="${extracted_date}1200.00"
echo " Using directory date with default time (noon): ${timestamp}"
fi
# If we still don't have a timestamp, get one from file's current mtime
if [ -z "$timestamp" ]; then
# Use file's current modification time
current_date=$(date -r "$file" "+%Y%m%d%H%M.%S")
timestamp=$current_date
echo " No date pattern found, using file's current mtime: $(date -r "$file" "+%Y-%m-%d %H:%M:%S")"
fi
# Copy file to target directory
if [ -f "$TARGET_DIR/$filename" ]; then
# Check for duplicates
source_md5=$(md5sum "$file" | cut -d ' ' -f 1)
target_md5=$(md5sum "$TARGET_DIR/$filename" | cut -d ' ' -f 1)
if [ "$source_md5" = "$target_md5" ]; then
echo " Skipping duplicate: $filename"
# Update timestamp of the existing file
touch -m -t "$timestamp" "$TARGET_DIR/$filename"
echo " Updated timestamp for existing file: $TARGET_DIR/$filename"
else
# Create unique name for file with same name but different content
new_filename="${filename%.*}_$(date +%s).${filename##*.}"
echo " Copying with new name: $new_filename"
cp "$file" "$TARGET_DIR/$new_filename"
touch -m -t "$timestamp" "$TARGET_DIR/$new_filename"
echo " Updated timestamp for: $TARGET_DIR/$new_filename"
fi
else
echo " Copying: $filename"
cp "$file" "$TARGET_DIR/$filename"
touch -m -t "$timestamp" "$TARGET_DIR/$filename"
echo " Updated timestamp for: $TARGET_DIR/$filename"
fi
}
# Find only specific media files recursively
echo "Searching for specific media files (IMG*.jpg, VID*.mp4, Screenshot*.png)..."
find "$BASE_DIR" -type f \( -name "IMG*.jpg" -o -name "VID*.mp4" -o -name "Screenshot*.png" \) | while read file; do
process_file "$file"
done
echo "Process completed. Files copied to $TARGET_DIR with updated modification times."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment