Last active
March 18, 2025 13:55
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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